added initial support for REST-initiated outdials

This commit is contained in:
Dave Horton
2020-02-01 16:16:00 -05:00
parent 44a1b45357
commit 2525b8c70a
28 changed files with 985 additions and 127 deletions

View File

@@ -5,6 +5,7 @@ class CallInfo {
constructor(opts) {
this.direction = opts.direction;
if (this.direction === CallDirection.Inbound) {
// inbound call
const {app, req} = opts;
this.callSid = req.locals.callSid,
this.accountSid = app.account_sid,
@@ -19,19 +20,32 @@ class CallInfo {
this.originatingSipTrunkName = req.get('X-Originating-Carrier');
}
else if (opts.parentCallInfo) {
console.log(`is opts.parentCallInfo a CallInfo ${opts.parentCallInfo instanceof CallInfo}`);
const {req, parentCallInfo} = opts;
this.callSid = uuidv4();
// outbound call that is a child of an existing call
const {req, parentCallInfo, to, callSid} = opts;
this.callSid = callSid || uuidv4();
this.parentCallSid = parentCallInfo.callSid;
this.accountSid = parentCallInfo.accountSid;
this.applicationSid = parentCallInfo.applicationSid;
this.from = req.callingNumber;
this.to = req.calledNumber;
this.to = to || req.calledNumber;
this.callerName = this.from.name || req.callingNumber;
this.callId = req.get('Call-ID');
this.callStatus = CallStatus.Trying,
this.sipStatus = 100;
}
else {
// outbound call triggered by REST
const {req, accountSid, applicationSid, to, tag} = opts;
this.callSid = uuidv4();
this.accountSid = accountSid;
this.applicationSid = applicationSid;
this.callStatus = CallStatus.Trying,
this.callId = req.get('Call-ID');
this.sipStatus = 100;
this.from = req.callingNumber;
this.to = to;
if (tag) this._customerData = tag;
}
}
updateCallStatus(callStatus, sipStatus) {
@@ -43,6 +57,10 @@ class CallInfo {
this._customerData = obj;
}
get customerData() {
return this._customerData;
}
toJSON() {
const obj = {
callSid: this.callSid,
@@ -59,9 +77,10 @@ class CallInfo {
['parentCallSid', 'originatingSipIP', 'originatingSipTrunkName'].forEach((prop) => {
if (this[prop]) obj[prop] = this[prop];
});
if (typeof this.duration === 'number') obj.duration = this.duration;
if (this._customerData && Object.keys(this._customerData).length) {
obj.customerData = this._customerData;
if (this._customerData) {
Object.assign(obj, {customerData: this._customerData});
}
return obj;
}

View File

@@ -39,6 +39,24 @@ class CallSession extends Emitter {
return this.callInfo.direction;
}
get call_status_hook() {
return this.application.call_status_hook;
}
get speechSynthesisVendor() {
return this.application.speech_synthesis_vendor;
}
get speechSynthesisVoice() {
return this.application.speech_synthesis_voice;
}
get speechRecognizerVendor() {
return this.application.speech_recognizer_vendor;
}
get speechRecognizerLanguage() {
return this.application.speech_recognizer_language;
}
async exec() {
this.logger.info(`CallSession:exec starting task list with ${this.tasks.length} tasks`);
while (this.tasks.length && !this.callGone) {
@@ -196,11 +214,16 @@ class CallSession extends Emitter {
}
return {ms: this.ms, ep: this.ep};
}
_notifyCallStatusChange({callStatus, sipStatus}) {
this.logger.debug(`CallSession:_notifyCallStatusChange: ${callStatus} ${sipStatus}`);
_notifyCallStatusChange({callStatus, sipStatus, duration}) {
assert((typeof duration === 'number' && callStatus === CallStatus.Completed) ||
(!duration && callStatus !== CallStatus.Completed),
'duration MUST be supplied when call completed AND ONLY when call completed');
const call_status_hook = this.call_status_hook;
this.callInfo.updateCallStatus(callStatus, sipStatus);
if (typeof duration === 'number') this.callInfo.duration = duration;
try {
this.notifyHook(this.application.call_status_hook);
this.notifyHook(call_status_hook);
} catch (err) {
this.logger.info(err, `CallSession:_notifyCallStatusChange error sending ${callStatus} ${sipStatus}`);
}

View File

@@ -1,18 +1,17 @@
const CallSession = require('./call-session');
const {CallDirection} = require('../utils/constants');
class ConfirmCallSession extends CallSession {
constructor({logger, application, dlg, ep, tasks}) {
constructor({logger, application, dlg, ep, tasks, callInfo}) {
super({
logger,
application,
srf: dlg.srf,
callSid: dlg.callSid,
tasks
tasks,
callInfo
});
this.dlg = dlg;
this.ep = ep;
this.direction = CallDirection.Outbound;
}
/**

View File

@@ -21,20 +21,6 @@ class InboundCallSession extends CallSession {
this._notifyCallStatusChange({callStatus: CallStatus.Trying, sipStatus: 100});
}
get speechSynthesisVendor() {
return this.application.speech_synthesis_vendor;
}
get speechSynthesisVoice() {
return this.application.speech_synthesis_voice;
}
get speechRecognizerVendor() {
return this.application.speech_recognizer_vendor;
}
get speechRecognizerLanguage() {
return this.application.speech_recognizer_language;
}
_onTasksDone() {
if (!this.res.finalResponseSent) {
this.logger.info('InboundCallSession:_onTasksDone auto-generating non-success response to invite');

View File

@@ -0,0 +1,34 @@
const CallSession = require('./call-session');
const {CallStatus} = require('../utils/constants');
const moment = require('moment');
class RestCallSession extends CallSession {
constructor({logger, application, srf, req, ep, tasks, callInfo}) {
super({
logger,
application,
srf,
callSid: callInfo.callSid,
tasks,
callInfo
});
this.req = req;
this.ep = ep;
}
setDialog(dlg) {
this.dlg = dlg;
dlg.on('destroy', this._callerHungup.bind(this));
dlg.connectTime = moment();
}
_callerHungup() {
const duration = moment().diff(this.dlg.connectTime, 'seconds');
this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration});
this.logger.debug('InboundCallSession: caller hung up');
this._callReleased();
}
}
module.exports = RestCallSession;