248 lines
7.4 KiB
JavaScript
248 lines
7.4 KiB
JavaScript
const logger = require('../utils/logger');
|
|
|
|
/**
|
|
* In-Dialog Handler - Equivalent to Kamailio's WITHINDLG route
|
|
* Handles SIP requests within established dialogs (BYE, re-INVITE, etc.)
|
|
*/
|
|
class InDialogHandler {
|
|
constructor(srf) {
|
|
this.srf = srf;
|
|
}
|
|
|
|
/**
|
|
* Handle media teardown for BYE/CANCEL (equivalent to kamailio.cfg:494-497)
|
|
*/
|
|
handleMediaTeardown(req) {
|
|
if (req.method === 'BYE' || req.method === 'CANCEL') {
|
|
logger.info('[WITHINDLG] Media teardown for %s | Call-ID: %s', req.method, req.get('Call-ID'));
|
|
// RTP proxy cleanup would go here
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle media setup for ACK (equivalent to kamailio.cfg:500-502)
|
|
*/
|
|
handleMediaSetup(req) {
|
|
if (req.method === 'ACK') {
|
|
logger.info('[WITHINDLG] Media setup for ACK | Call-ID: %s', req.get('Call-ID'));
|
|
// RTP proxy setup would go here
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process loose route headers (equivalent to kamailio.cfg:506-561)
|
|
*/
|
|
processLooseRoute(req, res) {
|
|
const routeHeaders = req.get('Route');
|
|
if (!routeHeaders) {
|
|
return false;
|
|
}
|
|
|
|
logger.info('[WITHINDLG] Route headers processed | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Process Route headers to determine next hop
|
|
const routes = Array.isArray(routeHeaders) ? routeHeaders : [routeHeaders];
|
|
const nextRoute = routes[0];
|
|
|
|
if (nextRoute.includes(this.srf.localAddress)) {
|
|
// Route points back to us - check if user is registered locally
|
|
const aor = `${req.uri.user}@${req.uri.host}`;
|
|
const registration = this.srf.registrationHandler?.lookup(aor);
|
|
|
|
if (registration) {
|
|
logger.info('[WITHINDLG] Loose route resolved to local user, forwarding | Call-ID: %s', req.get('Call-ID'));
|
|
return this.forwardToUser(req, res, registration);
|
|
} else {
|
|
logger.warn('[WITHINDLG] Loose route points to us but user not found locally | Call-ID: %s', req.get('Call-ID'));
|
|
res.send(404, 'Not here');
|
|
return true;
|
|
}
|
|
} else {
|
|
// Route is to a different destination, process normally
|
|
logger.info('[WITHINDLG] Forwarding to different destination | Call-ID: %s', req.get('Call-ID'));
|
|
return this.forwardToDestination(req, res, nextRoute);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forward request to registered user
|
|
*/
|
|
async forwardToUser(req, res, registration) {
|
|
try {
|
|
const dlg = await this.srf.createRequest(req.method, registration.contact, {
|
|
headers: {
|
|
'From': req.get('From'),
|
|
'To': req.get('To'),
|
|
'Call-ID': req.get('Call-ID'),
|
|
'CSeq': req.get('CSeq'),
|
|
'Contact': req.get('Contact'),
|
|
'Content-Type': req.get('Content-Type'),
|
|
'Via': req.get('Via'),
|
|
'Route': req.get('Route'),
|
|
'Max-Forwards': req.get('Max-Forwards')
|
|
},
|
|
body: req.body
|
|
});
|
|
|
|
logger.info('[WITHINDLG] Successfully forwarded to user | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Send response back to originator
|
|
res.send(200, 'OK');
|
|
|
|
// Relay responses
|
|
this.relayResponses(dlg, req, res);
|
|
|
|
} catch (error) {
|
|
logger.error('[WITHINDLG] Failed to forward to user: %s | Call-ID: %s', error.message, req.get('Call-ID'));
|
|
res.send(500, 'Server Error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forward request to destination
|
|
*/
|
|
async forwardToDestination(req, res, destination) {
|
|
try {
|
|
const dlg = await this.srf.createRequest(req.method, destination, {
|
|
headers: {
|
|
'From': req.get('From'),
|
|
'To': req.get('To'),
|
|
'Call-ID': req.get('Call-ID'),
|
|
'CSeq': req.get('CSeq'),
|
|
'Contact': req.get('Contact'),
|
|
'Content-Type': req.get('Content-Type'),
|
|
'Via': req.get('Via'),
|
|
'Route': req.get('Route'),
|
|
'Max-Forwards': req.get('Max-Forwards')
|
|
},
|
|
body: req.body
|
|
});
|
|
|
|
logger.info('[WITHINDLG] Successfully forwarded to destination | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Send response back to originator
|
|
res.send(200, 'OK');
|
|
|
|
// Relay responses
|
|
this.relayResponses(dlg, req, res);
|
|
|
|
} catch (error) {
|
|
logger.error('[WITHINDLG] Failed to forward to destination: %s | Call-ID: %s', error.message, req.get('Call-ID'));
|
|
res.send(500, 'Server Error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Relay responses between dialogs
|
|
*/
|
|
relayResponses(uacDialog, uasReq, uasRes) {
|
|
// Relay UAC responses back to UAS
|
|
uacDialog.on('response', (response) => {
|
|
uasRes.send(response.status, response.reason, response.headers);
|
|
});
|
|
|
|
// Handle UAS responses to UAC
|
|
uasRes.on('response', (response) => {
|
|
uacDialog.send(response.status, response.reason, response.headers);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle BYE request (equivalent to kamailio.cfg:508-511)
|
|
*/
|
|
handleBye(req, res) {
|
|
logger.info('[WITHINDLG] BYE request | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Handle media teardown
|
|
this.handleMediaTeardown(req);
|
|
|
|
// Process routing
|
|
if (this.processLooseRoute(req, res)) {
|
|
return;
|
|
}
|
|
|
|
// If no loose route, try to find the target user
|
|
const aor = `${req.uri.user}@${req.uri.host}`;
|
|
const registration = this.srf.registrationHandler?.lookup(aor);
|
|
|
|
if (registration) {
|
|
logger.info('[WITHINDLG] BYE: Target user found locally, forwarding | Call-ID: %s', req.get('Call-ID'));
|
|
this.forwardToUser(req, res, registration);
|
|
} else {
|
|
logger.warn('[WITHINDLG] BYE: Target user not found locally | Call-ID: %s', req.get('Call-ID'));
|
|
res.send(404, 'Not here');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle ACK request (equivalent to kamailio.cfg:611-621)
|
|
*/
|
|
handleAck(req, res) {
|
|
logger.info('[WITHINDLG] ACK request | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Handle media setup
|
|
this.handleMediaSetup(req);
|
|
|
|
// ACK is typically just absorbed
|
|
res.send(200, 'OK');
|
|
}
|
|
|
|
/**
|
|
* Handle in-dialog SUBSCRIBE (equivalent to kamailio.cfg:606-610)
|
|
*/
|
|
handleSubscribe(req, res) {
|
|
logger.info('[WITHINDLG] SUBSCRIBE request | Call-ID: %s', req.get('Call-ID'));
|
|
res.send(404, 'Not here');
|
|
}
|
|
|
|
/**
|
|
* Main in-dialog request handler (equivalent to kamailio.cfg:489-627)
|
|
*/
|
|
async handleInDialog(req, res) {
|
|
const method = req.method;
|
|
const callId = req.get('Call-ID');
|
|
|
|
logger.info('[WITHINDLG] %s request | Call-ID: %s', method, callId);
|
|
|
|
// Handle media-related operations
|
|
if (method === 'BYE' || method === 'CANCEL') {
|
|
this.handleMediaTeardown(req);
|
|
} else if (method === 'ACK') {
|
|
this.handleMediaSetup(req);
|
|
}
|
|
|
|
// Process based on method
|
|
switch (method) {
|
|
case 'BYE':
|
|
this.handleBye(req, res);
|
|
break;
|
|
|
|
case 'ACK':
|
|
this.handleAck(req, res);
|
|
break;
|
|
|
|
case 'SUBSCRIBE':
|
|
this.handleSubscribe(req, res);
|
|
break;
|
|
|
|
default:
|
|
// For other methods, try to process Route headers
|
|
if (!this.processLooseRoute(req, res)) {
|
|
// No route found, try direct forwarding
|
|
logger.info('[WITHINDLG] No route headers, forwarding to destination | Call-ID: %s', callId);
|
|
const aor = `${req.uri.user}@${req.uri.host}`;
|
|
const registration = this.srf.registrationHandler?.lookup(aor);
|
|
|
|
if (registration) {
|
|
this.forwardToUser(req, res, registration);
|
|
} else {
|
|
logger.warn('[WITHINDLG] No route found for %s | Call-ID: %s', method, callId);
|
|
res.send(404, 'Not here');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = InDialogHandler; |