mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-19 04:17:44 +00:00
added initial support for REST-initiated outdials
This commit is contained in:
20
app.js
20
app.js
@@ -13,6 +13,13 @@ const {
|
||||
invokeWebCallback
|
||||
} = require('./lib/middleware')(srf, logger);
|
||||
|
||||
// HTTP
|
||||
const PORT = process.env.HTTP_PORT || 3000;
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
app.locals.logger = logger;
|
||||
const httpRoutes = require('./lib/http-routes');
|
||||
|
||||
const InboundCallSession = require('./lib/session/inbound-call-session');
|
||||
|
||||
// disable logging in test mode
|
||||
@@ -46,4 +53,17 @@ srf.invite((req, res) => {
|
||||
session.exec();
|
||||
});
|
||||
|
||||
|
||||
// HTTP
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
app.use('/', httpRoutes);
|
||||
app.use((err, req, res, next) => {
|
||||
logger.error(err, 'burped error');
|
||||
res.status(err.status || 500).json({msg: err.message});
|
||||
});
|
||||
app.listen(PORT);
|
||||
|
||||
logger.info(`listening for HTTP requests on port ${PORT}`);
|
||||
|
||||
module.exports = {srf};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"port": 3010,
|
||||
"secret": "cymru"
|
||||
},
|
||||
"freeswitch: {
|
||||
"freeswitch": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 8021,
|
||||
"secret": "ClueCon"
|
||||
@@ -16,5 +16,15 @@
|
||||
"user": "jambones",
|
||||
"password": "jambones",
|
||||
"database": "jambones"
|
||||
},
|
||||
"outdials": {
|
||||
"drachtio": [
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": 9022,
|
||||
"secret": "cymru"
|
||||
}
|
||||
],
|
||||
"sbc": ["127.0.0.1:5060"]
|
||||
}
|
||||
}
|
||||
155
lib/http-routes/api/create-call.js
Normal file
155
lib/http-routes/api/create-call.js
Normal file
@@ -0,0 +1,155 @@
|
||||
const config = require('config');
|
||||
const router = require('express').Router();
|
||||
const sysError = require('./error');
|
||||
const makeTask = require('../../tasks/make_task');
|
||||
const RestCallSession = require('../../session/rest-call-session');
|
||||
const CallInfo = require('../../session/call-info');
|
||||
const {CallDirection} = require('../../utils/constants');
|
||||
const parseUrl = require('parse-url');
|
||||
const SipError = require('drachtio-srf').SipError;
|
||||
const Srf = require('drachtio-srf');
|
||||
const drachtio = config.get('outdials.drachtio');
|
||||
const sbcs = config.get('outdials.sbc');
|
||||
const Mrf = require('drachtio-fsmrf');
|
||||
let idxDrachtio = 0;
|
||||
let idxSbc = 0;
|
||||
|
||||
const srfs = drachtio.map((d) => {
|
||||
const srf = new Srf();
|
||||
srf.connect(d);
|
||||
srf
|
||||
.on('connect', (err, hp) => {
|
||||
console.log(err, `Connected to drachtio at ${hp}`);
|
||||
srf.locals.mrf = new Mrf(srf);
|
||||
})
|
||||
.on('error', (err) => console.log(err));
|
||||
return srf;
|
||||
});
|
||||
|
||||
async function validate(logger, payload) {
|
||||
const data = Object.assign({}, {
|
||||
from: payload.from,
|
||||
to: payload.to,
|
||||
call_hook: payload.call_hook
|
||||
});
|
||||
|
||||
const u = parseUrl(payload.call_hook.url);
|
||||
const myPort = u.port ? `:${u.port}` : '';
|
||||
payload.originalRequest = {
|
||||
baseUrl: `${u.protocol}://${u.resource}${myPort}`,
|
||||
method: payload.call_hook.method
|
||||
};
|
||||
if (payload.call_hook.username && payload.call_hook.password) {
|
||||
payload.originalRequest.auth = {
|
||||
username: payload.call_hook.username,
|
||||
password: payload.call_hook.password
|
||||
};
|
||||
}
|
||||
|
||||
return makeTask(logger, {'rest:dial': data});
|
||||
}
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
logger.debug({body: req.body}, 'got createCall request');
|
||||
try {
|
||||
let uri;
|
||||
const restDial = await validate(logger, req.body);
|
||||
const sbcAddress = sbcs[idxSbc++ % sbcs.length];
|
||||
const srf = srfs[idxDrachtio++ % srfs.length];
|
||||
const target = restDial.to;
|
||||
const opts = {
|
||||
'callingNumber': restDial.from
|
||||
};
|
||||
|
||||
switch (target.type) {
|
||||
case 'phone':
|
||||
uri = `sip:${target.number}@${sbcAddress}`;
|
||||
break;
|
||||
case 'user':
|
||||
uri = `sip:${target.name}`;
|
||||
break;
|
||||
case 'sip':
|
||||
uri = target.sipUri;
|
||||
break;
|
||||
}
|
||||
|
||||
/* create endpoint for outdial */
|
||||
const mrf = srf.locals.mrf;
|
||||
const ms = await mrf.connect(config.get('freeswitch'));
|
||||
logger.debug('createCall: successfully connected to media server');
|
||||
const ep = await ms.createEndpoint();
|
||||
logger.debug(`createCall: successfully allocated endpoint, sending INVITE to ${sbcAddress}`);
|
||||
ms.destroy();
|
||||
|
||||
/* launch outdial */
|
||||
let sdp, sipLogger;
|
||||
const connectStream = async(remoteSdp) => {
|
||||
if (remoteSdp !== sdp) {
|
||||
ep.modify(sdp = remoteSdp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Object.assign(opts, {
|
||||
proxy: `sip:${sbcAddress}`,
|
||||
localSdp: ep.local.sdp
|
||||
});
|
||||
if (target.auth) opts.auth = this.target.auth;
|
||||
const application = req.body;
|
||||
|
||||
try {
|
||||
const dlg = await srf.createUAC(uri, opts, {
|
||||
cbRequest: (err, inviteReq) => {
|
||||
if (err) {
|
||||
this.logger.error(err, 'createCall Error creating call');
|
||||
res.status(500).send('Call Failure');
|
||||
ep.destroy();
|
||||
}
|
||||
|
||||
/* call is in flight */
|
||||
const tasks = [restDial];
|
||||
const callInfo = new CallInfo({
|
||||
direction: CallDirection.Outbound,
|
||||
req: inviteReq,
|
||||
to: req.body.to,
|
||||
tag: req.body.tag,
|
||||
accountSid: req.body.account_sid,
|
||||
applicationSid: req.body.application_sid
|
||||
});
|
||||
const cs = new RestCallSession({logger, application, srf, req: inviteReq, ep, tasks, callInfo});
|
||||
cs.exec(req);
|
||||
|
||||
res.status(201).json({sid: cs.callSid});
|
||||
|
||||
sipLogger = logger.child({
|
||||
callSid: cs.callSid,
|
||||
callId: callInfo.callId
|
||||
});
|
||||
sipLogger.info(`outbound REST call attempt to ${JSON.stringify(target)} has been sent`);
|
||||
},
|
||||
cbProvisional: (prov) => {
|
||||
if ([180, 183].includes(prov.status) && prov.body) connectStream(prov.body);
|
||||
restDial.emit('callStatus', prov.status, !!prov.body);
|
||||
}
|
||||
});
|
||||
connectStream(dlg.remote.sdp);
|
||||
restDial.emit('callStatus', 200);
|
||||
restDial.emit('connect', dlg);
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof SipError) {
|
||||
sipLogger.info(`REST outdial failed with ${err.status}`);
|
||||
}
|
||||
else {
|
||||
sipLogger.error({err}, 'REST outdial failed');
|
||||
}
|
||||
ep.destroy();
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
20
lib/http-routes/api/error.js
Normal file
20
lib/http-routes/api/error.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../utils/errors');
|
||||
|
||||
function sysError(logger, res, err) {
|
||||
if (err instanceof DbErrorBadRequest) {
|
||||
logger.info(err, 'invalid client request');
|
||||
return res.status(400).json({msg: err.message});
|
||||
}
|
||||
if (err instanceof DbErrorUnprocessableRequest) {
|
||||
logger.info(err, 'unprocessable request');
|
||||
return res.status(422).json({msg: err.message});
|
||||
}
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
logger.info(err, 'duplicate entry on insert');
|
||||
return res.status(422).json({msg: err.message});
|
||||
}
|
||||
logger.error(err, 'Database error');
|
||||
res.status(500).json({msg: err.message});
|
||||
}
|
||||
|
||||
module.exports = sysError;
|
||||
5
lib/http-routes/api/index.js
Normal file
5
lib/http-routes/api/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const api = require('express').Router();
|
||||
|
||||
api.use('/createCall', require('./create-call'));
|
||||
|
||||
module.exports = api;
|
||||
16
lib/http-routes/index.js
Normal file
16
lib/http-routes/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const express = require('express');
|
||||
const api = require('./api');
|
||||
const routes = express.Router();
|
||||
|
||||
routes.use('/v1', api);
|
||||
|
||||
// health checks
|
||||
routes.get('/', (req, res) => {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
routes.get('/health', (req, res) => {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
module.exports = routes;
|
||||
23
lib/http-routes/utils/errors.js
Normal file
23
lib/http-routes/utils/errors.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class DbError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class DbErrorBadRequest extends DbError {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class DbErrorUnprocessableRequest extends DbError {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DbError,
|
||||
DbErrorBadRequest,
|
||||
DbErrorUnprocessableRequest
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
//const debug = require('debug')('jambonz:feature-server');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const {CallStatus, CallDirection} = require('./utils/constants');
|
||||
const {CallDirection} = require('./utils/constants');
|
||||
const CallInfo = require('./session/call-info');
|
||||
const retrieveApp = require('./utils/retrieve-app');
|
||||
const parseUrl = require('parse-url');
|
||||
@@ -71,7 +71,7 @@ module.exports = function(srf, logger) {
|
||||
const logger = req.locals.logger;
|
||||
const app = req.locals.application;
|
||||
const call_hook = app.call_hook;
|
||||
const method = (call_hook.method || 'POST').toUpperCase();
|
||||
const method = call_hook.method.toUpperCase();
|
||||
let auth;
|
||||
if (call_hook.username && call_hook.password) {
|
||||
auth = {username: call_hook.username, password: call_hook.password};
|
||||
@@ -81,7 +81,8 @@ module.exports = function(srf, logger) {
|
||||
const myPort = u.port ? `:${u.port}` : '';
|
||||
app.originalRequest = {
|
||||
baseUrl: `${u.protocol}://${u.resource}${myPort}`,
|
||||
auth
|
||||
auth,
|
||||
method
|
||||
};
|
||||
logger.debug({url: call_hook.url, method}, 'invokeWebCallback');
|
||||
const obj = Object.assign({}, req.locals.callInfo);
|
||||
@@ -91,8 +92,8 @@ module.exports = function(srf, logger) {
|
||||
app.tasks = await retrieveApp(logger, call_hook.url, method, auth, obj);
|
||||
next();
|
||||
} catch (err) {
|
||||
logger.error(err, 'Error retrieving or parsing application');
|
||||
res.send(500);
|
||||
logger.info(`Error retrieving or parsing application: ${err.message}`);
|
||||
res.send(480, {headers: {'X-Reason': err.message}});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
|
||||
34
lib/session/rest-call-session.js
Normal file
34
lib/session/rest-call-session.js
Normal 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;
|
||||
@@ -4,7 +4,6 @@ const {CallStatus, CallDirection, TaskName, TaskPreconditions, MAX_SIMRINGS} = r
|
||||
const assert = require('assert');
|
||||
const placeCall = require('../utils/place-outdial');
|
||||
const config = require('config');
|
||||
const moment = require('moment');
|
||||
const debug = require('debug')('jambonz:feature-server');
|
||||
|
||||
function compareTasks(t1, t2) {
|
||||
@@ -68,6 +67,14 @@ class TaskDial extends Task {
|
||||
this.dials = new Map();
|
||||
}
|
||||
|
||||
get dlg() {
|
||||
if (this.sd) return this.sd.dlg;
|
||||
}
|
||||
|
||||
get ep() {
|
||||
if (this.sd) return this.sd.ep;
|
||||
}
|
||||
|
||||
get name() { return TaskName.Dial; }
|
||||
|
||||
async exec(cs) {
|
||||
@@ -87,24 +94,25 @@ class TaskDial extends Task {
|
||||
|
||||
async kill() {
|
||||
super.kill();
|
||||
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.sd) {
|
||||
this.sd.kill();
|
||||
this.sd = null;
|
||||
}
|
||||
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.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
_killOutdials() {
|
||||
for (const [callSid, sd] of Array.from(this.dials)) {
|
||||
this.logger.debug(`Dial:_killOutdials killing callSid ${callSid}`);
|
||||
sd.kill().catch((err) => this.logger.info(err, `Dial:_killOutdials Error killing ${callSid}`));
|
||||
}
|
||||
this.dials.clear();
|
||||
}
|
||||
|
||||
async _initializeInbound(cs) {
|
||||
const {ep} = await cs.connectInboundCallToIvr(this.earlyMedia);
|
||||
this.epOther = ep;
|
||||
@@ -153,6 +161,13 @@ class TaskDial extends Task {
|
||||
this.dials.set(sd.callSid, sd);
|
||||
|
||||
sd
|
||||
.on('callCreateFail', () => {
|
||||
this.dials.delete(sd.callSid);
|
||||
if (this.dials.size === 0 && !this.sd) {
|
||||
this.logger.debug('Dial:_attemptCalls - all calls failed after call create err, ending task');
|
||||
this.kill();
|
||||
}
|
||||
})
|
||||
.on('callStatusChange', (obj) => {
|
||||
switch (obj.callStatus) {
|
||||
case CallStatus.Trying:
|
||||
@@ -170,7 +185,7 @@ class TaskDial extends Task {
|
||||
case CallStatus.Busy:
|
||||
case CallStatus.NoAnswer:
|
||||
this.dials.delete(sd.callSid);
|
||||
if (this.dials.size === 0 && !this.dlg) {
|
||||
if (this.dials.size === 0 && !this.sd) {
|
||||
this.logger.debug('Dial:_attemptCalls - all calls failed after call failure, ending task');
|
||||
clearTimeout(timerRing);
|
||||
this.kill();
|
||||
@@ -191,7 +206,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.dlg) {
|
||||
if (this.dials.size === 0 && !this.sd) {
|
||||
this.logger.debug('Dial:_attemptCalls - all calls failed after decline, ending task');
|
||||
this.kill();
|
||||
}
|
||||
@@ -228,17 +243,14 @@ class TaskDial extends Task {
|
||||
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.sd = sd;
|
||||
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.timerMaxCallDuration = setTimeout(() => {
|
||||
this.logger.info(`Dial:_selectSingleDial tearing down call as it has reached ${this.timeLimit}s`);
|
||||
this.ep.unbridge();
|
||||
this.kill();
|
||||
@@ -246,7 +258,7 @@ class TaskDial extends Task {
|
||||
}
|
||||
this.dlg.on('destroy', () => {
|
||||
this.logger.debug('Dial:_selectSingleDial called party hungup, ending dial operation');
|
||||
if (timerMaxCallDuration) clearTimeout(timerMaxCallDuration);
|
||||
if (this.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
|
||||
this.ep.unbridge();
|
||||
this.kill();
|
||||
});
|
||||
@@ -260,14 +272,6 @@ class TaskDial extends Task {
|
||||
if (this.listenTask) this.listenTask.exec(cs, this.ep, this);
|
||||
}
|
||||
|
||||
_killOutdials() {
|
||||
for (const [callSid, sd] of Array.from(this.dials)) {
|
||||
this.logger.debug(`Dial:_killOutdials killing callSid ${callSid}`);
|
||||
sd.kill().catch((err) => this.logger.info(err, `Dial:_killOutdials Error killing ${callSid}`));
|
||||
}
|
||||
this.dials.clear();
|
||||
}
|
||||
|
||||
_bridgeEarlyMedia(sd) {
|
||||
if (this.epOther && !this.bridged) {
|
||||
this.epOther.api('uuid_break', this.epOther.uuid);
|
||||
|
||||
@@ -4,19 +4,21 @@ const makeTask = require('./make_task');
|
||||
const moment = require('moment');
|
||||
|
||||
class TaskListen extends Task {
|
||||
constructor(logger, opts) {
|
||||
constructor(logger, opts, parentTask) {
|
||||
super(logger, opts);
|
||||
this.preconditions = TaskPreconditions.Endpoint;
|
||||
|
||||
[
|
||||
'action', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep',
|
||||
'sampleRate', 'timeout', 'transcribe'
|
||||
'action', 'auth', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep',
|
||||
'sampleRate', 'timeout', 'transcribe', 'wsAuth'
|
||||
].forEach((k) => this[k] = this.data[k]);
|
||||
|
||||
this.mixType = this.mixType || 'mono';
|
||||
this.sampleRate = this.sampleRate || 8000;
|
||||
this.method = this.method || 'POST';
|
||||
this.earlyMedia = this.data.earlyMedia === true;
|
||||
this.hook = this.normalizeUrl(this.url, 'GET', this.wsAuth);
|
||||
this.nested = typeof parentTask !== 'undefined';
|
||||
|
||||
this.results = {};
|
||||
|
||||
if (this.transcribe) this.transcribeTask = makeTask(logger, {'transcribe': opts.transcribe}, this);
|
||||
@@ -35,9 +37,9 @@ class TaskListen extends Task {
|
||||
this.logger.debug('TaskListen:exec - starting nested transcribe task');
|
||||
this.transcribeTask.exec(cs, ep, this);
|
||||
}
|
||||
await this._startListening(ep);
|
||||
await this._startListening(cs, ep);
|
||||
await this.awaitTaskDone();
|
||||
if (this.action) await this.performAction(this.method, null, this.results);
|
||||
if (this.action) await this.performAction(this.method, this.auth, this.results, !this.nested);
|
||||
} catch (err) {
|
||||
this.logger.info(err, `TaskListen:exec - error ${this.url}`);
|
||||
}
|
||||
@@ -47,8 +49,10 @@ class TaskListen extends Task {
|
||||
|
||||
async kill() {
|
||||
super.kill();
|
||||
this.logger.debug(`TaskListen:kill endpoint connected? ${this.ep && this.ep.connected}`);
|
||||
this._clearTimer();
|
||||
if (this.ep.connected) {
|
||||
if (this.ep && this.ep.connected) {
|
||||
this.logger.debug('TaskListen:kill closing websocket');
|
||||
await this.ep.forkAudioStop()
|
||||
.catch((err) => this.logger.info(err, 'TaskListen:kill'));
|
||||
}
|
||||
@@ -65,13 +69,26 @@ class TaskListen extends Task {
|
||||
.catch((err) => this.logger.info(err, 'TaskListen:_playBeep Error playing beep'));
|
||||
}
|
||||
|
||||
async _startListening(ep) {
|
||||
async _startListening(cs, ep) {
|
||||
this._initListeners(ep);
|
||||
const metadata = Object.assign(
|
||||
{sampleRate: this.sampleRate, mixType: this.mixType},
|
||||
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');
|
||||
await this.ep.set({
|
||||
'MOD_AUDIO_BASIC_AUTH_USERNAME': this.hook.username,
|
||||
'MOD_AUDIO_BASIC_AUTH_PASSWORD': this.hook.password
|
||||
});
|
||||
}
|
||||
await ep.forkAudioStart({
|
||||
wsUrl: this.url,
|
||||
wsUrl: this.hook.url,
|
||||
mixType: this.mixType,
|
||||
sampling: this.sampleRate,
|
||||
metadata: this.metadata
|
||||
metadata
|
||||
});
|
||||
this.recordStartTime = moment();
|
||||
if (this.maxLength) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Task = require('./task');
|
||||
const {TaskName} = require('../utils/constants');
|
||||
const errBadInstruction = new Error('invalid instruction payload');
|
||||
const errBadInstruction = new Error('malformed jambonz application payload');
|
||||
|
||||
function makeTask(logger, obj) {
|
||||
const keys = Object.keys(obj);
|
||||
@@ -42,13 +42,16 @@ function makeTask(logger, obj) {
|
||||
case TaskName.Redirect:
|
||||
const TaskRedirect = require('./redirect');
|
||||
return new TaskRedirect(logger, data);
|
||||
case TaskName.RestDial:
|
||||
const TaskRestDial = require('./rest_dial');
|
||||
return new TaskRestDial(logger, data);
|
||||
case TaskName.Tag:
|
||||
const TaskTag = require('./tag');
|
||||
return new TaskTag(logger, data);
|
||||
}
|
||||
|
||||
// should never reach
|
||||
throw new Error(`invalid task ${name} (please update specs.json and make_task.js)`);
|
||||
throw new Error(`invalid jambonz verb '${name}'`);
|
||||
}
|
||||
|
||||
module.exports = makeTask;
|
||||
|
||||
@@ -9,7 +9,7 @@ class TaskRedirect extends Task {
|
||||
super(logger, opts);
|
||||
|
||||
this.action = this.data.action;
|
||||
this.method = this.data.method || 'POST';
|
||||
this.method = (this.data.method || 'POST').toUpperCase();
|
||||
this.auth = this.data.auth;
|
||||
}
|
||||
|
||||
|
||||
82
lib/tasks/rest_dial.js
Normal file
82
lib/tasks/rest_dial.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const Task = require('./task');
|
||||
const {TaskName} = require('../utils/constants');
|
||||
|
||||
/**
|
||||
* Manages an outdial made via REST API
|
||||
*/
|
||||
class TaskRestDial extends Task {
|
||||
constructor(logger, opts) {
|
||||
super(logger, opts);
|
||||
|
||||
this.from = this.data.from;
|
||||
this.to = this.data.to;
|
||||
this.call_hook = this.data.call_hook;
|
||||
this.timeout = this.data.timeout || 60;
|
||||
|
||||
this.on('connect', this._onConnect.bind(this));
|
||||
this.on('callStatus', this._onCallStatus.bind(this));
|
||||
}
|
||||
|
||||
get name() { return TaskName.RestDial; }
|
||||
|
||||
/**
|
||||
* INVITE has just been sent at this point
|
||||
*/
|
||||
async exec(cs, req) {
|
||||
super.exec(cs);
|
||||
this.req = req;
|
||||
|
||||
this._setCallTimer();
|
||||
await this.awaitTaskDone();
|
||||
}
|
||||
|
||||
kill() {
|
||||
super.kill();
|
||||
this._clearCallTimer();
|
||||
if (this.req) {
|
||||
this.req.cancel();
|
||||
this.req = null;
|
||||
}
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
async _onConnect(dlg) {
|
||||
this.req = null;
|
||||
const cs = this.callSession;
|
||||
cs.setDialog(dlg);
|
||||
const obj = Object.assign({}, cs.callInfo);
|
||||
|
||||
const tasks = await this.actionHook(this.call_hook, obj);
|
||||
if (tasks && Array.isArray(tasks)) {
|
||||
this.logger.debug({tasks: tasks}, `TaskRestDial: replacing application with ${tasks.length} tasks`);
|
||||
cs.replaceApplication(tasks);
|
||||
}
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
_onCallStatus(status) {
|
||||
this.logger.debug(`CallStatus: ${status}`);
|
||||
if (status >= 200) {
|
||||
this.req = null;
|
||||
this._clearCallTimer();
|
||||
if (status !== 200) this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
_setCallTimer() {
|
||||
this.timer = setTimeout(this._onCallTimeout.bind(this), this.timeout * 1000);
|
||||
}
|
||||
|
||||
_clearCallTimer() {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
_onCallTimeout() {
|
||||
this.logger.debug('TaskRestDial: timeout expired without answer, killing task');
|
||||
this.timer = null;
|
||||
this.kill();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskRestDial;
|
||||
@@ -83,9 +83,14 @@
|
||||
"listen": {
|
||||
"properties": {
|
||||
"action": "string",
|
||||
"auth": "#auth",
|
||||
"finishOnKey": "string",
|
||||
"maxLength": "number",
|
||||
"metadata": "object",
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST"]
|
||||
},
|
||||
"mixType": {
|
||||
"type": "string",
|
||||
"enum": ["mono", "stereo", "mixed"]
|
||||
@@ -96,6 +101,7 @@
|
||||
"timeout": "number",
|
||||
"transcribe": "#transcribe",
|
||||
"url": "string",
|
||||
"wsAuth": "#auth",
|
||||
"earlyMedia": "boolean"
|
||||
},
|
||||
"required": [
|
||||
@@ -115,6 +121,19 @@
|
||||
"action"
|
||||
]
|
||||
},
|
||||
"rest:dial": {
|
||||
"properties": {
|
||||
"call_hook": "object",
|
||||
"from": "string",
|
||||
"to": "#target",
|
||||
"timeout": "number"
|
||||
},
|
||||
"required": [
|
||||
"call_hook",
|
||||
"from",
|
||||
"to"
|
||||
]
|
||||
},
|
||||
"tag": {
|
||||
"properties": {
|
||||
"data": "object"
|
||||
@@ -155,11 +174,11 @@
|
||||
},
|
||||
"auth": {
|
||||
"properties": {
|
||||
"user": "string",
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
},
|
||||
"required": [
|
||||
"user",
|
||||
"username",
|
||||
"password"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ class TaskTag extends Task {
|
||||
async exec(cs) {
|
||||
super.exec(cs);
|
||||
cs.callInfo.customerData = this.data;
|
||||
this.logger.debug({customerData: cs.callInfo.customerData}, 'TaskTag:exec set customer data');
|
||||
this.logger.debug({customerData: this.data}, 'TaskTag:exec set customer data');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,16 +56,26 @@ class Task extends Emitter {
|
||||
return this._completionPromise;
|
||||
}
|
||||
|
||||
async performAction(method, auth, results) {
|
||||
if (this.action) {
|
||||
let action = this.action;
|
||||
if (action.startsWith('/')) {
|
||||
const or = this.callSession.originalRequest;
|
||||
action = `${or.baseUrl}${this.action}`;
|
||||
this.logger.debug({originalUrl: this.action, normalizedUrl: action}, 'Task:performAction normalized url');
|
||||
if (!auth && or.auth) auth = or.auth;
|
||||
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);
|
||||
}
|
||||
const tasks = await this.actionHook(action, method, auth, results);
|
||||
}
|
||||
this.logger.debug({hook}, 'Task:normalizeUrl');
|
||||
return hook;
|
||||
}
|
||||
|
||||
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)) {
|
||||
this.logger.debug({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
|
||||
this.callSession.replaceApplication(tasks);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"Listen": "listen",
|
||||
"Play": "play",
|
||||
"Redirect": "redirect",
|
||||
"RestDial": "rest:dial",
|
||||
"SipDecline": "sip:decline",
|
||||
"SipNotify": "sip:notify",
|
||||
"SipRedirect": "sip:redirect",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
function normalizeJambones(logger, obj) {
|
||||
logger.debug(`normalizeJambones: ${JSON.stringify(obj)}`);
|
||||
if (!Array.isArray(obj)) throw new Error('invalid JSON: jambones docs must be array');
|
||||
if (!Array.isArray(obj)) throw new Error('malformed jambonz payload: must be array');
|
||||
const document = [];
|
||||
for (const tdata of obj) {
|
||||
if (typeof tdata !== 'object') throw new Error('invalid JSON: jambones docs must be array of objects');
|
||||
if (typeof tdata !== 'object') throw new Error('malformed jambonz payload: must be array of objects');
|
||||
if ('verb' in tdata) {
|
||||
// {verb: 'say', text: 'foo..bar'..}
|
||||
const name = tdata.verb;
|
||||
@@ -21,8 +21,8 @@ function normalizeJambones(logger, obj) {
|
||||
document.push(tdata);
|
||||
}
|
||||
else {
|
||||
logger.info(tdata, `invalid JSON: invalid verb form, numkeys ${Object.keys(tdata).length}`);
|
||||
throw new Error('invalid JSON: invalid verb form');
|
||||
logger.info(tdata, 'malformed jambonz payload: missing verb property');
|
||||
throw new Error('malformed jambonz payload: missing verb property');
|
||||
}
|
||||
}
|
||||
logger.debug(`returning document with ${document.length} tasks`);
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
const request = require('request');
|
||||
require('request-debug')(request);
|
||||
//require('request-debug')(request);
|
||||
const retrieveApp = require('./retrieve-app');
|
||||
|
||||
function hooks(logger, callInfo) {
|
||||
logger.debug({callInfo}, 'creating action hook');
|
||||
function actionHook(hook, obj = {}, expectResponse = true) {
|
||||
const method = hook.method.toUpperCase();
|
||||
const method = (hook.method || 'POST').toUpperCase();
|
||||
const auth = (hook.username && hook.password) ?
|
||||
{username: hook.username, password: hook.password} :
|
||||
null;
|
||||
|
||||
const data = Object.assign({}, obj, callInfo);
|
||||
logger.debug({data}, `actionhook sending to ${hook.url}`);
|
||||
if ('GET' === method) {
|
||||
// remove customer data - only for POSTs since it might be quite complex
|
||||
delete data.customerData;
|
||||
}
|
||||
const data = Object.assign({}, obj, callInfo.toJSON());
|
||||
logger.debug({hook, data, auth}, 'actionhook');
|
||||
|
||||
/* customer data only on POSTs */
|
||||
if ('GET' === method) delete data.customerData;
|
||||
|
||||
const opts = {
|
||||
url: hook.url,
|
||||
method,
|
||||
json: 'POST' === method || expectResponse
|
||||
};
|
||||
if (auth) obj.auth = auth;
|
||||
if (auth) opts.auth = auth;
|
||||
if ('POST' === method) opts.body = data;
|
||||
else opts.qs = data;
|
||||
|
||||
@@ -40,8 +39,8 @@ function hooks(logger, callInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
function notifyHook(url, method, auth, opts = {}) {
|
||||
return actionHook(url, method, auth, opts, false);
|
||||
function notifyHook(hook, opts = {}) {
|
||||
return actionHook(hook, opts, false);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@ const ConfirmCallSession = require('../session/confirm-call-session');
|
||||
const hooks = require('./notifiers');
|
||||
const moment = require('moment');
|
||||
const parseUrl = require('parse-url');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
|
||||
class SingleDialer extends Emitter {
|
||||
constructor({logger, sbcAddress, target, opts, application, callInfo}) {
|
||||
@@ -25,23 +26,13 @@ class SingleDialer extends Emitter {
|
||||
this.bindings = logger.bindings();
|
||||
|
||||
this.parentCallInfo = callInfo;
|
||||
/*
|
||||
this.callInfo = Object.assign({}, callInfo, {
|
||||
callSid: this._callSid,
|
||||
parentCallSid: callInfo.callSid,
|
||||
direction: CallDirection.Outbound,
|
||||
callStatus: CallStatus.Trying,
|
||||
sipStatus: 100
|
||||
});
|
||||
*/
|
||||
this.callGone = false;
|
||||
|
||||
this.callSid = uuidv4();
|
||||
|
||||
this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
|
||||
}
|
||||
|
||||
get callSid() {
|
||||
return this._callSid;
|
||||
}
|
||||
get callStatus() {
|
||||
return this.callInfo.callStatus;
|
||||
}
|
||||
@@ -88,21 +79,26 @@ class SingleDialer extends Emitter {
|
||||
if (this.target.auth) opts.auth = this.target.auth;
|
||||
this.dlg = await srf.createUAC(uri, opts, {
|
||||
cbRequest: (err, req) => {
|
||||
if (err) return this.logger.error(err, 'SingleDialer:exec Error creating call');
|
||||
if (err) {
|
||||
this.logger.error(err, 'SingleDialer:exec Error creating call');
|
||||
this.emit('callCreateFail', err);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* INVITE has been sent out
|
||||
* (a) create a CallInfo for this call
|
||||
* (a) create a logger for this call
|
||||
* (b) augment this.callInfo with additional call info
|
||||
*/
|
||||
this.logger.debug(`call sent, creating CallInfo parentCallInfo is CallInfo? ${this.parentCallInfo instanceof CallInfo}`);
|
||||
this.callInfo = new CallInfo({
|
||||
direction: CallDirection.Outbound,
|
||||
parentCallInfo: this.parentCallInfo,
|
||||
req
|
||||
req,
|
||||
to,
|
||||
callSid: this.callSid
|
||||
});
|
||||
this.logger = srf.locals.parentLogger.child({
|
||||
callSid: this.callInfo.callSid,
|
||||
callSid: this.callSid,
|
||||
parentCallSid: this.parentCallInfo.callSid,
|
||||
callId: this.callInfo.callId
|
||||
});
|
||||
@@ -164,6 +160,7 @@ class SingleDialer extends Emitter {
|
||||
const duration = moment().diff(this.dlg.connectTime, 'seconds');
|
||||
this.logger.debug('SingleDialer:kill hanging up called party');
|
||||
this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration});
|
||||
this.dlg.destroy();
|
||||
}
|
||||
if (this.ep) {
|
||||
this.logger.debug(`SingleDialer:kill - deleting endpoint ${this.ep.uuid}`);
|
||||
@@ -181,13 +178,14 @@ class SingleDialer extends Emitter {
|
||||
async _executeApp(url) {
|
||||
this.logger.debug(`SingleDialer:_executeApp: executing ${url} after connect`);
|
||||
try {
|
||||
let auth;
|
||||
let auth, method;
|
||||
const app = Object.assign({}, this.application);
|
||||
if (url.startsWith('/')) {
|
||||
const savedUrl = url;
|
||||
const or = app.originalRequest;
|
||||
url = `${or.baseUrl}${url}`;
|
||||
auth = or.auth;
|
||||
method = this.method || or.method || 'POST';
|
||||
this.logger.debug({originalUrl: savedUrl, normalizedUrl: url}, 'SingleDialer:_executeApp normalized url');
|
||||
}
|
||||
else {
|
||||
@@ -196,9 +194,10 @@ class SingleDialer extends Emitter {
|
||||
app.originalRequest = {
|
||||
baseUrl: `${u.protocol}://${u.resource}${myPort}`
|
||||
};
|
||||
method = this.method || 'POST';
|
||||
}
|
||||
|
||||
const tasks = await this.actionHook(url, this.method, auth);
|
||||
const tasks = await this.actionHook({url, method, auth});
|
||||
const allowedTasks = tasks.filter((task) => {
|
||||
return [
|
||||
TaskPreconditions.StableCall,
|
||||
@@ -210,7 +209,14 @@ class SingleDialer extends Emitter {
|
||||
}
|
||||
|
||||
this.logger.debug(`SingleDialer:_executeApp: executing ${tasks.length} tasks`);
|
||||
const cs = new ConfirmCallSession({logger: this.logger, application: app, dlg: this.dlg, ep: this.ep, tasks});
|
||||
const cs = new ConfirmCallSession({
|
||||
logger: this.logger,
|
||||
application: app,
|
||||
dlg: this.dlg,
|
||||
ep: this.ep,
|
||||
callInfo: this.callInfo,
|
||||
tasks
|
||||
});
|
||||
await cs.exec();
|
||||
this.emit(this.dlg.connected ? 'accept' : 'decline');
|
||||
} catch (err) {
|
||||
@@ -220,9 +226,12 @@ class SingleDialer extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
_notifyCallStatusChange({callStatus, sipStatus}) {
|
||||
this.logger.debug(`SingleDialer:_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');
|
||||
this.callInfo.updateCallStatus(callStatus, sipStatus);
|
||||
if (typeof duration === 'number') this.callInfo.duration = duration;
|
||||
try {
|
||||
this.notifyHook(this.application.call_status_hook);
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,7 +12,9 @@ function retrieveUrl(logger, url, method, auth, obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(opts, (err, response, body) => {
|
||||
if (err) throw err;
|
||||
if (body) logger.debug({body}, 'retrieveUrl: customer returned an application');
|
||||
if (response.statusCode === 401) return reject(new Error('HTTP request failed: Unauthorized'));
|
||||
else if (response.statusCode !== 200) return reject(new Error(`HTTP request failed: ${response.statusCode}`));
|
||||
if (body) logger.debug({body}, 'retrieveUrl: ${method} ${url} returned an application');
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
|
||||
402
package-lock.json
generated
402
package-lock.json
generated
@@ -127,6 +127,15 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
|
||||
@@ -219,6 +228,11 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
@@ -302,6 +316,43 @@
|
||||
"tape": ">=2.0.0 <5.0.0"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -339,6 +390,11 @@
|
||||
"integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
|
||||
"dev": true
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"caching-transform": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz",
|
||||
@@ -533,6 +589,26 @@
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
|
||||
@@ -550,6 +626,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@@ -675,11 +761,21 @@
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"deprecate": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz",
|
||||
"integrity": "sha512-ZGDXefq1xknT292LnorMY5s8UVU08/WKdzDZCUT6t9JzsiMSP4uzUhgpqugffNVcT5WC6wMBiSQ+LFjlv3v7iQ=="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
|
||||
@@ -811,12 +907,22 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@@ -869,6 +975,11 @@
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -1003,6 +1114,11 @@
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"eventemitter2": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
|
||||
@@ -1014,6 +1130,73 @@
|
||||
"integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=",
|
||||
"dev": true
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@@ -1088,6 +1271,35 @@
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"find-cache-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
|
||||
@@ -1199,6 +1411,16 @@
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"fs-exists-cached": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz",
|
||||
@@ -1383,6 +1605,25 @@
|
||||
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
|
||||
"dev": true
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
@@ -1397,7 +1638,6 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@@ -1478,6 +1718,11 @@
|
||||
"through": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@@ -1993,11 +2238,21 @@
|
||||
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
|
||||
"dev": true
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
|
||||
"integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ=="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"merge-source-map": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
|
||||
@@ -2015,6 +2270,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.42.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
|
||||
@@ -2151,6 +2416,11 @@
|
||||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
|
||||
"dev": true
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
|
||||
@@ -2281,6 +2551,14 @@
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -2450,6 +2728,11 @@
|
||||
"protocols": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
@@ -2474,6 +2757,11 @@
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"path-type": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
|
||||
@@ -2595,6 +2883,15 @@
|
||||
"resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz",
|
||||
"integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
|
||||
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.0"
|
||||
}
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
@@ -2620,6 +2917,22 @@
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-3.0.3.tgz",
|
||||
"integrity": "sha512-dy1yjycmn9blucmJLXOfZDx1ikZJUi6E8bBZLnhPG5gBrVhHXx2xVyqqgKBubVNEXmx51dBACMHpoMQK/N/AXQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"re-emitter": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.4.tgz",
|
||||
@@ -2865,17 +3178,75 @@
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
|
||||
"dev": true
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
@@ -3051,6 +3422,11 @@
|
||||
"integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==",
|
||||
"dev": true
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
@@ -4696,6 +5072,11 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
@@ -4771,6 +5152,15 @@
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||
"dev": true
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
@@ -4839,6 +5229,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
@@ -4879,6 +5274,11 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-fsmrf": "^1.5.12",
|
||||
"drachtio-srf": "^4.4.27",
|
||||
"express": "^4.17.1",
|
||||
"jambonz-db-helpers": "^0.2.0",
|
||||
"moment": "^2.24.0",
|
||||
"parse-url": "^5.0.1",
|
||||
|
||||
Reference in New Issue
Block a user