mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-19 04:17:44 +00:00
remove config in favor of env vars, other major changes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -36,3 +36,5 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
examples/*
|
examples/*
|
||||||
|
|
||||||
|
ecosystem.config.js
|
||||||
36
app.js
36
app.js
@@ -1,13 +1,23 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||||
|
process.env.JAMBONES_MYSQL_USER &&
|
||||||
|
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||||
|
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||||
|
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
|
||||||
|
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
||||||
|
assert.ok(process.env.JAMBONES_SBCS, 'missing JAMBONES_SBCS env var');
|
||||||
|
assert.ok(process.env.JAMBONES_FREESWITCH, 'missing JAMBONES_FREESWITCH env var');
|
||||||
|
assert.ok(process.env.JAMBONES_FEATURE_SERVERS, 'missing JAMBONES_FEATURE_SERVERS env var');
|
||||||
|
|
||||||
const Srf = require('drachtio-srf');
|
const Srf = require('drachtio-srf');
|
||||||
const srf = new Srf();
|
const srf = new Srf();
|
||||||
const Mrf = require('drachtio-fsmrf');
|
const Mrf = require('drachtio-fsmrf');
|
||||||
srf.locals.mrf = new Mrf(srf);
|
srf.locals.mrf = new Mrf(srf);
|
||||||
const config = require('config');
|
const PORT = process.env.HTTP_PORT || 3000;
|
||||||
const PORT = process.env.HTTP_PORT || config.get('defaultHttpPort');
|
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
||||||
}, config.get('logging'));
|
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
||||||
const logger = srf.locals.parentLogger = require('pino')(opts);
|
const logger = require('pino')(opts);
|
||||||
const installSrfLocals = require('./lib/utils/install-srf-locals');
|
const installSrfLocals = require('./lib/utils/install-srf-locals');
|
||||||
installSrfLocals(srf, logger);
|
installSrfLocals(srf, logger);
|
||||||
|
|
||||||
@@ -18,7 +28,6 @@ const {
|
|||||||
invokeWebCallback
|
invokeWebCallback
|
||||||
} = require('./lib/middleware')(srf, logger);
|
} = require('./lib/middleware')(srf, logger);
|
||||||
|
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -27,24 +36,15 @@ const httpRoutes = require('./lib/http-routes');
|
|||||||
|
|
||||||
const InboundCallSession = require('./lib/session/inbound-call-session');
|
const InboundCallSession = require('./lib/session/inbound-call-session');
|
||||||
|
|
||||||
|
if (process.env.DRACHTIO_HOST) {
|
||||||
// disable logging in test mode
|
srf.connect({host: process.env.DRACHTIO_HOST, port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET });
|
||||||
if (process.env.NODE_ENV === 'test') {
|
|
||||||
const noop = () => {};
|
|
||||||
logger.info = logger.debug = noop;
|
|
||||||
logger.child = () => {return {info: noop, error: noop, debug: noop};};
|
|
||||||
}
|
|
||||||
|
|
||||||
// config dictates whether to use outbound or inbound connections
|
|
||||||
if (config.has('drachtio.host')) {
|
|
||||||
srf.connect(config.get('drachtio'));
|
|
||||||
srf.on('connect', (err, hp) => {
|
srf.on('connect', (err, hp) => {
|
||||||
logger.info(`connected to drachtio listening on ${hp}`);
|
logger.info(`connected to drachtio listening on ${hp}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.info(`listening for drachtio server traffic on ${JSON.stringify(config.get('drachtio'))}`);
|
logger.info(`listening for drachtio requests on port ${process.env.DRACHTIO_PORT}`);
|
||||||
srf.listen(config.get('drachtio'));
|
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
srf.on('error', (err) => {
|
srf.on('error', (err) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const config = require('config');
|
|
||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const makeTask = require('../../tasks/make_task');
|
const makeTask = require('../../tasks/make_task');
|
||||||
const RestCallSession = require('../../session/rest-call-session');
|
const RestCallSession = require('../../session/rest-call-session');
|
||||||
@@ -7,12 +6,9 @@ const {CallDirection, CallStatus} = require('../../utils/constants');
|
|||||||
const SipError = require('drachtio-srf').SipError;
|
const SipError = require('drachtio-srf').SipError;
|
||||||
const Srf = require('drachtio-srf');
|
const Srf = require('drachtio-srf');
|
||||||
const sysError = require('./error');
|
const sysError = require('./error');
|
||||||
const drachtio = config.get('outdials.drachtio');
|
|
||||||
const sbcs = config.get('outdials.sbc');
|
|
||||||
const Mrf = require('drachtio-fsmrf');
|
const Mrf = require('drachtio-fsmrf');
|
||||||
const installSrfLocals = require('../../utils/install-srf-locals');
|
const installSrfLocals = require('../../utils/install-srf-locals');
|
||||||
const Requestor = require('../../utils/requestor');
|
const Requestor = require('../../utils/requestor');
|
||||||
|
|
||||||
let idxDrachtio = 0;
|
let idxDrachtio = 0;
|
||||||
let idxSbc = 0;
|
let idxSbc = 0;
|
||||||
let srfs = [];
|
let srfs = [];
|
||||||
@@ -51,6 +47,8 @@ function getSrfForOutdial(logger) {
|
|||||||
if (srfs.length === 0 && initializedSrfs) return reject('no available drachtio servers for outdial');
|
if (srfs.length === 0 && initializedSrfs) return reject('no available drachtio servers for outdial');
|
||||||
else if (srfs.length > 0) return resolve(srfs[idxDrachtio++ % srfs.length]);
|
else if (srfs.length > 0) return resolve(srfs[idxDrachtio++ % srfs.length]);
|
||||||
else {
|
else {
|
||||||
|
const {srf} = require('../../../');
|
||||||
|
const drachtio = srf.locals.drachtio;
|
||||||
logger.debug(drachtio, 'getSrfForOutdial - attempting to connect');
|
logger.debug(drachtio, 'getSrfForOutdial - attempting to connect');
|
||||||
initializedSrfs = true;
|
initializedSrfs = true;
|
||||||
resolve(Promise.race(drachtio.map((d) => connectSrf(logger, d))));
|
resolve(Promise.race(drachtio.map((d) => connectSrf(logger, d))));
|
||||||
@@ -64,8 +62,8 @@ router.post('/', async(req, res) => {
|
|||||||
try {
|
try {
|
||||||
let uri, cs, to;
|
let uri, cs, to;
|
||||||
const restDial = makeTask(logger, {'rest:dial': req.body});
|
const restDial = makeTask(logger, {'rest:dial': req.body});
|
||||||
const sbcAddress = sbcs[idxSbc++ % sbcs.length];
|
|
||||||
const srf = await getSrfForOutdial(logger);
|
const srf = await getSrfForOutdial(logger);
|
||||||
|
const sbcAddress = srf.locals.sbcs[idxSbc++ % srf.locals.sbcs.length];
|
||||||
const target = restDial.to;
|
const target = restDial.to;
|
||||||
const opts = { callingNumber: restDial.from };
|
const opts = { callingNumber: restDial.from };
|
||||||
|
|
||||||
@@ -86,7 +84,8 @@ router.post('/', async(req, res) => {
|
|||||||
|
|
||||||
/* create endpoint for outdial */
|
/* create endpoint for outdial */
|
||||||
const mrf = srf.locals.mrf;
|
const mrf = srf.locals.mrf;
|
||||||
const ms = await mrf.connect(config.get('freeswitch'));
|
|
||||||
|
const ms = await mrf.connect(srf.locals.freeswitch);
|
||||||
logger.debug('createCall: successfully connected to media server');
|
logger.debug('createCall: successfully connected to media server');
|
||||||
const ep = await ms.createEndpoint();
|
const ep = await ms.createEndpoint();
|
||||||
logger.debug(`createCall: successfully allocated endpoint, sending INVITE to ${sbcAddress}`);
|
logger.debug(`createCall: successfully allocated endpoint, sending INVITE to ${sbcAddress}`);
|
||||||
@@ -119,8 +118,8 @@ router.post('/', async(req, res) => {
|
|||||||
* attach our requestor and notifier objects
|
* attach our requestor and notifier objects
|
||||||
* these will be used for all http requests we make during this call
|
* these will be used for all http requests we make during this call
|
||||||
*/
|
*/
|
||||||
app.requestor = new Requestor(this.logger, app.call_hook);
|
app.requestor = new Requestor(logger, app.call_hook);
|
||||||
if (app.call_status_hook) app.notifier = new Requestor(this.logger, app.call_status_hook);
|
if (app.call_status_hook) app.notifier = new Requestor(logger, app.call_status_hook);
|
||||||
else app.notifier = {request: () => {}};
|
else app.notifier = {request: () => {}};
|
||||||
|
|
||||||
/* now launch the outdial */
|
/* now launch the outdial */
|
||||||
@@ -128,7 +127,7 @@ router.post('/', async(req, res) => {
|
|||||||
const dlg = await srf.createUAC(uri, opts, {
|
const dlg = await srf.createUAC(uri, opts, {
|
||||||
cbRequest: (err, inviteReq) => {
|
cbRequest: (err, inviteReq) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.logger.error(err, 'createCall Error creating call');
|
logger.error(err, 'createCall Error creating call');
|
||||||
res.status(500).send('Call Failure');
|
res.status(500).send('Call Failure');
|
||||||
ep.destroy();
|
ep.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const makeTask = require('./tasks/make_task');
|
|||||||
const normalizeJamones = require('./utils/normalize-jamones');
|
const normalizeJamones = require('./utils/normalize-jamones');
|
||||||
|
|
||||||
module.exports = function(srf, logger) {
|
module.exports = function(srf, logger) {
|
||||||
const {lookupAppByPhoneNumber, lookupApplicationBySid} = srf.locals.dbHelpers;
|
const {lookupAppByPhoneNumber, lookupAppBySid, lookupAppByRealm} = srf.locals.dbHelpers;
|
||||||
|
|
||||||
function initLocals(req, res, next) {
|
function initLocals(req, res, next) {
|
||||||
const callSid = uuidv4();
|
const callSid = uuidv4();
|
||||||
@@ -19,6 +19,8 @@ module.exports = function(srf, logger) {
|
|||||||
req.locals.logger.debug(`got application from X-Application-Sid header: ${application_sid}`);
|
req.locals.logger.debug(`got application from X-Application-Sid header: ${application_sid}`);
|
||||||
req.locals.application_sid = application_sid;
|
req.locals.application_sid = application_sid;
|
||||||
}
|
}
|
||||||
|
if (req.has('X-Authenticated-User')) req.locals.originatingUser = req.get('X-Authenticated-User');
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +52,17 @@ module.exports = function(srf, logger) {
|
|||||||
const logger = req.locals.logger;
|
const logger = req.locals.logger;
|
||||||
try {
|
try {
|
||||||
let app;
|
let app;
|
||||||
if (req.locals.application_sid) app = await lookupApplicationBySid(req.locals.application_sid);
|
if (req.locals.application_sid) app = await lookupAppBySid(req.locals.application_sid);
|
||||||
|
else if (req.locals.originatingUser) {
|
||||||
|
const arr = /^(.*)@(.*)/.exec(req.locals.originatingUser);
|
||||||
|
if (arr) {
|
||||||
|
const sipRealm = arr[2];
|
||||||
|
logger.debug(`looking for device calling app for realm ${sipRealm}`);
|
||||||
|
app = await lookupAppByRealm(sipRealm);
|
||||||
|
if (app) logger.debug({app}, `retrieved device calling app for realm ${sipRealm}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
else app = await lookupAppByPhoneNumber(req.locals.calledNumber);
|
else app = await lookupAppByPhoneNumber(req.locals.calledNumber);
|
||||||
|
|
||||||
if (!app || !app.call_hook || !app.call_hook.url) {
|
if (!app || !app.call_hook || !app.call_hook.url) {
|
||||||
@@ -66,8 +78,8 @@ module.exports = function(srf, logger) {
|
|||||||
* create a requestor that we will use for all http requests we make during the call.
|
* create a requestor that we will use for all http requests we make during the call.
|
||||||
* also create a notifier for call status events (if not needed, its a no-op).
|
* also create a notifier for call status events (if not needed, its a no-op).
|
||||||
*/
|
*/
|
||||||
app.requestor = new Requestor(this.logger, app.call_hook);
|
app.requestor = new Requestor(logger, app.call_hook);
|
||||||
if (app.call_status_hook) app.notifier = new Requestor(this.logger, app.call_status_hook);
|
if (app.call_status_hook) app.notifier = new Requestor(logger, app.call_status_hook);
|
||||||
else app.notifier = {request: () => {}};
|
else app.notifier = {request: () => {}};
|
||||||
|
|
||||||
req.locals.application = app;
|
req.locals.application = app;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const config = require('config');
|
|
||||||
const {CallDirection, TaskPreconditions, CallStatus, TaskName} = require('../utils/constants');
|
const {CallDirection, TaskPreconditions, CallStatus, TaskName} = require('../utils/constants');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
@@ -227,13 +226,11 @@ class CallSession extends Emitter {
|
|||||||
* @param {object} [opts.call_hook] - new call_status_hook
|
* @param {object} [opts.call_hook] - new call_status_hook
|
||||||
*/
|
*/
|
||||||
async _lccCallHook(opts) {
|
async _lccCallHook(opts) {
|
||||||
const tasks = await this.requestor(opts.call_hook, this.callInfo);
|
const tasks = await this.requestor.request(opts.call_hook, this.callInfo);
|
||||||
|
if (tasks && tasks.length > 0) {
|
||||||
//TODO: if they gave us a call status hook, we should replace
|
this.logger.info({tasks}, 'CallSession:updateCall new task list');
|
||||||
//the existing one (or just remove this option altogether?)
|
this.replaceApplication(normalizeJamones(this.logger, tasks).map((tdata) => makeTask(this.logger, tdata)));
|
||||||
|
}
|
||||||
this.logger.info({tasks}, 'CallSession:updateCall new task list');
|
|
||||||
this.replaceApplication(tasks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -488,7 +485,7 @@ class CallSession extends Emitter {
|
|||||||
async getMS() {
|
async getMS() {
|
||||||
if (!this.ms) {
|
if (!this.ms) {
|
||||||
const mrf = this.srf.locals.mrf;
|
const mrf = this.srf.locals.mrf;
|
||||||
this.ms = await mrf.connect(config.get('freeswitch'));
|
this.ms = await mrf.connect(this.srf.locals.freeswitch);
|
||||||
}
|
}
|
||||||
return this.ms;
|
return this.ms;
|
||||||
}
|
}
|
||||||
@@ -503,7 +500,7 @@ class CallSession extends Emitter {
|
|||||||
|
|
||||||
// get a media server
|
// get a media server
|
||||||
if (!this.ms) {
|
if (!this.ms) {
|
||||||
this.ms = await mrf.connect(config.get('freeswitch'));
|
this.ms = await mrf.connect(this.srf.locals.freeswitch);
|
||||||
}
|
}
|
||||||
if (!this.ep) {
|
if (!this.ep) {
|
||||||
this.ep = await this.ms.createEndpoint({remoteSdp: this.req.body});
|
this.ep = await this.ms.createEndpoint({remoteSdp: this.req.body});
|
||||||
@@ -534,6 +531,7 @@ class CallSession extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update calls db
|
// update calls db
|
||||||
|
this.logger.debug(`updating redis with ${JSON.stringify(this.callInfo)}`);
|
||||||
this.updateCallStatus(this.callInfo, this.serviceUrl).catch((err) => this.logger.error(err, 'redis error'));
|
this.updateCallStatus(this.callInfo, this.serviceUrl).catch((err) => this.logger.error(err, 'redis error'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const assert = require('assert');
|
|||||||
const placeCall = require('../utils/place-outdial');
|
const placeCall = require('../utils/place-outdial');
|
||||||
const sessionTracker = require('../session/session-tracker');
|
const sessionTracker = require('../session/session-tracker');
|
||||||
const DtmfCollector = require('../utils/dtmf-collector');
|
const DtmfCollector = require('../utils/dtmf-collector');
|
||||||
const config = require('config');
|
|
||||||
const debug = require('debug')('jambonz:feature-server');
|
const debug = require('debug')('jambonz:feature-server');
|
||||||
|
|
||||||
function parseDtmfOptions(logger, dtmfCapture) {
|
function parseDtmfOptions(logger, dtmfCapture) {
|
||||||
@@ -129,11 +128,11 @@ class TaskDial extends Task {
|
|||||||
this._installDtmfDetection(cs, this.epOther, this.parentDtmfCollector);
|
this._installDtmfDetection(cs, this.epOther, this.parentDtmfCollector);
|
||||||
await this._attemptCalls(cs);
|
await this._attemptCalls(cs);
|
||||||
await this.awaitTaskDone();
|
await this.awaitTaskDone();
|
||||||
await cs.requestor.request(this.actionHook, Object.assign({}, cs.callInfo, this.results));
|
await this.performAction(Object.assign({}, cs.callInfo, this.results));
|
||||||
this._removeDtmfDetection(cs, this.epOther);
|
this._removeDtmfDetection(cs, this.epOther);
|
||||||
this._removeDtmfDetection(cs, this.ep);
|
this._removeDtmfDetection(cs, this.ep);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(`TaskDial:exec terminating with error ${err.message}`);
|
this.logger.error({err}, 'TaskDial:exec terminating with error');
|
||||||
this.kill();
|
this.kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +146,7 @@ class TaskDial extends Task {
|
|||||||
this.sd.kill();
|
this.sd.kill();
|
||||||
this.sd = null;
|
this.sd = null;
|
||||||
}
|
}
|
||||||
sessionTracker.remove(this.callSid);
|
if (this.callSid) sessionTracker.remove(this.callSid);
|
||||||
if (this.listenTask) await this.listenTask.kill();
|
if (this.listenTask) await this.listenTask.kill();
|
||||||
if (this.transcribeTask) await this.transcribeTask.kill();
|
if (this.transcribeTask) await this.transcribeTask.kill();
|
||||||
if (this.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
|
if (this.timerMaxCallDuration) clearTimeout(this.timerMaxCallDuration);
|
||||||
@@ -242,7 +241,7 @@ class TaskDial extends Task {
|
|||||||
|
|
||||||
const sbcAddress = cs.direction === CallDirection.Inbound ?
|
const sbcAddress = cs.direction === CallDirection.Inbound ?
|
||||||
`${req.source_address}:${req.source_port}` :
|
`${req.source_address}:${req.source_port}` :
|
||||||
config.get('outdials.sbc');
|
srf.locals.sbcs[0];
|
||||||
const opts = {
|
const opts = {
|
||||||
headers: req && req.has('X-CID') ? Object.assign(this.headers, {'X-CID': req.get('X-CID')}) : this.headers,
|
headers: req && req.has('X-CID') ? Object.assign(this.headers, {'X-CID': req.get('X-CID')}) : this.headers,
|
||||||
proxy: `sip:${sbcAddress}`,
|
proxy: `sip:${sbcAddress}`,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const Task = require('./task');
|
const Task = require('./task');
|
||||||
const {TaskName} = require('../utils/constants');
|
const {TaskName} = require('../utils/constants');
|
||||||
|
const makeTask = require('./make_task');
|
||||||
|
const normalizeJamones = require('../utils/normalize-jamones');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages an outdial made via REST API
|
* Manages an outdial made via REST API
|
||||||
@@ -44,12 +46,17 @@ class TaskRestDial extends Task {
|
|||||||
this.req = null;
|
this.req = null;
|
||||||
const cs = this.callSession;
|
const cs = this.callSession;
|
||||||
cs.setDialog(dlg);
|
cs.setDialog(dlg);
|
||||||
const tasks = await cs.requestor.request(this.call_hook, cs.callInfo);
|
|
||||||
if (tasks && Array.isArray(tasks)) {
|
try {
|
||||||
this.logger.debug({tasks: tasks}, `TaskRestDial: replacing application with ${tasks.length} tasks`);
|
const tasks = await cs.requestor.request(this.call_hook, cs.callInfo);
|
||||||
cs.replaceApplication(tasks);
|
if (tasks && Array.isArray(tasks)) {
|
||||||
|
this.logger.debug({tasks: tasks}, `TaskRestDial: replacing application with ${tasks.length} tasks`);
|
||||||
|
cs.replaceApplication(normalizeJamones(this.logger, tasks).map((tdata) => makeTask(this.logger, tdata)));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err, 'TaskRestDial:_onConnect error retrieving or parsing application, ending call');
|
||||||
|
this.notifyTaskDone();
|
||||||
}
|
}
|
||||||
this.notifyTaskDone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCallStatus(status) {
|
_onCallStatus(status) {
|
||||||
|
|||||||
@@ -116,8 +116,15 @@
|
|||||||
},
|
},
|
||||||
"rest:dial": {
|
"rest:dial": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"account_sid": "string",
|
||||||
|
"application_sid": "string",
|
||||||
"call_hook": "object|string",
|
"call_hook": "object|string",
|
||||||
|
"call_status_hook": "object|string",
|
||||||
"from": "string",
|
"from": "string",
|
||||||
|
"speech_synthesis_vendor": "string",
|
||||||
|
"speech_synthesis_voice": "string",
|
||||||
|
"speech_recognizer_vendor": "string",
|
||||||
|
"speech_recognizer_language": "string",
|
||||||
"tag": "object",
|
"tag": "object",
|
||||||
"to": "#target",
|
"to": "#target",
|
||||||
"timeout": "number"
|
"timeout": "number"
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
|
|
||||||
|
|
||||||
module.exports = (auth) => {
|
|
||||||
if (!auth || !auth.username ||
|
|
||||||
typeof auth.username !== 'string' ||
|
|
||||||
(auth.password && typeof auth.password !== 'string')) return {};
|
|
||||||
const creds = `${auth.username}:${auth.password || ''}`;
|
|
||||||
const header = `Basic ${toBase64(creds)}`;
|
|
||||||
return {Authorization: header};
|
|
||||||
};
|
|
||||||
@@ -1,26 +1,57 @@
|
|||||||
const config = require('config');
|
|
||||||
const ip = require('ip');
|
const ip = require('ip');
|
||||||
const localIp = ip.address();
|
const localIp = ip.address();
|
||||||
const PORT = process.env.HTTP_PORT || config.get('defaultHttpPort');
|
const PORT = process.env.HTTP_PORT || 3000;
|
||||||
|
|
||||||
function installSrfLocals(srf, logger) {
|
function installSrfLocals(srf, logger) {
|
||||||
if (srf.locals.dbHelpers) return;
|
if (srf.locals.dbHelpers) return;
|
||||||
|
|
||||||
|
const freeswitch = process.env.JAMBONES_FREESWITCH
|
||||||
|
.split(',')
|
||||||
|
.map((fs) => {
|
||||||
|
const arr = /^(.*):(.*):(.*)/.exec(fs);
|
||||||
|
if (arr) return {address: arr[1], port: arr[2], secret: arr[3]};
|
||||||
|
});
|
||||||
|
logger.info({freeswitch}, 'freeswitch inventory');
|
||||||
|
|
||||||
|
const sbcs = process.env.JAMBONES_SBCS
|
||||||
|
.split(',')
|
||||||
|
.map((sbc) => sbc.trim());
|
||||||
|
logger.info({sbcs}, 'SBC inventory');
|
||||||
|
|
||||||
|
const drachtio = process.env.JAMBONES_FEATURE_SERVERS
|
||||||
|
.split(',')
|
||||||
|
.map((fs) => {
|
||||||
|
const arr = /^(.*):(.*):(.*)/.exec(fs);
|
||||||
|
if (arr) return {host: arr[1], port: arr[2], secret: arr[3]};
|
||||||
|
});
|
||||||
|
logger.info({drachtio}, 'drachtio feature server inventory');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
lookupAppByPhoneNumber,
|
lookupAppByPhoneNumber,
|
||||||
lookupApplicationBySid
|
lookupAppBySid,
|
||||||
} = require('jambonz-db-helpers')(config.get('mysql'), logger);
|
lookupAppByRealm
|
||||||
|
} = require('jambonz-db-helpers')({
|
||||||
|
host: process.env.JAMBONES_MYSQL_HOST,
|
||||||
|
user: process.env.JAMBONES_MYSQL_USER,
|
||||||
|
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||||
|
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||||
|
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||||
|
}, logger);
|
||||||
const {
|
const {
|
||||||
updateCallStatus,
|
updateCallStatus,
|
||||||
retrieveCall,
|
retrieveCall,
|
||||||
listCalls,
|
listCalls,
|
||||||
deleteCall
|
deleteCall
|
||||||
} = require('jambonz-realtimedb-helpers')(config.get('redis'), logger);
|
} = require('jambonz-realtimedb-helpers')({
|
||||||
|
host: process.env.JAMBONES_REDIS_HOST,
|
||||||
|
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||||
|
}, logger);
|
||||||
|
|
||||||
Object.assign(srf.locals, {
|
Object.assign(srf.locals, {
|
||||||
dbHelpers: {
|
dbHelpers: {
|
||||||
lookupAppByPhoneNumber,
|
lookupAppByPhoneNumber,
|
||||||
lookupApplicationBySid,
|
lookupAppBySid,
|
||||||
|
lookupAppByRealm,
|
||||||
updateCallStatus,
|
updateCallStatus,
|
||||||
retrieveCall,
|
retrieveCall,
|
||||||
listCalls,
|
listCalls,
|
||||||
@@ -28,7 +59,10 @@ function installSrfLocals(srf, logger) {
|
|||||||
},
|
},
|
||||||
parentLogger: logger,
|
parentLogger: logger,
|
||||||
ipv4: localIp,
|
ipv4: localIp,
|
||||||
serviceUrl: `http://${localIp}:${PORT}`
|
serviceUrl: `http://${localIp}:${PORT}`,
|
||||||
|
freeswitch: freeswitch[0],
|
||||||
|
sbcs,
|
||||||
|
drachtio
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ class SingleDialer extends Emitter {
|
|||||||
this.callInfo.updateCallStatus(callStatus, sipStatus);
|
this.callInfo.updateCallStatus(callStatus, sipStatus);
|
||||||
if (typeof duration === 'number') this.callInfo.duration = duration;
|
if (typeof duration === 'number') this.callInfo.duration = duration;
|
||||||
try {
|
try {
|
||||||
this.notifyHook(this.application.call_status_hook);
|
this.requestor.request(this.application.call_status_hook, this.callInfo.toJSON());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.info(err, `SingleDialer:_notifyCallStatusChange error sending ${callStatus} ${sipStatus}`);
|
this.logger.info(err, `SingleDialer:_notifyCallStatusChange error sending ${callStatus} ${sipStatus}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
const bent = require('bent');
|
const bent = require('bent');
|
||||||
const parseUrl = require('parse-url');
|
const parseUrl = require('parse-url');
|
||||||
const basicAuth = require('./basic-auth');
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
|
||||||
|
|
||||||
|
function basicAuth(username, password) {
|
||||||
|
if (!username || !password) return {};
|
||||||
|
const creds = `${username}:${password || ''}`;
|
||||||
|
const header = `Basic ${toBase64(creds)}`;
|
||||||
|
return {Authorization: header};
|
||||||
|
}
|
||||||
|
|
||||||
function isRelativeUrl(u) {
|
function isRelativeUrl(u) {
|
||||||
return typeof u === 'string' && u.startsWith('/');
|
return typeof u === 'string' && u.startsWith('/');
|
||||||
}
|
}
|
||||||
@@ -14,25 +22,22 @@ function isAbsoluteUrl(u) {
|
|||||||
|
|
||||||
class Requestor {
|
class Requestor {
|
||||||
constructor(logger, hook) {
|
constructor(logger, hook) {
|
||||||
|
assert(typeof hook === 'object');
|
||||||
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.url = hook.url;
|
this.url = hook.url;
|
||||||
this.method = hook.method || 'POST';
|
this.method = hook.method || 'POST';
|
||||||
this.authHeader = basicAuth(hook.auth);
|
this.authHeader = basicAuth(hook.username, hook.password);
|
||||||
|
|
||||||
const u = parseUrl(this.url);
|
const u = parseUrl(this.url);
|
||||||
const myPort = u.port ? `:${u.port}` : '';
|
const myPort = u.port ? `:${u.port}` : '';
|
||||||
const baseUrl = `${u.protocol}://${u.resource}${myPort}`;
|
const baseUrl = `${u.protocol}://${u.resource}${myPort}`;
|
||||||
|
|
||||||
this.get = bent(baseUrl, 'GET', 'json', 200);
|
this.get = bent(baseUrl, 'GET', 'buffer', 200);
|
||||||
this.post = bent(baseUrl, 'POST', 'json', 200);
|
this.post = bent(baseUrl, 'POST', 'buffer', 200);
|
||||||
|
|
||||||
assert(isAbsoluteUrl(this.url));
|
assert(isAbsoluteUrl(this.url));
|
||||||
assert(['GET', 'POST'].includes(this.method));
|
assert(['GET', 'POST'].includes(this.method));
|
||||||
assert(!this.auth || typeof auth == 'object');
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAuth() {
|
|
||||||
return 'Authorization' in this.authHeader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,23 +47,36 @@ class Requestor {
|
|||||||
* @param {object|string} hook - may be a absolute or relative url, or an object
|
* @param {object|string} hook - may be a absolute or relative url, or an object
|
||||||
* @param {string} [hook.url] - an absolute or relative url
|
* @param {string} [hook.url] - an absolute or relative url
|
||||||
* @param {string} [hook.method] - 'GET' or 'POST'
|
* @param {string} [hook.method] - 'GET' or 'POST'
|
||||||
|
* @param {string} [hook.username] - if basic auth is protecting the endpoint
|
||||||
|
* @param {string} [hook.password] - if basic auth is protecting the endpoint
|
||||||
* @param {object} [params] - request parameters
|
* @param {object} [params] - request parameters
|
||||||
*/
|
*/
|
||||||
async request(hook, params) {
|
async request(hook, params) {
|
||||||
params = params || null;
|
params = params || null;
|
||||||
if (isRelativeUrl(hook)) {
|
const url = hook.url || hook;
|
||||||
this.logger.debug({params}, `Requestor:request relative url ${hook}`);
|
|
||||||
return await this.post(hook, params, this.authHeader);
|
|
||||||
}
|
|
||||||
const url = hook.url;
|
|
||||||
const method = hook.method || 'POST';
|
const method = hook.method || 'POST';
|
||||||
const authHeader = isRelativeUrl(url) ? this.authHeader : basicAuth(hook.auth);
|
const {username, password} = typeof hook === 'object' ? hook : {};
|
||||||
|
|
||||||
assert(url);
|
assert.ok(url, 'Requestor:request url was not provided');
|
||||||
assert(['GET', 'POST'].includes(method));
|
assert.ok, (['GET', 'POST'].includes(method), `Requestor:request method must be 'GET' or 'POST' not ${method}`);
|
||||||
return await this[method.toLowerCase()](url, params, authHeader);
|
|
||||||
|
|
||||||
|
this.logger.debug({hook}, `Requestor:request ${method} ${url}`);
|
||||||
|
const buf = isRelativeUrl(url) ?
|
||||||
|
await this.post(url, params, this.authHeader) :
|
||||||
|
await bent(method, 'buffer', 200)(url, params, basicAuth(username, password));
|
||||||
|
//this.logger.debug({body: }, `Requestor:request ${method} ${url} succeeded`);
|
||||||
|
|
||||||
|
if (buf && buf.toString().length > 0) {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(buf.toString());
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.logger.debug({err, url, method}, `Requestor:request returned non-JSON content: '${buf.toString()}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Requestor;
|
module.exports = Requestor;
|
||||||
|
|||||||
28
package-lock.json
generated
28
package-lock.json
generated
@@ -625,14 +625,6 @@
|
|||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"config": {
|
|
||||||
"version": "3.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/config/-/config-3.2.4.tgz",
|
|
||||||
"integrity": "sha512-H1XIGfnU1EAkfjSLn9ZvYDRx9lOezDViuzLDgiJ/lMeqjYe3q6iQfpcLt2NInckJgpAeekbNhQkmnnbdEDs9rw==",
|
|
||||||
"requires": {
|
|
||||||
"json5": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"content-disposition": {
|
"content-disposition": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||||
@@ -2123,18 +2115,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jambonz-db-helpers": {
|
"jambonz-db-helpers": {
|
||||||
"version": "0.2.4",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/jambonz-db-helpers/-/jambonz-db-helpers-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/jambonz-db-helpers/-/jambonz-db-helpers-0.3.2.tgz",
|
||||||
"integrity": "sha512-qfMKvXv//UDGFveOmeC3Xq2jMvTP7Y1P4F3EPf7VAgD10/ipozLRdEx+o3HlyF9wOeP3syha9ofpnel8VYLGLA==",
|
"integrity": "sha512-j7AEgts+Bj1CFPiM0estFmWmdDTZKWbkeIPY1QT3BR0cLClzjqo9fmdCzLoDtk/NWMy7IPNEQpVHzEejxFHq9g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"mysql2": "^2.0.2"
|
"mysql2": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jambonz-realtimedb-helpers": {
|
"jambonz-realtimedb-helpers": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/jambonz-realtimedb-helpers/-/jambonz-realtimedb-helpers-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/jambonz-realtimedb-helpers/-/jambonz-realtimedb-helpers-0.1.7.tgz",
|
||||||
"integrity": "sha512-5W7hRuPDCGeJfVLrweoNrfzQ7lCWy77+CcF4jqbTrbztZOK1rm0XhC1phCEUbghntmdLjTkwxpzEFxu7kyJKNQ==",
|
"integrity": "sha512-eORZH5ODz5F59P/DWUxKLAwnLY3AXkfBG/MdcvJslpXYrk0WiqbdriyCz/xujmNEcftCKVBVCMsMMWcg7YKNeg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -2238,14 +2230,6 @@
|
|||||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
|
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
|
|||||||
@@ -27,15 +27,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bent": "^7.0.6",
|
"bent": "^7.0.6",
|
||||||
"config": "^3.2.4",
|
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||||
"drachtio-fsmrf": "^1.5.14",
|
"drachtio-fsmrf": "^1.5.14",
|
||||||
"drachtio-srf": "^4.4.27",
|
"drachtio-srf": "^4.4.27",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"ip": "^1.1.5",
|
"ip": "^1.1.5",
|
||||||
"jambonz-db-helpers": "^0.2.4",
|
"jambonz-db-helpers": "^0.3.2",
|
||||||
"jambonz-realtimedb-helpers": "0.1.6",
|
"jambonz-realtimedb-helpers": "0.1.7",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"parse-url": "^5.0.1",
|
"parse-url": "^5.0.1",
|
||||||
"pino": "^5.14.0"
|
"pino": "^5.14.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user