mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-19 04:17:44 +00:00
more testing
This commit is contained in:
@@ -22,7 +22,7 @@ class CallSession extends Emitter {
|
||||
this.logger.info(`CallSession:exec starting task list with ${this.tasks.length} tasks`);
|
||||
while (this.tasks.length && !this.callGone) {
|
||||
const task = this.tasks.shift();
|
||||
this.logger.debug(`CallSession:exec starting task #${++idx}: ${task.name}`);
|
||||
this.logger.debug({task}, `CallSession:exec starting task #${++idx}: ${task.name}`);
|
||||
try {
|
||||
const resources = await this._evaluatePreconditions(task);
|
||||
this.currentTask = task;
|
||||
@@ -64,7 +64,7 @@ class CallSession extends Emitter {
|
||||
*/
|
||||
replaceApplication(tasks) {
|
||||
this.tasks = tasks;
|
||||
this.logger.info(`CallSession:replaceApplication - set ${tasks.length} new tasks`);
|
||||
this.logger.info({tasks}, `CallSession:replaceApplication - set ${tasks.length} new tasks`);
|
||||
}
|
||||
_evaluatePreconditions(task) {
|
||||
switch (task.preconditions) {
|
||||
|
||||
@@ -15,7 +15,7 @@ function compareTasks(t1, t2) {
|
||||
case 'user':
|
||||
return t2.name === t1.name;
|
||||
case 'sip':
|
||||
return t2.uri === t1.uri;
|
||||
return t2.sipUri === t1.sipUri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,11 @@ class TaskDial extends Task {
|
||||
this.dialMusic = this.data.dialMusic;
|
||||
this.headers = this.data.headers || {};
|
||||
this.method = this.data.method || 'POST';
|
||||
this.statusCallback = this.data.statusCallback;
|
||||
this.statusCallbackMethod = this.data.statusCallbackMethod || 'POST';
|
||||
this.target = filterAndLimit(this.logger, this.data.target);
|
||||
this.timeout = this.data.timeout || 60;
|
||||
this.timeLimit = this.data.timeLimit;
|
||||
this.url = this.data.url;
|
||||
this.confirmUrl = this.data.confirmUrl;
|
||||
this.confirmMethod = this.data.confirmMethod;
|
||||
|
||||
if (this.data.listen) {
|
||||
this.listenTask = makeTask(logger, {'listen': this.data.listen});
|
||||
@@ -79,7 +78,7 @@ class TaskDial extends Task {
|
||||
}
|
||||
await this._attemptCalls(cs);
|
||||
await this.awaitTaskDone();
|
||||
this.performAction(this.method, null, this.results);
|
||||
await this.performAction(this.method, null, this.results);
|
||||
} catch (err) {
|
||||
this.logger.error(`TaskDial:exec terminating with error ${err.message}`);
|
||||
this.kill();
|
||||
@@ -88,21 +87,21 @@ class TaskDial extends Task {
|
||||
|
||||
async kill() {
|
||||
super.kill();
|
||||
if (this.connectTime) {
|
||||
const duration = moment().diff(this.connectTime, 'seconds');
|
||||
if (this.dlg) {
|
||||
const duration = moment().diff(this.dlg.connectTime, 'seconds');
|
||||
this.results.dialCallDuration = duration;
|
||||
this.logger.debug(`Dial:kill call ended after ${duration} seconds`);
|
||||
}
|
||||
|
||||
this._killOutdials();
|
||||
if (this.listenTask) await this.listenTask.kill();
|
||||
if (this.transcribeTask) await this.transcribeTask.kill();
|
||||
if (this.dlg) {
|
||||
assert(this.ep);
|
||||
if (this.dlg.connected) this.dlg.destroy();
|
||||
debug(`Dial:kill deleting endpoint ${this.ep.uuid}`);
|
||||
this.ep.destroy();
|
||||
}
|
||||
if (this.listenTask) await this.listenTask.kill();
|
||||
if (this.transcribeTask) await this.transcribeTask.kill();
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
@@ -126,7 +125,7 @@ class TaskDial extends Task {
|
||||
`${req.source_address}:${req.source_port}` :
|
||||
config.get('sbcAddress');
|
||||
const opts = {
|
||||
headers: this.headers,
|
||||
headers: req && req.has('X-CID') ? Object.assign(this.headers, {'X-CID': req.get('X-CID')}) : this.headers,
|
||||
proxy: `sip:${sbcAddress}`,
|
||||
callingNumber: this.callerId || req.callingNumber
|
||||
};
|
||||
@@ -138,9 +137,15 @@ class TaskDial extends Task {
|
||||
['callSid', 'callID', 'from', 'to', 'callerId', 'sipStatus', 'callStatus'].forEach((k) => delete callInfo[k]);
|
||||
|
||||
const ms = await cs.getMS();
|
||||
const timerRing = setTimeout(() => {
|
||||
this.logger.info(`Dial:_attemptCall: ring no answer timer ${this.timeout}s exceeded`);
|
||||
this._killOutdials();
|
||||
}, this.timeout * 1000);
|
||||
|
||||
this.target.forEach((t) => {
|
||||
try {
|
||||
t.url = t.url || this.url;
|
||||
t.url = t.url || this.confirmUrl;
|
||||
t.method = t.method || this.confirmMethod;
|
||||
const sd = placeCall({
|
||||
logger: this.logger,
|
||||
application: cs.application,
|
||||
@@ -165,13 +170,15 @@ class TaskDial extends Task {
|
||||
break;
|
||||
case CallStatus.InProgress:
|
||||
this.logger.debug('Dial:_attemptCall -- call was answered');
|
||||
clearTimeout(timerRing);
|
||||
break;
|
||||
case CallStatus.Failed:
|
||||
case CallStatus.Busy:
|
||||
case CallStatus.NoAnswer:
|
||||
this.dials.delete(sd.callSid);
|
||||
if (this.dials.size === 0 && !this.connectTime) {
|
||||
if (this.dials.size === 0 && !this.dlg) {
|
||||
this.logger.debug('Dial:_attemptCalls - all calls failed after call failure, ending task');
|
||||
clearTimeout(timerRing);
|
||||
this.kill();
|
||||
}
|
||||
break;
|
||||
@@ -190,7 +197,7 @@ class TaskDial extends Task {
|
||||
.on('decline', () => {
|
||||
this.logger.debug(`Dial:_attemptCalls - declined: ${sd.callSid}`);
|
||||
this.dials.delete(sd.callSid);
|
||||
if (this.dials.size === 0 && !this.connectTime) {
|
||||
if (this.dials.size === 0 && !this.dlg) {
|
||||
this.logger.debug('Dial:_attemptCalls - all calls failed after decline, ending task');
|
||||
this.kill();
|
||||
}
|
||||
@@ -214,19 +221,38 @@ class TaskDial extends Task {
|
||||
this._killOutdials(); // NB: order is important
|
||||
}
|
||||
|
||||
/**
|
||||
* We now have a call leg produced by the Dial action, so
|
||||
* - hangup any simrings in progress
|
||||
* - save the dialog and endpoint
|
||||
* - clock the start time of the call,
|
||||
* - start a max call length timer (optionally)
|
||||
* - launch any nested tasks
|
||||
* - and establish a handler to clean up if the called party hangs up
|
||||
*/
|
||||
_selectSingleDial(cs, sd) {
|
||||
this.connectTime = moment();
|
||||
this.dials.delete(sd.callSid);
|
||||
debug(`Dial:_selectSingleDial ep for outbound call: ${sd.ep.uuid}`);
|
||||
this.dials.delete(sd.callSid);
|
||||
|
||||
this.ep = sd.ep;
|
||||
this.dlg = sd.dlg;
|
||||
this.dlg.connectTime = moment();
|
||||
this.callSid = sd.callSid;
|
||||
if (this.earlyMedia) {
|
||||
debug('Dial:_selectSingleDial propagating answer supervision on A leg now that B is connected');
|
||||
cs.propagateAnswer();
|
||||
}
|
||||
let timerMaxCallDuration;
|
||||
if (this.timeLimit) {
|
||||
timerMaxCallDuration = setTimeout(() => {
|
||||
this.logger.info(`Dial:_selectSingleDial tearing down call as it has reached ${this.timeLimit}s`);
|
||||
this.ep.unbridge();
|
||||
this.kill();
|
||||
}, this.timeLimit * 1000);
|
||||
}
|
||||
this.dlg.on('destroy', () => {
|
||||
this.logger.debug('Dial:_selectSingleDial called party hungup, ending dial operation');
|
||||
if (timerMaxCallDuration) clearTimeout(timerMaxCallDuration);
|
||||
this.ep.unbridge();
|
||||
this.kill();
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ class TaskGather extends Task {
|
||||
[
|
||||
'action', 'finishOnKey', 'hints', 'input', 'method', 'numDigits',
|
||||
'partialResultCallback', 'partialResultCallbackMethod', 'profanityFilter',
|
||||
'speechTimeout', 'timeout', 'say'
|
||||
'speechTimeout', 'timeout', 'say', 'play'
|
||||
].forEach((k) => this[k] = this.data[k]);
|
||||
|
||||
this.partialResultCallbackMethod = this.partialResultCallbackMethod || 'POST';
|
||||
@@ -28,6 +28,7 @@ class TaskGather extends Task {
|
||||
this._earlyMedia = this.data.earlyMedia === true;
|
||||
|
||||
if (this.say) this.sayTask = makeTask(this.logger, {say: this.say}, this);
|
||||
if (this.play) this.playTask = makeTask(this.logger, {play: this.play}, this);
|
||||
}
|
||||
|
||||
get name() { return TaskName.Gather; }
|
||||
@@ -48,6 +49,12 @@ class TaskGather extends Task {
|
||||
if (!this.killed) this._startTimer();
|
||||
});
|
||||
}
|
||||
else if (this.playTask) {
|
||||
this.playTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete
|
||||
this.playTask.on('playDone', (err) => {
|
||||
if (!this.killed) this._startTimer();
|
||||
});
|
||||
}
|
||||
else this._startTimer();
|
||||
|
||||
if (this.input.includes('speech')) {
|
||||
@@ -122,7 +129,8 @@ class TaskGather extends Task {
|
||||
}
|
||||
|
||||
_killAudio() {
|
||||
this.sayTask.kill();
|
||||
if (this.sayTask && !this.sayTask.killed) this.sayTask.kill();
|
||||
if (this.playTask && !this.playTask.killed) this.playTask.kill();
|
||||
}
|
||||
|
||||
_onTranscription(ep, evt) {
|
||||
@@ -138,13 +146,13 @@ class TaskGather extends Task {
|
||||
async _resolve(reason, evt) {
|
||||
this.logger.debug(`TaskGather:resolve with reason ${reason}`);
|
||||
|
||||
this._clearTimer();
|
||||
if (reason.startsWith('dtmf')) {
|
||||
this.performAction(this.method, null, {digits: this.digitBuffer});
|
||||
await this.performAction(this.method, null, {digits: this.digitBuffer});
|
||||
}
|
||||
else if (reason.startsWith('speech')) {
|
||||
this.performAction(this.method, null, {speech: evt});
|
||||
await this.performAction(this.method, null, {speech: evt});
|
||||
}
|
||||
this._clearTimer();
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ function makeTask(logger, obj) {
|
||||
case TaskName.Say:
|
||||
const TaskSay = require('./say');
|
||||
return new TaskSay(logger, data);
|
||||
case TaskName.Play:
|
||||
const TaskPlay = require('./play');
|
||||
return new TaskPlay(logger, data);
|
||||
case TaskName.Gather:
|
||||
const TaskGather = require('./gather');
|
||||
return new TaskGather(logger, data);
|
||||
|
||||
@@ -37,11 +37,11 @@ class TaskSay extends Task {
|
||||
this.emit('playDone');
|
||||
}
|
||||
|
||||
kill() {
|
||||
async kill() {
|
||||
super.kill();
|
||||
if (this.ep.connected) {
|
||||
this.logger.debug('TaskSay:kill - killing audio');
|
||||
this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));
|
||||
await this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"speechTimeout": "number",
|
||||
"timeout": "number",
|
||||
"recognizer": "#recognizer",
|
||||
"play": "#play",
|
||||
"say": "#say"
|
||||
},
|
||||
"required": [
|
||||
@@ -58,6 +59,11 @@
|
||||
"action": "string",
|
||||
"answerOnBridge": "boolean",
|
||||
"callerId": "string",
|
||||
"confirmUrl": "string",
|
||||
"confirmMethod": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"dialMusic": "string",
|
||||
"headers": "object",
|
||||
"listen": "#listen",
|
||||
@@ -65,13 +71,7 @@
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"statusCallback": "string",
|
||||
"statusCallbackMethod": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"target": ["#target"],
|
||||
"url": "string",
|
||||
"timeLimit": "number",
|
||||
"timeout": "number",
|
||||
"transcribe": "#transcribe"
|
||||
|
||||
@@ -2,7 +2,6 @@ const Emitter = require('events');
|
||||
const debug = require('debug')('jambonz:feature-server');
|
||||
const assert = require('assert');
|
||||
const {TaskPreconditions} = require('../utils/constants');
|
||||
const hooks = require('../utils/notifiers');
|
||||
const specs = new Map();
|
||||
const _specData = require('./specs');
|
||||
for (const key in _specData) {specs.set(key, _specData[key]);}
|
||||
@@ -28,7 +27,9 @@ class Task extends Emitter {
|
||||
|
||||
async exec(cs) {
|
||||
this.cs = cs;
|
||||
const {actionHook, notifyHook} = hooks(this.logger, cs.callInfo);
|
||||
|
||||
// N.B. need to require it down here rather than at top to avoid recursion in require of this module
|
||||
const {actionHook, notifyHook} = require('../utils/notifiers')(this.logger, cs.callInfo);
|
||||
this.actionHook = actionHook;
|
||||
this.notifyHook = notifyHook;
|
||||
}
|
||||
@@ -55,7 +56,7 @@ class Task extends Emitter {
|
||||
if (this.action) {
|
||||
const tasks = await this.actionHook(this.action, method, auth, results);
|
||||
if (tasks && Array.isArray(tasks)) {
|
||||
this.logger.debug(`${this.name} replacing application with ${tasks.length} tasks`);
|
||||
this.logger.debug({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
|
||||
this.callSession.replaceApplication(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
const request = require('request');
|
||||
//require('request-debug')(request);
|
||||
const makeTask = require('../tasks/make_task');
|
||||
const normalizeJamones = require('./normalize-jamones');
|
||||
|
||||
const debug = require('debug')('jambonz:feature-server');
|
||||
const retrieveApp = require('./retrieve-app');
|
||||
|
||||
function hooks(logger, callAttributes) {
|
||||
debug(`notifiers: callAttributes ${JSON.stringify(callAttributes)}`);
|
||||
function actionHook(url, method, auth, opts, expectResponse = false) {
|
||||
function actionHook(url, method, auth, opts, expectResponse = true) {
|
||||
const params = Object.assign({}, callAttributes, opts);
|
||||
let basicauth, qs, body;
|
||||
if (auth && typeof auth === 'object' && Object.keys(auth) === 2) basicauth = auth;
|
||||
@@ -21,12 +16,9 @@ function hooks(logger, callAttributes) {
|
||||
logger.info(`actionHook error ${method} ${url}: ${err.message}`);
|
||||
return reject(err);
|
||||
}
|
||||
if (body) {
|
||||
if (body && expectResponse) {
|
||||
logger.debug(body, `actionHook response ${method} ${url}`);
|
||||
if (expectResponse) {
|
||||
const tasks = normalizeJamones(logger, body).map((tdata) => makeTask(logger, tdata));
|
||||
return resolve(tasks);
|
||||
}
|
||||
return resolve(retrieveApp(logger, body));
|
||||
}
|
||||
resolve(body);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ class SingleDialer extends Emitter {
|
||||
switch (this.target.type) {
|
||||
case 'phone':
|
||||
assert(this.target.number);
|
||||
uri = `sip:${this.opts.number}@${this.sbcAddress}`;
|
||||
uri = `sip:${this.target.number}@${this.sbcAddress}`;
|
||||
to = this.target.number;
|
||||
break;
|
||||
case 'user':
|
||||
@@ -51,9 +51,9 @@ class SingleDialer extends Emitter {
|
||||
to = this.target.name;
|
||||
break;
|
||||
case 'sip':
|
||||
assert(this.target.uri);
|
||||
uri = this.target.uri;
|
||||
to = this.target.name;
|
||||
assert(this.target.sipUri);
|
||||
uri = this.target.sipUri;
|
||||
to = this.target.sipUri;
|
||||
break;
|
||||
default:
|
||||
// should have been caught by parser
|
||||
|
||||
Reference in New Issue
Block a user