From 92acd505953d004a6bbc651815a72523d0b0f1ec Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Wed, 29 Jan 2020 15:27:20 -0500 Subject: [PATCH] add tag task and varioius cleanup --- lib/middleware.js | 47 ++++++++---------- lib/session/call-info.js | 67 ++++++++++++++++++++++---- lib/session/call-session.js | 40 +++++++++++----- lib/session/inbound-call-session.js | 11 +---- lib/tasks/dial.js | 2 +- lib/tasks/gather.js | 12 +++-- lib/tasks/make_task.js | 3 ++ lib/tasks/redirect.js | 1 + lib/tasks/specs.json | 8 ++++ lib/tasks/tag.js | 19 ++++++++ lib/tasks/task.js | 9 +++- lib/utils/constants.json | 1 + lib/utils/notifiers.js | 39 ++++++++++----- lib/utils/place-outdial.js | 74 +++++++++++++++++++---------- lib/utils/retrieve-app.js | 17 ++++--- package-lock.json | 38 +++++++++++++++ package.json | 1 + 17 files changed, 278 insertions(+), 111 deletions(-) create mode 100644 lib/tasks/tag.js diff --git a/lib/middleware.js b/lib/middleware.js index 1b652f1b..6c535639 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -3,6 +3,7 @@ const uuidv4 = require('uuid/v4'); const {CallStatus, CallDirection} = require('./utils/constants'); const CallInfo = require('./session/call-info'); const retrieveApp = require('./utils/retrieve-app'); +const parseUrl = require('parse-url'); module.exports = function(srf, logger) { const {lookupAppByPhoneNumber} = srf.locals.dbHelpers; @@ -44,8 +45,8 @@ module.exports = function(srf, logger) { const logger = req.locals.logger; try { const app = await lookupAppByPhoneNumber(req.locals.calledNumber); - if (!app) { - logger.info(`rejecting call to DID ${req.locals.calledNumber}: no application associated`); + if (!app || !app.call_hook || !app.call_hook.url) { + logger.info(`rejecting call to ${req.locals.calledNumber}: no application or webhook url`); return res.send(480, { headers: { 'X-Reason': 'no configured application' @@ -53,22 +54,9 @@ module.exports = function(srf, logger) { }); } - //TODO: temp hack pre-refactoring to latest db schema: bang the data into expected shape - req.locals.application = app; - //end hack logger.debug(app, `retrieved application for ${req.locals.calledNumber}`); - const from = req.getParsedHeader('From'); - req.locals.callInfo = new CallInfo({ - callSid: req.locals.callSid, - accountSid: app.account_sid, - applicationSid: app.application_sid, - from: req.callingNumber, - to: req.calledNumber, - direction: CallDirection.Inbound, - callerName: from.name || req.callingNumber, - callId: req.get('Call-ID') - }); + req.locals.callInfo = new CallInfo({req, app, direction: CallDirection.Inbound}); next(); } catch (err) { logger.error(err, `${req.get('Call-ID')} Error looking up application for ${req.calledNumber}`); @@ -82,20 +70,25 @@ module.exports = function(srf, logger) { async function invokeWebCallback(req, res, next) { const logger = req.locals.logger; const app = req.locals.application; - const method = (app.hook_http_method || 'POST').toUpperCase(); - const qs = Object.assign({}, req.locals.callInfo, { - sipStatus: 100, - callStatus: CallStatus.Trying, - originatingSipIP: req.get('X-Forwarded-For'), - originatingSipTrunkName: req.get('X-Originating-Carrier') - }); + const call_hook = app.call_hook; + const method = (call_hook.method || 'POST').toUpperCase(); let auth; - if (app.hook_basic_auth_user && app.hook_basic_auth_password) { - logger.debug(`using basic auth with ${app.hook_basic_auth_user}:${app.hook_basic_auth_password}`); - auth = Object.assign({}, {user: app.hook_basic_auth_user, password: app.hook_basic_auth_password}); + if (call_hook.username && call_hook.password) { + auth = {username: call_hook.username, password: call_hook.password}; } try { - app.tasks = await retrieveApp(logger, app.call_hook, method, auth, qs, method === 'POST' ? req.msg : null); + const u = parseUrl(call_hook.url); + const myPort = u.port ? `:${u.port}` : ''; + app.originalRequest = { + baseUrl: `${u.protocol}://${u.resource}${myPort}`, + auth + }; + logger.debug({originalRequest: app.originalRequest}, 'invokeWebCallback'); + const obj = req.locals.callInfo; + + // if the call hook is a POST add the entire SIP message to the payload + if (method === 'POST') Object.assign(obj, {sip: req.msg}); + app.tasks = await retrieveApp(logger, app.call_hook, method, auth, obj); next(); } catch (err) { logger.error(err, 'Error retrieving or parsing application'); diff --git a/lib/session/call-info.js b/lib/session/call-info.js index 42d63f1d..488d6434 100644 --- a/lib/session/call-info.js +++ b/lib/session/call-info.js @@ -1,22 +1,69 @@ +const {CallDirection, CallStatus} = require('../lib/constants'); +const uuidv4 = require('uuid/v4'); + class CallInfo { constructor(opts) { - this.callSid = opts.callSid; - this.parentCallSid = opts.parentCallSid; this.direction = opts.direction; - this.from = opts.from; - this.to = opts.to; - this.callId = opts.callId; - this.sipStatus = opts.sipStatus; - this.callStatus = opts.callStatus; - this.callerId = opts.callerId; - this.accountSid = opts.accountSid; - this.applicationSid = opts.applicationSid; + if (this.direction === CallDirection.Inbound) { + const {app, req} = opts; + this.callSid = req.locals.callSid, + this.accountSid = app.account_sid, + this.applicationSid = app.application_sid; + this.from = req.callingNumber; + this.to = req.calledNumber; + this.callerName = this.from.name || req.callingNumber; + this.callId = req.get('Call-ID'); + this.sipStatus = 100; + this.callStatus = CallStatus.Trying; + this.originatingSipIP = req.get('X-Forwarded-For'); + this.originatingSipTrunkName = req.get('X-Originating-Carrier'); + } + else if (opts.parentCallInfo instanceof CallInfo) { + const {req, parentCallInfo} = opts; + this.callSid = uuidv4(); + this.parentCallSid = parentCallInfo.callSid; + this.accountSid = parentCallInfo.accountSid; + this.applicationSid = parentCallInfo.applicationSid; + this.from = req.callingNumber; + this.to = req.calledNumber; + this.callerName = this.from.name || req.callingNumber; + this.callId = req.get('Call-ID'); + this.callStatus = CallStatus.Trying, + this.sipStatus = 100; + } } updateCallStatus(callStatus, sipStatus) { this.callStatus = callStatus; if (sipStatus) this.sipStatus = sipStatus; } + + set customerData(obj) { + this._customerData = obj; + } + + toJSON() { + const obj = { + callSid: this.callSid, + direction: this.direction, + from: this.from, + to: this.to, + callId: this.callId, + sipStatus: this.sipStatus, + callStatus: this.callStatus, + callerId: this.callId, + accountSid: this.accountSid, + applicationSid: this.applicationSid + }; + ['parentCallSid', 'originatingSipIP', 'originatingSipTrunkName'].forEach((prop) => { + if (this[prop]) obj[prop] = this[prop]; + }); + + if (this._customerData && Object.keys(this._customerData).length) { + obj.customerData = this._customerData; + } + return obj; + } } module.exports = CallInfo; diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 50702f5f..011b1c0e 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -1,24 +1,44 @@ const Emitter = require('events'); const config = require('config'); const {CallDirection, TaskPreconditions, CallStatus} = require('../utils/constants'); +const hooks = require('../utils/notifiers'); const moment = require('moment'); const assert = require('assert'); const BADPRECONDITIONS = 'preconditions not met'; class CallSession extends Emitter { - constructor({logger, application, srf, tasks, callSid}) { + constructor({logger, application, srf, tasks, callInfo}) { super(); this.logger = logger; this.application = application; this.srf = srf; - this.callSid = callSid; + this.callInfo = callInfo; this.tasks = tasks; + const {notifyHook} = hooks(this.logger, this.callInfo); + this.notifyHook = notifyHook; + this.taskIdx = 0; this.stackIdx = 0; this.callGone = false; } + get callSid() { + return this.callInfo.callSid; + } + + get originalRequest() { + return this.application.originalRequest; + } + + get direction() { + return this.callInfo.direction; + } + + get callId() { + return this.callInfo.direction; + } + async exec() { this.logger.info(`CallSession:exec starting task list with ${this.tasks.length} tasks`); while (this.tasks.length && !this.callGone) { @@ -176,19 +196,13 @@ class CallSession extends Emitter { } return {ms: this.ms, ep: this.ep}; } - _notifyCallStatusChange(callStatus) { - this.logger.debug({app: this.application}, `CallSession:_notifyCallStatusChange: ${JSON.stringify(callStatus)}`); + _notifyCallStatusChange({callStatus, sipStatus}) { + this.logger.debug(`CallSession:_notifyCallStatusChange: ${callStatus} ${sipStatus}`); + this.callInfo.updateStatus(callStatus, sipStatus); try { - const auth = {}; - if (this.application.hook_basic_auth_user && this.application.hook_basic_auth_password) { - Object.assign(auth, {user: this.application.hook_basic_auth_user, password: this.hook_basic_auth_password}); - } - this.notifyHook(this.application.call_status_hook, - this.application.hook_http_method, - auth, - callStatus); + this.notifyHook(this.application.call_status_hook); } catch (err) { - this.logger.info(err, `CallSession:_notifyCallStatusChange error sending ${JSON.stringify(callStatus)}`); + this.logger.info(err, `CallSession:_notifyCallStatusChange error sending ${callStatus} ${sipStatus}`); } } } diff --git a/lib/session/inbound-call-session.js b/lib/session/inbound-call-session.js index 3addd581..3cf0a6de 100644 --- a/lib/session/inbound-call-session.js +++ b/lib/session/inbound-call-session.js @@ -1,6 +1,5 @@ const CallSession = require('./call-session'); -const {CallDirection, CallStatus} = require('../utils/constants'); -const hooks = require('../utils/notifiers'); +const {CallStatus} = require('../utils/constants'); const moment = require('moment'); const assert = require('assert'); @@ -10,17 +9,11 @@ class InboundCallSession extends CallSession { logger: req.locals.logger, srf: req.srf, application: req.locals.application, - callSid: req.locals.callInfo.callSid, + callInfo: req.locals.callInfo, tasks: req.locals.application.tasks }); this.req = req; this.res = res; - this.srf = req.srf; - this.logger = req.locals.logger; - this.callInfo = req.locals.callInfo; - this.direction = CallDirection.Inbound; - const {notifyHook} = hooks(this.logger, this.callInfo); - this.notifyHook = notifyHook; req.on('cancel', this._callReleased.bind(this)); diff --git a/lib/tasks/dial.js b/lib/tasks/dial.js index ee6b915e..88f80304 100644 --- a/lib/tasks/dial.js +++ b/lib/tasks/dial.js @@ -145,7 +145,7 @@ class TaskDial extends Task { this.target.forEach((t) => { try { t.url = t.url || this.confirmUrl; - t.method = t.method || this.confirmMethod; + t.method = t.method || this.confirmMethod || 'POST'; const sd = placeCall({ logger: this.logger, application: cs.application, diff --git a/lib/tasks/gather.js b/lib/tasks/gather.js index aebca7b0..4ca71f8a 100644 --- a/lib/tasks/gather.js +++ b/lib/tasks/gather.js @@ -62,7 +62,7 @@ class TaskGather extends Task { this._startTranscribing(ep); } - if (this.input.includes('dtmf')) { + if (this.input.includes('digits')) { ep.on('dtmf', this._onDtmf.bind(this, ep)); } @@ -129,8 +129,14 @@ class TaskGather extends Task { } _killAudio() { - if (this.sayTask && !this.sayTask.killed) this.sayTask.kill(); - if (this.playTask && !this.playTask.killed) this.playTask.kill(); + if (this.sayTask && !this.sayTask.killed) { + this.sayTask.removeAllListeners('playDone'); + this.sayTask.kill(); + } + if (this.playTask && !this.playTask.killed) { + this.playTask.removeAllListeners('playDone'); + this.playTask.kill(); + } } _onTranscription(ep, evt) { diff --git a/lib/tasks/make_task.js b/lib/tasks/make_task.js index 03db0b2c..0c46400c 100644 --- a/lib/tasks/make_task.js +++ b/lib/tasks/make_task.js @@ -42,6 +42,9 @@ function makeTask(logger, obj) { case TaskName.Redirect: const TaskRedirect = require('./redirect'); return new TaskRedirect(logger, data); + case TaskName.Tag: + const TaskTag = require('./tag'); + return new TaskTag(logger, data); } // should never reach diff --git a/lib/tasks/redirect.js b/lib/tasks/redirect.js index a0b2da1c..a258ac9e 100644 --- a/lib/tasks/redirect.js +++ b/lib/tasks/redirect.js @@ -10,6 +10,7 @@ class TaskRedirect extends Task { this.action = this.data.action; this.method = this.data.method || 'POST'; + this.auth = this.data.auth; } get name() { return TaskName.Redirect; } diff --git a/lib/tasks/specs.json b/lib/tasks/specs.json index 2cdeb886..8c65e2ee 100644 --- a/lib/tasks/specs.json +++ b/lib/tasks/specs.json @@ -115,6 +115,14 @@ "action" ] }, + "tag": { + "properties": { + "data": "object" + }, + "required": [ + "data" + ] + }, "transcribe": { "properties": { "transcriptionCallback": "string", diff --git a/lib/tasks/tag.js b/lib/tasks/tag.js new file mode 100644 index 00000000..46b6ae56 --- /dev/null +++ b/lib/tasks/tag.js @@ -0,0 +1,19 @@ +const Task = require('./task'); +const {TaskName} = require('../utils/constants'); + +class TaskTag extends Task { + constructor(logger, opts) { + super(logger, opts); + this.data = this.data.data; + } + + get name() { return TaskName.Tag; } + + async exec(cs) { + super.exec(cs); + cs.callInfo.customerData = this.data; + this.logger.debug({customerData: cs.callInfo.customerData}, 'TaskTag:exec set customer data'); + } +} + +module.exports = TaskTag; diff --git a/lib/tasks/task.js b/lib/tasks/task.js index 2fa097c2..9ce5a4ab 100644 --- a/lib/tasks/task.js +++ b/lib/tasks/task.js @@ -58,7 +58,14 @@ class Task extends Emitter { async performAction(method, auth, results) { if (this.action) { - const tasks = await this.actionHook(this.action, method, auth, results); + 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; + } + const tasks = await this.actionHook(action, method, auth, results); if (tasks && Array.isArray(tasks)) { this.logger.debug({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`); this.callSession.replaceApplication(tasks); diff --git a/lib/utils/constants.json b/lib/utils/constants.json index 45b47f74..a70b3f2b 100644 --- a/lib/utils/constants.json +++ b/lib/utils/constants.json @@ -10,6 +10,7 @@ "SipNotify": "sip:notify", "SipRedirect": "sip:redirect", "Say": "say", + "Tag": "tag", "Transcribe": "transcribe" }, "CallStatus": { diff --git a/lib/utils/notifiers.js b/lib/utils/notifiers.js index 94f674b9..01135484 100644 --- a/lib/utils/notifiers.js +++ b/lib/utils/notifiers.js @@ -1,23 +1,36 @@ const request = require('request'); +require('request-debug')(request); const retrieveApp = require('./retrieve-app'); -function hooks(logger, callAttributes) { - 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; - if ('GET' === method.toUpperCase()) qs = params; - else body = params; - const obj = {url, method, auth: basicauth, json: expectResponse || !!body, qs, body}; - logger.debug({opts: obj}, 'actionHook'); +function hooks(logger, callInfo) { + function actionHook(hook, obj, expectResponse = true) { + const method = hook.method.toUpperCase(); + const auth = (hook.username && hook.password) ? + {username: hook.username, password: hook.password} : + null; + + const data = Object.assign({}, obj, callInfo); + if ('GET' === method) { + // remove customer data - only for POSTs since it might be quite complex + delete data.customerData; + } + const opts = { + url: hook.url, + method, + json: 'POST' === method || expectResponse + }; + if (auth) obj.auth = auth; + if ('POST' === method) obj.body = data; + else obj.qs = data; + return new Promise((resolve, reject) => { - request(obj, (err, response, body) => { + request(opts, (err, response, body) => { if (err) { - logger.info(`actionHook error ${method} ${url}: ${err.message}`); + logger.info(`actionHook error ${method} ${hook.url}: ${err.message}`); return reject(err); } if (body && expectResponse) { - logger.debug(body, `actionHook response ${method} ${url}`); + logger.debug(body, `actionHook response ${method} ${hook.url}`); return resolve(retrieveApp(logger, body)); } resolve(body); @@ -25,7 +38,7 @@ function hooks(logger, callAttributes) { }); } - function notifyHook(url, method, auth, opts) { + function notifyHook(url, method, auth, opts = {}) { return actionHook(url, method, auth, opts, false); } diff --git a/lib/utils/place-outdial.js b/lib/utils/place-outdial.js index b38f8049..de89782b 100644 --- a/lib/utils/place-outdial.js +++ b/lib/utils/place-outdial.js @@ -1,12 +1,13 @@ const Emitter = require('events'); const {CallStatus} = require('./constants'); -const uuidv4 = require('uuid/v4'); const SipError = require('drachtio-srf').SipError; -const {TaskPreconditions} = require('../utils/constants'); +const {TaskPreconditions, CallDirection} = require('../utils/constants'); +const CallInfo = require('../session/call-info'); const assert = require('assert'); const ConfirmCallSession = require('../session/confirm-call-session'); const hooks = require('./notifiers'); const moment = require('moment'); +const parseUrl = require('parse-url'); class SingleDialer extends Emitter { constructor({logger, sbcAddress, target, opts, application, callInfo}) { @@ -18,13 +19,21 @@ class SingleDialer extends Emitter { this.sbcAddress = sbcAddress; this.opts = opts; this.application = application; - this.url = opts.url; - this.method = opts.method; + this.url = target.url; + this.method = target.method; - this._callSid = uuidv4(); this.bindings = logger.bindings(); - this.callInfo = Object.assign({}, callInfo, {callSid: this._callSid}); - this.sipStatus; + + 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.on('callStatusChange', this._notifyCallStatusChange.bind(this)); @@ -86,18 +95,21 @@ class SingleDialer extends Emitter { * (a) create a logger for this call * (b) augment this.callInfo with additional call info */ + this.callInfo = new CallInfo({ + direction: CallDirection.Outbound, + parentCallInfo: this.parentCallInfo, + req + }); this.logger = srf.locals.parentLogger.child({ - callSid: this.callSid, - parentCallSid: this.bindings.callSid, - callId: req.get('Call-ID') + callSid: this.callInfo.callSid, + parentCallSid: this.parentCallInfo.callSid, + callId: this.callInfo.callId }); this.inviteInProgress = req; - const status = {callStatus: CallStatus.Trying, sipStatus: 100}; - Object.assign(this.callInfo, {callId: req.get('Call-ID'), from: req.callingNumber, to}); const {actionHook, notifyHook} = hooks(this.logger, this.callInfo); this.actionHook = actionHook; this.notifyHook = notifyHook; - this.emit('callStatusChange', status); + this.emit('callStatusChange', {callStatus: CallStatus.Trying, sipStatus: 100}); }, cbProvisional: (prov) => { const status = {sipStatus: prov.status}; @@ -168,7 +180,24 @@ class SingleDialer extends Emitter { async _executeApp(url) { this.logger.debug(`SingleDialer:_executeApp: executing ${url} after connect`); try { - const tasks = await this.actionHook(this.url, this.method); + let auth; + const app = Object.assign({}, this.application); + if (url.startsWith('/')) { + const savedUrl = url; + const or = app.originalRequest; + url = `${or.baseUrl}${url}`; + auth = or.auth; + this.logger.debug({originalUrl: savedUrl, normalizedUrl: url}, 'SingleDialer:_executeApp normalized url'); + } + else { + const u = parseUrl(url); + const myPort = u.port ? `:${u.port}` : ''; + app.originalRequest = { + baseUrl: `${u.protocol}://${u.resource}${myPort}` + }; + } + + const tasks = await this.actionHook(url, this.method, auth); const allowedTasks = tasks.filter((task) => { return [ TaskPreconditions.StableCall, @@ -180,7 +209,7 @@ class SingleDialer extends Emitter { } this.logger.debug(`SingleDialer:_executeApp: executing ${tasks.length} tasks`); - const cs = new ConfirmCallSession(this.logger, this.application, this.dlg, this.ep, tasks); + const cs = new ConfirmCallSession({logger: this.logger, application: app, dlg: this.dlg, ep: this.ep, tasks}); await cs.exec(); this.emit(this.dlg.connected ? 'accept' : 'decline'); } catch (err) { @@ -190,18 +219,13 @@ class SingleDialer extends Emitter { } } - _notifyCallStatusChange(callStatus) { + _notifyCallStatusChange({callStatus, sipStatus}) { + this.logger.debug(`SingleDialer:_notifyCallStatusChange: ${callStatus} ${sipStatus}`); + this.callInfo.updateStatus(callStatus, sipStatus); try { - const auth = {}; - if (this.application.hook_basic_auth_user && this.application.hook_basic_auth_password) { - Object.assign(auth, {user: this.application.hook_basic_auth_user, password: this.hook_basic_auth_password}); - } - this.notifyHook(this.application.call_status_hook, - this.application.hook_http_method, - auth, - callStatus); + this.notifyHook(this.application.call_status_hook); } catch (err) { - this.logger.info(err, `SingleDialer:_notifyCallStatusChange: error sending ${JSON.stringify(callStatus)}`); + this.logger.info(err, `SingleDialer:_notifyCallStatusChange error sending ${callStatus} ${sipStatus}`); } } } diff --git a/lib/utils/retrieve-app.js b/lib/utils/retrieve-app.js index 66d5a632..e387bc35 100644 --- a/lib/utils/retrieve-app.js +++ b/lib/utils/retrieve-app.js @@ -4,26 +4,25 @@ const makeTask = require('../tasks/make_task'); const normalizeJamones = require('./normalize-jamones'); -function retrieveUrl(logger, url, method, auth, qs, body) { - logger.debug(`body: ${body}`); - const opts = {url, method, auth, qs, json: true}; - if (body) { - logger.debug('adding body'); - Object.assign(opts, {body}); - } +function retrieveUrl(logger, url, method, auth, obj) { + const opts = {url, method, auth, json: true}; + if (method === 'GET') Object.assign(opts, {qs: obj}); + else Object.assign(opts, {body: 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'); resolve(body); }); }); } -async function retrieveApp(logger, url, method, auth, qs, body) { +async function retrieveApp(logger, url, method, auth, obj) { let json; if (typeof url === 'object') json = url; - else json = await retrieveUrl(logger, url, method, auth, qs, body); + else json = await retrieveUrl(logger, url, method, auth, obj); return normalizeJamones(logger, json).map((tdata) => makeTask(logger, tdata)); } diff --git a/package-lock.json b/package-lock.json index e1f07932..804e5e2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1561,6 +1561,14 @@ "has": "^1.0.1" } }, + "is-ssh": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", + "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==", + "requires": { + "protocols": "^1.1.0" + } + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -2198,6 +2206,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -2417,6 +2430,26 @@ "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", "dev": true }, + "parse-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz", + "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==", + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "parse-url": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz", + "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==", + "requires": { + "is-ssh": "^1.3.0", + "normalize-url": "^3.3.0", + "parse-path": "^4.0.0", + "protocols": "^1.4.0" + } + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -2557,6 +2590,11 @@ "react-is": "^16.8.1" } }, + "protocols": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", + "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==" + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", diff --git a/package.json b/package.json index 647035b2..be1ee89f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "drachtio-srf": "^4.4.27", "jambonz-db-helpers": "^0.1.8", "moment": "^2.24.0", + "parse-url": "^5.0.1", "pino": "^5.14.0", "request": "^2.88.0", "request-debug": "^0.2.0"