mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-09 02:30:17 +00:00
* fix: propagate SIP 488 error to SBC on endpoint allocation failure When FreeSWITCH returns a SIP 488 'Not Acceptable Here' error during endpoint allocation (e.g., codec incompatibility), this error was not being propagated back to the SBC/client. Instead, the call would wait indefinitely for websocket commands or return a generic 603 response. Implementation: - In _evalEndpointPrecondition(), detect SipError by checking err.type === 'SipError' or err.name === 'SipError' - Extract the SIP status code (e.g., 488), reason, and the Reason header from the error response (e.g., Q.850;cause=88;text=INCOMPATIBLE_DESTINATION) - Send the SIP error response immediately to the SBC with: - X-Reason header: endpoint allocation failure details - Reason header: original Q.850 cause from FreeSWITCH - Notify call status change as Failed with proper SIP status - Release the call immediately instead of waiting for commands Also added fallback handling in InboundCallSession._onTasksDone() to propagate the stored error if immediate send was not possible. * wip * Simplify SipError check to only use err.name
122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
const CallSession = require('./call-session');
|
|
const {CallStatus} = require('../utils/constants');
|
|
const moment = require('moment');
|
|
const assert = require('assert');
|
|
|
|
/**
|
|
* @classdesc Subclass of CallSession. This represents a CallSession that is
|
|
* established for an inbound call.
|
|
* @extends CallSession
|
|
*/
|
|
class InboundCallSession extends CallSession {
|
|
constructor(req, res) {
|
|
super({
|
|
logger: req.locals.logger,
|
|
srf: req.srf,
|
|
application: req.locals.application,
|
|
callInfo: req.locals.callInfo,
|
|
accountInfo: req.locals.accountInfo,
|
|
tasks: req.locals.application.tasks,
|
|
rootSpan: req.locals.rootSpan
|
|
});
|
|
this.req = req;
|
|
this.res = res;
|
|
|
|
// if the call was canceled before we got here, handle it
|
|
if (this.req.locals.canceled) {
|
|
req.locals.logger.info('InboundCallSession: constructor - call was already canceled');
|
|
this._onCancel();
|
|
}
|
|
|
|
req.once('cancel', this._onCancel.bind(this));
|
|
|
|
this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
|
|
this._notifyCallStatusChange({
|
|
callStatus: CallStatus.Trying,
|
|
sipStatus: 100,
|
|
sipReason: 'Trying'
|
|
});
|
|
}
|
|
|
|
_onCancel() {
|
|
this.rootSpan.setAttributes({'call.termination': 'caller abandoned'});
|
|
this.callInfo.callTerminationBy = 'caller';
|
|
this._notifyCallStatusChange({
|
|
callStatus: CallStatus.NoAnswer,
|
|
sipStatus: 487,
|
|
sipReason: 'Request Terminated'
|
|
});
|
|
this._callReleased();
|
|
}
|
|
|
|
_onTasksDone() {
|
|
if (!this.res.finalResponseSent) {
|
|
if (this._mediaServerFailure) {
|
|
this.rootSpan.setAttributes({'call.termination': 'media server failure'});
|
|
this.logger.info('InboundCallSession:_onTasksDone generating 480 due to media server failure');
|
|
this.res.send(480, {
|
|
headers: {
|
|
'X-Reason': 'crankback: media server failure'
|
|
}
|
|
});
|
|
}
|
|
else if (this._endpointAllocationError) {
|
|
// Propagate SIP error from endpoint allocation failure back to the client
|
|
const {status, reason, sipReasonHeader} = this._endpointAllocationError;
|
|
this.rootSpan.setAttributes({'call.termination': `endpoint allocation SIP error ${status}`});
|
|
this.logger.info({endpointAllocationError: this._endpointAllocationError},
|
|
`InboundCallSession:_onTasksDone generating ${status} due to endpoint allocation failure`);
|
|
this.res.send(status, {
|
|
headers: {
|
|
'X-Reason': `endpoint allocation failure: ${reason}`,
|
|
...(sipReasonHeader && {'Reason': sipReasonHeader})
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
this.rootSpan.setAttributes({'call.termination': 'tasks completed without answering call'});
|
|
this.logger.info('InboundCallSession:_onTasksDone auto-generating non-success response to invite');
|
|
this.res.send(603);
|
|
}
|
|
}
|
|
this.req.removeAllListeners('cancel');
|
|
}
|
|
|
|
/**
|
|
* This is invoked when the caller hangs up, in order to calculate the call duration.
|
|
*/
|
|
_callerHungup() {
|
|
this._hangup('caller');
|
|
}
|
|
|
|
_jambonzHangup(reason) {
|
|
this.dlg?.destroy({
|
|
headers: {
|
|
...(reason && {'X-Reason': reason})
|
|
}
|
|
});
|
|
// kill current task or wakeup the call session.
|
|
this._callReleased();
|
|
}
|
|
|
|
_hangup(terminatedBy = 'jambonz') {
|
|
if (this.dlg === null) {
|
|
this.logger.info('InboundCallSession:_hangup - race condition, dlg cleared by app hangup');
|
|
return;
|
|
}
|
|
this.logger.info(`InboundCallSession: ${terminatedBy} hung up`);
|
|
assert(this.dlg.connectTime);
|
|
const duration = moment().diff(this.dlg.connectTime, 'seconds');
|
|
this.rootSpan.setAttributes({'call.termination': `hangup by ${terminatedBy}`});
|
|
this.callInfo.callTerminationBy = terminatedBy;
|
|
this.emit('callStatusChange', {
|
|
callStatus: CallStatus.Completed,
|
|
duration
|
|
});
|
|
this._callReleased();
|
|
this.req.removeAllListeners('cancel');
|
|
}
|
|
}
|
|
|
|
module.exports = InboundCallSession;
|