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;