major revamp of http client functionalit

This commit is contained in:
Dave Horton
2020-02-14 12:45:28 -05:00
parent ff531e6964
commit 446000ee97
35 changed files with 906 additions and 433 deletions

View File

@@ -3,9 +3,37 @@ const makeTask = require('./make_task');
const {CallStatus, CallDirection, TaskName, TaskPreconditions, MAX_SIMRINGS} = require('../utils/constants');
const assert = require('assert');
const placeCall = require('../utils/place-outdial');
const sessionTracker = require('../session/session-tracker');
const DtmfCollector = require('../utils/dtmf-collector');
const config = require('config');
const debug = require('debug')('jambonz:feature-server');
function parseDtmfOptions(logger, dtmfCapture) {
let parentDtmfCollector, childDtmfCollector;
const parentKeys = [], childKeys = [];
if (Array.isArray(dtmfCapture)) {
Array.prototype.push.apply(parentKeys, dtmfCapture);
Array.prototype.push.apply(childKeys, dtmfCapture);
}
else if (dtmfCapture.childCall || dtmfCapture.parentCall) {
if (dtmfCapture.childCall && Array.isArray(dtmfCapture.childCall)) {
Array.prototype.push.apply(childKeys, dtmfCapture.childCall);
}
if (dtmfCapture.parentCall && Array.isArray(dtmfCapture.parentCall)) {
Array.prototype.push.apply(childKeys, dtmfCapture.parentCall);
}
}
if (childKeys.length) {
childDtmfCollector = new DtmfCollector({logger, patterns: childKeys});
}
if (parentKeys.length) {
parentDtmfCollector = new DtmfCollector({logger, patterns: parentKeys});
}
return {childDtmfCollector, parentDtmfCollector};
}
function compareTasks(t1, t2) {
if (t1.type !== t2.type) return false;
switch (t1.type) {
@@ -44,6 +72,7 @@ class TaskDial extends Task {
super(logger, opts);
this.preconditions = TaskPreconditions.None;
this.actionHook = this.data.actionHook;
this.earlyMedia = this.data.answerOnBridge === true;
this.callerId = this.data.callerId;
this.dialMusic = this.data.dialMusic;
@@ -52,8 +81,19 @@ class TaskDial extends Task {
this.target = filterAndLimit(this.logger, this.data.target);
this.timeout = this.data.timeout || 60;
this.timeLimit = this.data.timeLimit;
this.confirmUrl = this.data.confirmUrl;
this.confirmHook = this.data.confirmHook;
this.confirmMethod = this.data.confirmMethod;
this.dtmfHook = this.data.dtmfHook;
if (this.dtmfHook) {
const {parentDtmfCollector, childDtmfCollector} = parseDtmfOptions(logger, this.data.dtmfCapture || {});
if (parentDtmfCollector) {
this.parentDtmfCollector = parentDtmfCollector;
}
if (childDtmfCollector) {
this.childDtmfCollector = childDtmfCollector;
}
}
if (this.data.listen) {
this.listenTask = makeTask(logger, {'listen': this.data.listen}, this);
@@ -83,9 +123,15 @@ class TaskDial extends Task {
if (cs.direction === CallDirection.Inbound) {
await this._initializeInbound(cs);
}
else {
this.epOther = cs.ep;
}
this._installDtmfDetection(cs, this.epOther, this.parentDtmfCollector);
await this._attemptCalls(cs);
await this.awaitTaskDone();
await this.performAction(this.method, null, this.results);
await cs.requestor.request(this.actionHook, Object.assign({}, cs.callInfo, this.results));
this._removeDtmfDetection(cs, this.epOther);
this._removeDtmfDetection(cs, this.ep);
} catch (err) {
this.logger.error(`TaskDial:exec terminating with error ${err.message}`);
this.kill();
@@ -94,17 +140,57 @@ class TaskDial extends Task {
async kill() {
super.kill();
this._removeDtmfDetection(this.cs, this.epOther);
this._removeDtmfDetection(this.cs, this.ep);
this._killOutdials();
if (this.sd) {
this.sd.kill();
this.sd = null;
}
sessionTracker.remove(this.callSid);
if (this.listenTask) await this.listenTask.kill();
if (this.transcribeTask) await this.transcribeTask.kill();
if (this.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
this.notifyTaskDone();
}
/**
* whisper a prompt to one side of the call
* @param {*} tasks - array of play/say tasks to execute
*/
async whisper(tasks, callSid) {
if (!this.epOther || !this.ep) return this.logger.info('Dial:whisper: no paired endpoint found');
try {
const cs = this.callSession;
this.logger.debug('Dial:whisper unbridging endpoints');
await this.epOther.unbridge();
this.logger.debug('Dial:whisper executing tasks');
while (tasks.length && !cs.callGone) {
const task = tasks.shift();
await task.exec(cs, callSid === this.callSid ? this.ep : this.epOther);
}
this.logger.debug('Dial:whisper tasks complete');
if (!cs.callGone) this.epOther.bridge(this.ep);
} catch (err) {
this.logger.error(err, 'Dial:whisper error');
}
}
/**
* mute or unmute one side of the call
*/
async mute(callSid, doMute) {
if (!this.epOther || !this.ep) return this.logger.info('Dial:mute: no paired endpoint found');
try {
const parentCall = callSid !== this.callSid;
const ep = parentCall ? this.epOther : this.ep;
await ep[doMute ? 'mute' : 'unmute']();
this.logger.debug(`Dial:mute ${doMute ? 'muted' : 'unmuted'} ${parentCall ? 'parentCall' : 'childCall'}`);
} catch (err) {
this.logger.error(err, 'Dial:mute error');
}
}
_killOutdials() {
for (const [callSid, sd] of Array.from(this.dials)) {
this.logger.debug(`Dial:_killOutdials killing callSid ${callSid}`);
@@ -113,8 +199,33 @@ class TaskDial extends Task {
this.dials.clear();
}
_installDtmfDetection(cs, ep, dtmfDetector) {
if (ep && this.dtmfHook && !ep.dtmfDetector) {
ep.dtmfDetector = dtmfDetector;
ep.on('dtmf', this._onDtmf.bind(this, cs, ep));
}
}
_removeDtmfDetection(cs, ep) {
if (ep) {
delete ep.dtmfDetector;
ep.removeListener('dtmf', this._onDtmf.bind(this, cs, ep));
}
}
_onDtmf(cs, ep, evt) {
const match = ep.dtmfDetector.keyPress(evt.dtmf);
const requestor = ep.dtmfDetector === this.parentDtmfCollector ?
cs.requestor :
this.sd.requestor;
if (match) {
this.logger.debug(`parentCall triggered dtmf match: ${match}`);
requestor.request(this.dtmfHook, Object.assign({dtmf: match}, cs.callInfo))
.catch((err) => this.logger.info(err, 'Dial:_onDtmf - error'));
}
}
async _initializeInbound(cs) {
const {ep} = await cs.connectInboundCallToIvr(this.earlyMedia);
const ep = await cs._evalEndpointPrecondition(this);
this.epOther = ep;
debug(`Dial:__initializeInbound allocated ep for incoming call: ${ep.uuid}`);
@@ -256,8 +367,10 @@ class TaskDial extends Task {
this.kill();
}, this.timeLimit * 1000);
}
sessionTracker.add(this.callSid, cs);
this.dlg.on('destroy', () => {
this.logger.debug('Dial:_selectSingleDial called party hungup, ending dial operation');
sessionTracker.remove(this.callSid);
if (this.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
this.ep.unbridge();
this.kill();
@@ -268,6 +381,8 @@ class TaskDial extends Task {
dialCallSid: sd.callSid,
});
if (this.childDtmfCollector) this._installDtmfDetection(cs, this.ep, this.childDtmfCollector);
if (this.transcribeTask) this.transcribeTask.exec(cs, this.ep);
if (this.listenTask) this.listenTask.exec(cs, this.ep);
}