From d84690dfb16c08317120770fd33d34c7ee3bf24d Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Thu, 21 May 2026 15:02:28 +0100 Subject: [PATCH] add custom nye headers to completed hook (#1552) --- lib/session/adulting-call-session.js | 10 ++++++---- lib/session/call-info.js | 12 ++++++++++++ lib/session/call-session.js | 15 ++++++++++++++- lib/session/inbound-call-session.js | 10 ++++++---- lib/session/rest-call-session.js | 9 +++++---- lib/utils/place-outdial.js | 20 +++++++++++++++++--- 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/lib/session/adulting-call-session.js b/lib/session/adulting-call-session.js index 198a7ee4..03063d87 100644 --- a/lib/session/adulting-call-session.js +++ b/lib/session/adulting-call-session.js @@ -54,22 +54,24 @@ class AdultingCallSession extends CallSession { return this.callInfo.callSid; } - _callerHungup() { - this._hangup('caller'); + _callerHungup(req) { + this._hangup('caller', req); } _jambonzHangup() { this._hangup(); } - _hangup(terminatedBy = 'jambonz') { + _hangup(terminatedBy = 'jambonz', req) { if (this.dlg.connectTime) { const duration = moment().diff(this.dlg.connectTime, 'seconds'); + const headers = this._extractCustomHeaders(req); this.rootSpan.setAttributes({'call.termination': `hangup by ${terminatedBy}`}); this.callInfo.callTerminationBy = terminatedBy; this.emit('callStatusChange', { callStatus: CallStatus.Completed, - duration + duration, + ...(headers && {headers}) }); } this.logger.info(`InboundCallSession: ${terminatedBy} hung up`); diff --git a/lib/session/call-info.js b/lib/session/call-info.js index ac3b813d..2527dc99 100644 --- a/lib/session/call-info.js +++ b/lib/session/call-info.js @@ -124,6 +124,14 @@ class CallInfo { return this._customerData; } + set sipHeaders(obj) { + this._sipHeaders = obj; + } + + get sipHeaders() { + return this._sipHeaders; + } + toJSON() { const obj = { callSid: this.callSid, @@ -150,6 +158,10 @@ class CallInfo { Object.assign(obj, {customerData: this._customerData}); } + if (this._sipHeaders) { + Object.assign(obj, {headers: this._sipHeaders}); + } + if (JAMBONES_API_BASE_URL) { Object.assign(obj, {apiBaseUrl: JAMBONES_API_BASE_URL}); } diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 099325c5..1469cd84 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -2664,6 +2664,18 @@ Duration=${duration} ` assert(false, 'subclass responsibility to override this method'); } + _extractCustomHeaders(req) { + const dominated = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', + 'content-length', 'contact', 'route', 'record-route', 'content-type', + 'authorization', 'proxy-authorization', 'www-authenticate', 'proxy-authenticate']; + if (!req || !req.headers) return null; + const headers = {}; + Object.keys(req.headers).forEach((h) => { + if (!dominated.includes(h)) headers[h] = req.headers[h]; + }); + return Object.keys(headers).length ? headers : null; + } + /** * called when the jambonz has hung up. Provided for subclasses to override * in order to apply logic at this point if needed. @@ -3053,7 +3065,7 @@ Duration=${duration} ` * @param {number} sipStatus - current sip status * @param {number} [duration] - duration of a completed call, in seconds */ - async _notifyCallStatusChange({callStatus, sipStatus, sipReason, duration}) { + async _notifyCallStatusChange({callStatus, sipStatus, sipReason, duration, headers}) { if (this.callMoved) return; // manage record all call. @@ -3077,6 +3089,7 @@ Duration=${duration} ` this.callInfo.updateCallStatus(callStatus, sipStatus, sipReason); if (typeof duration === 'number') this.callInfo.duration = duration; + if (headers) this.callInfo.sipHeaders = headers; this.executeStatusCallback(callStatus, sipStatus); // update calls db diff --git a/lib/session/inbound-call-session.js b/lib/session/inbound-call-session.js index a905f4bd..4b1bb1b6 100644 --- a/lib/session/inbound-call-session.js +++ b/lib/session/inbound-call-session.js @@ -85,8 +85,8 @@ class InboundCallSession extends CallSession { /** * This is invoked when the caller hangs up, in order to calculate the call duration. */ - _callerHungup() { - this._hangup('caller'); + _callerHungup(req) { + this._hangup('caller', req); } _jambonzHangup(reason) { @@ -99,7 +99,7 @@ class InboundCallSession extends CallSession { this._callReleased(); } - _hangup(terminatedBy = 'jambonz') { + _hangup(terminatedBy = 'jambonz', req) { if (this.dlg === null) { this.logger.info('InboundCallSession:_hangup - race condition, dlg cleared by app hangup'); return; @@ -107,11 +107,13 @@ class InboundCallSession extends CallSession { this.logger.info(`InboundCallSession: ${terminatedBy} hung up`); assert(this.dlg.connectTime); const duration = moment().diff(this.dlg.connectTime, 'seconds'); + const headers = this._extractCustomHeaders(req); this.rootSpan.setAttributes({'call.termination': `hangup by ${terminatedBy}`}); this.callInfo.callTerminationBy = terminatedBy; this.emit('callStatusChange', { callStatus: CallStatus.Completed, - duration + duration, + ...(headers && {headers}) }); this._callReleased(); this.req.removeAllListeners('cancel'); diff --git a/lib/session/rest-call-session.js b/lib/session/rest-call-session.js index 1bafac4c..ba131be2 100644 --- a/lib/session/rest-call-session.js +++ b/lib/session/rest-call-session.js @@ -51,21 +51,22 @@ class RestCallSession extends CallSession { /** * This is invoked when the called party hangs up, in order to calculate the call duration. */ - _callerHungup() { - this._hangup('caller'); + _callerHungup(req) { + this._hangup('caller', req); } _jambonzHangup() { this._hangup(); } - _hangup(terminatedBy = 'jambonz') { + _hangup(terminatedBy = 'jambonz', req) { if (this.restDialTask) { this.restDialTask.turnOffAmd(); } this.callInfo.callTerminationBy = terminatedBy; const duration = moment().diff(this.dlg.connectTime, 'seconds'); - this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration}); + const headers = this._extractCustomHeaders(req); + this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration, ...(headers && {headers})}); this.logger.info(`RestCallSession: called party hung up by ${terminatedBy}`); this._callReleased(); } diff --git a/lib/utils/place-outdial.js b/lib/utils/place-outdial.js index 585250c4..fbfe9a2e 100644 --- a/lib/utils/place-outdial.js +++ b/lib/utils/place-outdial.js @@ -265,10 +265,11 @@ class SingleDialer extends Emitter { } this.dlg - .on('destroy', () => { + .on('destroy', (req) => { const duration = moment().diff(connectTime, 'seconds'); + const headers = this._extractCustomHeaders(req); this.logger.debug('SingleDialer:exec called party hung up'); - this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration}); + this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration, ...(headers && {headers})}); this.ep && this.ep.destroy(); }) .on('refresh', () => this.logger.info('SingleDialer:exec - dialog refreshed by uas')) @@ -530,7 +531,19 @@ class SingleDialer extends Emitter { } } - _notifyCallStatusChange({callStatus, sipStatus, sipReason, duration}) { + _extractCustomHeaders(req) { + const dominated = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', + 'content-length', 'contact', 'route', 'record-route', 'content-type', + 'authorization', 'proxy-authorization', 'www-authenticate', 'proxy-authenticate']; + if (!req || !req.headers) return null; + const headers = {}; + Object.keys(req.headers).forEach((h) => { + if (!dominated.includes(h)) headers[h] = req.headers[h]; + }); + return Object.keys(headers).length ? headers : null; + } + + _notifyCallStatusChange({callStatus, sipStatus, sipReason, duration, headers}) { assert((typeof duration === 'number' && callStatus === CallStatus.Completed) || (!duration && callStatus !== CallStatus.Completed), 'duration MUST be supplied when call completed AND ONLY when call completed'); @@ -538,6 +551,7 @@ class SingleDialer extends Emitter { if (this.callInfo) { this.callInfo.updateCallStatus(callStatus, sipStatus, sipReason); if (typeof duration === 'number') this.callInfo.duration = duration; + if (headers) this.callInfo.sipHeaders = headers; try { this.notifier.request('call:status', this.application.call_status_hook, this.callInfo.toJSON()); } catch (err) {