188 lines
5.8 KiB
JavaScript
188 lines
5.8 KiB
JavaScript
const logger = require('../utils/logger');
|
|
const config = require('config');
|
|
|
|
/**
|
|
* INVITE Handler - Equivalent to Kamailio's INCOMING_INVITE route
|
|
* Handles SIP INVITE requests and manages call routing
|
|
*/
|
|
class InviteHandler {
|
|
constructor(srf) {
|
|
this.srf = srf;
|
|
this.specialNumbers = config.get('security.specialNumbers');
|
|
this.asteriskServers = config.get('dispatcher.asteriskServers');
|
|
this.currentServerIndex = 0; // For round-robin dispatching
|
|
}
|
|
|
|
/**
|
|
* Record routing for dialog-forming requests (equivalent to kamailio.cfg:766-767)
|
|
*/
|
|
recordRoute(req) {
|
|
// Add Record-Route header
|
|
const recordRoute = `Record-Route: <sip:${req.source_address}:${req.source_port};lr>`;
|
|
req.set('Record-Route', recordRoute);
|
|
}
|
|
|
|
/**
|
|
* Enable RTP proxy for media relay (equivalent to kamailio.cfg:774)
|
|
*/
|
|
enableRtpProxy(req, res) {
|
|
if (config.get('rtpProxy.enabled')) {
|
|
logger.info('[INVITE] Enabling RTPProxy media relay | Call-ID: %s', req.get('Call-ID'));
|
|
// RTP proxy integration will be handled in the SDP processing
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if it's a special number (equivalent to kamailio.cfg:778-782)
|
|
*/
|
|
isSpecialNumber(number) {
|
|
return this.specialNumbers.includes(number);
|
|
}
|
|
|
|
/**
|
|
* Route to Asterisk servers (equivalent to kamailio.cfg:696-720)
|
|
*/
|
|
async routeToAsterisk(req, res) {
|
|
logger.info('[INVITE] Routing to Asterisk servers | Call-ID: %s', req.get('Call-ID'));
|
|
|
|
// Select Asterisk server using round-robin
|
|
const server = this.asteriskServers[this.currentServerIndex];
|
|
this.currentServerIndex = (this.currentServerIndex + 1) % this.asteriskServers.length;
|
|
|
|
if (!server) {
|
|
logger.error('[INVITE] No available Asterisk servers | Call-ID: %s', req.get('Call-ID'));
|
|
res.send(404, 'No destination');
|
|
return;
|
|
}
|
|
|
|
logger.info('[INVITE] Selected destination: %s:%s | Call-ID: %s', server.host, server.port, req.get('Call-ID'));
|
|
|
|
try {
|
|
// Create new INVITE to Asterisk
|
|
const targetUri = `sip:${req.uri.user}@${server.host}:${server.port}`;
|
|
|
|
const dlg = await this.srf.createUacInvite(targetUri, {
|
|
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('[INVITE] Successfully routed to Asterisk: %s | Call-ID: %s', targetUri, req.get('Call-ID'));
|
|
|
|
// Send 100 Trying response
|
|
res.send(100, 'Trying');
|
|
|
|
// Relay responses between endpoints
|
|
this.relayResponses(dlg, req, res);
|
|
|
|
} catch (error) {
|
|
logger.error('[INVITE] Failed to route to Asterisk: %s | Call-ID: %s', error.message, req.get('Call-ID'));
|
|
res.send(500, 'Server Error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Route to local user (equivalent to kamailio.cfg:786-791)
|
|
*/
|
|
async routeToLocalUser(req, res, registration) {
|
|
logger.info('[INVITE] User %s found locally - routing directly | Call-ID: %s',
|
|
req.uri.user, req.get('Call-ID'));
|
|
|
|
try {
|
|
// Create INVITE to local user's contact
|
|
const targetUri = registration.contact;
|
|
|
|
const dlg = await this.srf.createUacInvite(targetUri, {
|
|
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('[INVITE] Successfully routed to local user: %s | Call-ID: %s', targetUri, req.get('Call-ID'));
|
|
|
|
// Send 100 Trying response
|
|
res.send(100, 'Trying');
|
|
|
|
// Relay responses between endpoints
|
|
this.relayResponses(dlg, req, res);
|
|
|
|
} catch (error) {
|
|
logger.error('[INVITE] Failed to route to local user: %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 INVITE request (equivalent to kamailio.cfg:762-856)
|
|
*/
|
|
async handleInvite(req, res) {
|
|
const callId = req.get('Call-ID');
|
|
const fromUser = req.getParsedHeader('from').uri.user;
|
|
const toUser = req.uri.user;
|
|
|
|
logger.info('[INVITE] Call setup: %s -> %s | Call-ID: %s', fromUser, toUser, callId);
|
|
|
|
// Record routing
|
|
this.recordRoute(req);
|
|
|
|
// Enable RTP proxy
|
|
this.enableRtpProxy(req, res);
|
|
|
|
// Step 1: Check if it's a special number
|
|
if (this.isSpecialNumber(toUser)) {
|
|
logger.info('[INVITE] Special service call to %s, routing to Asterisk | Call-ID: %s', toUser, callId);
|
|
await this.routeToAsterisk(req, res);
|
|
return;
|
|
}
|
|
|
|
// Step 2: Check if user is registered locally
|
|
const aor = `${toUser}@${req.uri.host}`;
|
|
const registration = this.srf.registrationHandler?.lookup(aor);
|
|
|
|
if (registration) {
|
|
await this.routeToLocalUser(req, res, registration);
|
|
return;
|
|
}
|
|
|
|
// Step 3: No route found - send error
|
|
logger.info('[INVITE] User %s not found locally, no route available | Call-ID: %s', toUser, callId);
|
|
res.send(480, 'Temporarily Unavailable - User not found');
|
|
}
|
|
}
|
|
|
|
module.exports = InviteHandler; |