Files
jambonz-feature-server/lib/middleware.js
2020-01-07 20:57:49 -05:00

129 lines
3.9 KiB
JavaScript

const debug = require('debug')('jambonz:feature-server');
const assert = require('assert');
const request = require('request');
//require('request-debug')(request);
const uuidv4 = require('uuid/v4');
const makeTask = require('./tasks/make_task');
module.exports = function(srf, logger) {
const {lookupAppByPhoneNumber} = srf.locals.dbHelpers;
function initLocals(req, res, next) {
req.locals = req.locals || {};
req.locals.logger = logger.child({callId: req.get('Call-ID')});
next();
}
/**
* Within the system, we deal with E.164 numbers _without_ the leading '+
*/
function normalizeNumbers(req, res, next) {
const logger = req.locals.logger;
Object.assign(req.locals, {
calledNumber: req.calledNumber,
callingNumber: req.callingNumber
});
try {
const regex = /^\+(\d+)$/;
let arr = regex.exec(req.calledNumber);
if (arr) req.locals.calledNumber = arr[1];
arr = regex.exec(req.callingNumber);
if (arr) req.locals.callingNumber = arr[1];
} catch (err) {
logger.error(err, `${req.get('Call-ID')} Error performing regex`);
}
next();
}
/**
* Given the dialed DID/phone number, retrieve the application to invoke
*/
async function retrieveApplication(req, res, next) {
const logger = req.locals.logger;
try {
const app = req.locals.application = await lookupAppByPhoneNumber(req.locals.calledNumber);
if (!app) {
logger.info(`rejecting call to DID ${req.locals.calledNumber}: no application associated`);
return res.send(480, {
headers: {
'X-Reason': 'no configured application'
}
});
}
logger.debug(app, `retrieved application for ${req.locals.calledNumber}`);
next();
} catch (err) {
logger.error(err, `${req.get('Call-ID')} Error looking up application for ${req.calledNumber}`);
res.send(500);
}
}
/**
* Invoke the application callback and get the initial set of instructions
*/
async function invokeWebCallback(req, res, next) {
const logger = req.locals.logger;
const app = req.locals.application;
const call_sid = uuidv4();
const method = (app.hook_http_method || 'GET').toUpperCase();
const from = req.getParsedHeader('From');
const opts = {
url: app.call_hook,
method,
json: true,
qs: {
CallSid: call_sid,
AccountSid: app.account_sid,
From: req.callingNumber,
To: req.calledNumber,
CallStatus: 'ringing',
Direction: 'inbound',
CallerName: from.name || req.callingNumber
}
};
if (app.hook_basic_auth_user && app.hook_basic_auth_password) {
Object.assign(opts, {auth: {user: app.hook_basic_auth_user, password: app.hook_basic_auth_password}});
}
if (method === 'POST') {
Object.assign(opts, {json: true, body: req.msg});
}
try {
assert(app && app.call_hook);
request(opts, (err, response, body) => {
if (err) {
logger.error(err, `Error invoking callback ${app.call_hook}`);
return res.send(603, 'Bad webhook');
}
logger.debug(body, 'application payload');
const taskData = Array.isArray(body) ? body : [body];
app.tasks = [];
for (const t in taskData) {
try {
const task = makeTask(logger, taskData[t]);
app.tasks.push(task);
} catch (err) {
logger.info({data: taskData[t]}, `invalid web callback payload: ${err.message}`);
res.send(500, 'Application Error', {
headers: {
'X-Reason': err.message
}
});
break;
}
}
if (!res.finalResponseSent) next();
});
} catch (err) {
logger.error(err, 'Error invoking web callback');
res.send(500);
}
}
return {
initLocals,
normalizeNumbers,
retrieveApplication,
invokeWebCallback
};
};