add support for live call control

This commit is contained in:
Dave Horton
2020-02-07 10:26:35 -05:00
parent 2811e35c6b
commit 3ca2d982cc
18 changed files with 332 additions and 73 deletions

View File

@@ -56,10 +56,10 @@ class TaskDial extends Task {
this.confirmMethod = this.data.confirmMethod;
if (this.data.listen) {
this.listenTask = makeTask(logger, {'listen': this.data.listen});
this.listenTask = makeTask(logger, {'listen': this.data.listen}, this);
}
if (this.data.transcribe) {
this.transcribeTask = makeTask(logger, {'transcribe' : this.data.transcribe});
this.transcribeTask = makeTask(logger, {'transcribe' : this.data.transcribe}, this);
}
this.results = {};
@@ -131,7 +131,7 @@ class TaskDial extends Task {
const sbcAddress = cs.direction === CallDirection.Inbound ?
`${req.source_address}:${req.source_port}` :
config.get('sbcAddress');
config.get('outdials.sbc');
const opts = {
headers: req && req.has('X-CID') ? Object.assign(this.headers, {'X-CID': req.get('X-CID')}) : this.headers,
proxy: `sip:${sbcAddress}`,
@@ -268,8 +268,8 @@ class TaskDial extends Task {
dialCallSid: sd.callSid,
});
if (this.transcribeTask) this.transcribeTask.exec(cs, this.ep, this);
if (this.listenTask) this.listenTask.exec(cs, this.ep, this);
if (this.transcribeTask) this.transcribeTask.exec(cs, this.ep);
if (this.listenTask) this.listenTask.exec(cs, this.ep);
}
_bridgeEarlyMedia(sd) {

View File

@@ -16,10 +16,11 @@ class TaskListen extends Task {
this.mixType = this.mixType || 'mono';
this.sampleRate = this.sampleRate || 8000;
this.earlyMedia = this.data.earlyMedia === true;
this.hook = this.normalizeUrl(this.url, 'GET', this.wsAuth);
this.nested = typeof parentTask !== 'undefined';
this.parentTask = parentTask;
this.nested = parentTask instanceof Task;
this.results = {};
this.ranToCompletion = false;
if (this.transcribe) this.transcribeTask = makeTask(logger, {'transcribe': opts.transcribe}, this);
@@ -31,15 +32,18 @@ class TaskListen extends Task {
async exec(cs, ep) {
super.exec(cs);
this.ep = ep;
try {
this.hook = this.normalizeUrl(this.url, 'GET', this.wsAuth);
if (this.playBeep) await this._playBeep(ep);
if (this.transcribeTask) {
this.logger.debug('TaskListen:exec - starting nested transcribe task');
this.transcribeTask.exec(cs, ep, this);
this.transcribeTask.exec(cs, ep);
}
await this._startListening(cs, ep);
await this.awaitTaskDone();
if (this.action) await this.performAction(this.method, this.auth, this.results, !this.nested);
const acceptNewApp = !this.nested && this.ranToCompletion;
if (this.action) await this.performAction(this.method, this.auth, this.results, acceptNewApp);
} catch (err) {
this.logger.info(err, `TaskListen:exec - error ${this.url}`);
}
@@ -73,9 +77,8 @@ class TaskListen extends Task {
this._initListeners(ep);
const metadata = Object.assign(
{sampleRate: this.sampleRate, mixType: this.mixType},
cs.callInfo.toJSON(),
this.nested ? this.parentTask.sd.callInfo : cs.callInfo.toJSON(),
this.metadata);
this.logger.debug({metadata, hook: this.hook}, 'TaskListen:_startListening');
if (this.hook.username && this.hook.password) {
this.logger.debug({username: this.hook.username, password: this.hook.password},
'TaskListen:_startListening basic auth');
@@ -94,6 +97,7 @@ class TaskListen extends Task {
if (this.maxLength) {
this._timer = setTimeout(() => {
this.logger.debug(`TaskListen terminating task due to timeout of ${this.timeout}s reached`);
this.ranToCompletion = true;
this.kill();
}, this.maxLength * 1000);
}
@@ -121,6 +125,7 @@ class TaskListen extends Task {
if (evt.dtmf === this.finishOnKey) {
this.logger.info(`TaskListen:_onDtmf terminating task due to dtmf ${evt.dtmf}`);
this.results.digits = evt.dtmf;
this.ranToCompletion = true;
this.kill();
}
}

View File

@@ -2,7 +2,7 @@ const Task = require('./task');
const {TaskName} = require('../utils/constants');
const errBadInstruction = new Error('malformed jambonz application payload');
function makeTask(logger, obj) {
function makeTask(logger, obj, parent) {
const keys = Object.keys(obj);
if (!keys || keys.length !== 1) {
throw errBadInstruction;
@@ -17,40 +17,40 @@ function makeTask(logger, obj) {
switch (name) {
case TaskName.SipDecline:
const TaskSipDecline = require('./sip_decline');
return new TaskSipDecline(logger, data);
return new TaskSipDecline(logger, data, parent);
case TaskName.Dial:
const TaskDial = require('./dial');
return new TaskDial(logger, data);
return new TaskDial(logger, data, parent);
case TaskName.Hangup:
const TaskHangup = require('./hangup');
return new TaskHangup(logger, data);
return new TaskHangup(logger, data, parent);
case TaskName.Say:
const TaskSay = require('./say');
return new TaskSay(logger, data);
return new TaskSay(logger, data, parent);
case TaskName.Play:
const TaskPlay = require('./play');
return new TaskPlay(logger, data);
return new TaskPlay(logger, data, parent);
case TaskName.Pause:
const TaskPause = require('./pause');
return new TaskPause(logger, data);
return new TaskPause(logger, data, parent);
case TaskName.Gather:
const TaskGather = require('./gather');
return new TaskGather(logger, data);
return new TaskGather(logger, data, parent);
case TaskName.Transcribe:
const TaskTranscribe = require('./transcribe');
return new TaskTranscribe(logger, data);
return new TaskTranscribe(logger, data, parent);
case TaskName.Listen:
const TaskListen = require('./listen');
return new TaskListen(logger, data);
return new TaskListen(logger, data, parent);
case TaskName.Redirect:
const TaskRedirect = require('./redirect');
return new TaskRedirect(logger, data);
return new TaskRedirect(logger, data, parent);
case TaskName.RestDial:
const TaskRestDial = require('./rest_dial');
return new TaskRestDial(logger, data);
return new TaskRestDial(logger, data, parent);
case TaskName.Tag:
const TaskTag = require('./tag');
return new TaskTag(logger, data);
return new TaskTag(logger, data, parent);
}
// should never reach

View File

@@ -57,26 +57,14 @@ class Task extends Emitter {
}
normalizeUrl(url, method, auth) {
const hook = {url, method};
if (auth && auth.username && auth.password) Object.assign(hook, auth);
if (url.startsWith('/')) {
const or = this.callSession.originalRequest;
if (or) {
hook.url = `${or.baseUrl}${url}`;
hook.method = hook.method || or.method || 'POST';
if (!hook.auth && or.auth) Object.assign(hook, or.auth);
}
}
this.logger.debug({hook}, 'Task:normalizeUrl');
return hook;
return this.callSession.normalizeUrl(url, method, auth);
}
async performAction(method, auth, results, expectResponse = true) {
if (this.action) {
const hook = this.normalizeUrl(this.action, method, auth);
const tasks = await this.actionHook(hook, results, expectResponse);
if (tasks && Array.isArray(tasks)) {
if (expectResponse && tasks && Array.isArray(tasks)) {
this.logger.debug({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
this.callSession.replaceApplication(tasks);
}