Feature/incoming refer (#76)

* Dial: handle incoming REFER on either leg by calling referHook, if configured

* lint

* modify payload of referHook

* support target.trunk on rest createCall api

* bugfix: gather partial result hook was not working

* lint

* handling of incoming REFER
This commit is contained in:
Dave Horton
2022-03-05 15:21:26 -05:00
committed by GitHub
parent 9908485eb8
commit 72b74de767
14 changed files with 213 additions and 27 deletions

View File

@@ -30,15 +30,25 @@ class AdultingCallSession extends CallSession {
return this.sd.dlg;
}
/**
* Note: this is not an error. It is only here to avoid an assert ("no setter for dlg")
* when there is a call in Session:_clearResources to null out dlg and ep
*/
set dlg(newDlg) {}
get ep() {
return this.sd.ep;
}
/* see note above */
set ep(newEp) {}
get callSid() {
return this.callInfo.callSid;
}
_callerHungup() {
}
}
module.exports = AdultingCallSession;

View File

@@ -27,6 +27,7 @@ class CallInfo {
this.to = req.calledNumber;
this.callId = req.get('Call-ID');
this.sipStatus = 100;
this.sipReason = 'Trying';
this.callStatus = CallStatus.Trying;
this.originatingSipIp = req.get('X-Forwarded-For');
this.originatingSipTrunkName = req.get('X-Originating-Carrier');
@@ -45,6 +46,7 @@ class CallInfo {
this.callId = req.get('Call-ID');
this.callStatus = CallStatus.Trying,
this.sipStatus = 100;
this.sipReason = 'Trying';
}
else if (this.direction === CallDirection.None) {
// outbound SMS
@@ -65,6 +67,7 @@ class CallInfo {
this.callStatus = CallStatus.Trying,
this.callId = req.get('Call-ID');
this.sipStatus = 100;
this.sipReason = 'Trying';
this.from = from || req.callingNumber;
this.to = to;
if (tag) this._customerData = tag;
@@ -81,9 +84,10 @@ class CallInfo {
* @param {string} callStatus - current call status
* @param {number} sipStatus - current sip status
*/
updateCallStatus(callStatus, sipStatus) {
updateCallStatus(callStatus, sipStatus, sipReason) {
this.callStatus = callStatus;
if (sipStatus) this.sipStatus = sipStatus;
if (sipReason) this.sipReason = sipReason;
}
/**
@@ -106,6 +110,7 @@ class CallInfo {
to: this.to,
callId: this.callId,
sipStatus: this.sipStatus,
sipReason: this.sipReason,
callStatus: this.callStatus,
callerId: this.callerId,
accountSid: this.accountSid,

View File

@@ -521,6 +521,29 @@ class CallSession extends Emitter {
task.doConferenceMuteNonModerators(this, opts);
}
async _lccSipRequest(callSid, opts) {
const {sip_request} = opts;
const {method, content_type, content, headers = {}} = sip_request;
if (!this.hasStableDialog) {
this.logger.info('CallSession:_lccSipRequest - invalid command as we do not have a stable call');
return;
}
try {
const res = await this.dlg.request({
method,
headers: {
...headers,
'Content-Type': content_type,
'Content': content
}
});
this.logger.debug({res}, `CallSession:_lccSipRequest got response to ${method}`);
return res;
} catch (err) {
this.logger.error({err}, `CallSession:_lccSipRequest - error sending ${method}`);
}
}
/**
* perform live call control -- whisper to one party or the other on a call
* @param {array} opts - array of play or say tasks
@@ -596,6 +619,10 @@ class CallSession extends Emitter {
else if (opts.conf_mute_status) {
await this._lccConfMuteStatus(callSid, opts);
}
else if (opts.sip_request) {
const res = await this._lccSipRequest(callSid, opts);
return {status: res.status, reason: res.reason};
}
// whisper may be the only thing we are asked to do, or it may that
// we are doing a whisper after having muted, paused reccording etc..
@@ -752,7 +779,11 @@ class CallSession extends Emitter {
} catch (err) {
if (err === CALLER_CANCELLED_ERR_MSG) {
this.logger.error(err, 'caller canceled quickly before we could respond, ending call');
this._notifyCallStatusChange({callStatus: CallStatus.NoAnswer, sipStatus: 487});
this._notifyCallStatusChange({
callStatus: CallStatus.NoAnswer,
sipStatus: 487,
sipReason: 'Request Terminated'
});
this._callReleased();
}
else {
@@ -862,9 +893,10 @@ class CallSession extends Emitter {
this.dlg.on('destroy', this._callerHungup.bind(this));
this.wrapDialog(this.dlg);
this.dlg.callSid = this.callSid;
this.emit('callStatusChange', {sipStatus: 200, callStatus: CallStatus.InProgress});
this.emit('callStatusChange', {sipStatus: 200, sipReason: 'OK', callStatus: CallStatus.InProgress});
this.dlg.on('modify', this._onReinvite.bind(this));
this.dlg.on('refer', this._onRefer.bind(this));
this.logger.debug(`CallSession:propagateAnswer - answered callSid ${this.callSid}`);
}
@@ -890,6 +922,22 @@ class CallSession extends Emitter {
}
}
/**
* Handle incoming REFER if we are in a dial task
* @param {*} req
* @param {*} res
*/
_onRefer(req, res) {
const task = this.currentTask;
const sd = task.sd;
if (task && TaskName.Dial === task.name && sd) {
task.handleRefer(this, req, res);
}
else {
res.send(501);
}
}
/**
* create and endpoint if we don't have one; otherwise simply return
* the current media server and endpoint that are associated with this call
@@ -1075,7 +1123,7 @@ class CallSession extends Emitter {
* @param {number} sipStatus - current sip status
* @param {number} [duration] - duration of a completed call, in seconds
*/
_notifyCallStatusChange({callStatus, sipStatus, duration}) {
_notifyCallStatusChange({callStatus, sipStatus, sipReason, duration}) {
if (this.callMoved) return;
/* race condition: we hang up at the same time as the caller */
@@ -1088,7 +1136,7 @@ class CallSession extends Emitter {
(!duration && callStatus !== CallStatus.Completed),
'duration MUST be supplied when call completed AND ONLY when call completed');
this.callInfo.updateCallStatus(callStatus, sipStatus);
this.callInfo.updateCallStatus(callStatus, sipStatus, sipReason);
if (typeof duration === 'number') this.callInfo.duration = duration;
try {
this.notifier.request('call:status', this.call_status_hook, this.callInfo.toJSON());

View File

@@ -30,6 +30,10 @@ class ConfirmCallSession extends CallSession {
_clearResources() {
}
_callerHungup() {
}
}
module.exports = ConfirmCallSession;

View File

@@ -24,11 +24,19 @@ class InboundCallSession extends CallSession {
req.once('cancel', this._onCancel.bind(this));
this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
this._notifyCallStatusChange({callStatus: CallStatus.Trying, sipStatus: 100});
this._notifyCallStatusChange({
callStatus: CallStatus.Trying,
sipStatus: 100,
sipReason: 'Trying'
});
}
_onCancel() {
this._notifyCallStatusChange({callStatus: CallStatus.NoAnswer, sipStatus: 487});
this._notifyCallStatusChange({
callStatus: CallStatus.NoAnswer,
sipStatus: 487,
sipReason: 'Request Terminated'
});
this._callReleased();
}
@@ -56,7 +64,10 @@ class InboundCallSession extends CallSession {
_callerHungup() {
assert(this.dlg.connectTime);
const duration = moment().diff(this.dlg.connectTime, 'seconds');
this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration});
this.emit('callStatusChange', {
callStatus: CallStatus.Completed,
duration
});
this.logger.debug('InboundCallSession: caller hung up');
this._callReleased();
this.req.removeAllListeners('cancel');

View File

@@ -22,7 +22,11 @@ class RestCallSession extends CallSession {
this.ep = ep;
this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
this._notifyCallStatusChange({callStatus: CallStatus.Trying, sipStatus: 100});
this._notifyCallStatusChange({
callStatus: CallStatus.Trying,
sipStatus: 100,
sipReason: 'Trying'
});
}
/**