Files
jambonz-api-server/lib/routes/api/sms-inbound.js
Dave Horton ed51d8b13f merge of features from hosted branch (#7)
major merge of features from the hosted branch that was created temporarily during the initial launch of jambonz.org
2021-06-17 15:56:21 -04:00

143 lines
4.4 KiB
JavaScript

const router = require('express').Router();
const request = require('request');
const getProvider = require('../../utils/sms-provider');
const uuidv4 = require('uuid/v4');
const sysError = require('../error');
let idx = 0;
async function doSendResponse(res, respondFn, body) {
if (typeof respondFn === 'number') res.sendStatus(respondFn);
else if (typeof respondFn !== 'function') res.sendStatus(200);
else {
const payload = await respondFn(body);
res.status(200).json(payload);
}
}
router.post('/:provider', async(req, res) => {
const provider = req.params.provider;
const {
retrieveSet,
lookupAppByPhoneNumber,
logger
} = req.app.locals;
const setName = `${process.env.JAMBONES_CLUSTER_ID || 'default'}:active-fs`;
logger.debug({path: req.path, body: req.body}, 'incomingSMS from carrier');
// search for provider module
const arr = getProvider(logger, provider);
if (!arr) {
logger.info({body: req.body, params: req.params},
`rejecting incomingSms request from unknown provider ${provider}`
);
return res.sendStatus(404);
}
const providerData = arr[1];
if (!providerData || !providerData.module) {
logger.info({body: req.body, params: req.params},
`rejecting incomingSms request from badly configured provider ${provider}`
);
return res.sendStatus(404);
}
// load provider module
let filterFn, respondFn;
try {
const {
fromProviderFormat,
formatProviderResponse
} = require(providerData.module);
// must at least provide a filter function
if (!fromProviderFormat) {
logger.info(
`missing fromProviderFormat function in module ${providerData.module} for provider ${provider}`
);
return res.sendStatus(404);
}
filterFn = fromProviderFormat;
respondFn = formatProviderResponse;
} catch (err) {
logger.info(
err,
`failure loading module ${providerData.module} for provider ${provider}`
);
return res.sendStatus(500);
}
try {
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return res
.json({
msg: 'no available feature servers at this time'
})
.status(480);
}
const ip = fs[idx++ % fs.length];
const serviceUrl = `http://${ip}:3000/v1/messaging/${provider}`;
const messageSid = uuidv4();
const payload = await Promise.resolve(filterFn({messageSid}, req.body));
/**
* lookup the application associated with the number in the To field
* since there could be multiple Tos, we have to search through (and cc also)
*/
let app;
const to = Array.isArray(payload.to) ? payload.to : [payload.to];
const cc = Array.isArray(payload.cc) ? payload.cc : (payload.cc ? [payload.cc] : []);
const dids = to.concat(cc).filter((n) => n.length);
for (let did of dids) {
const regex = /^\+(\d+)$/;
const arr = regex.exec(did);
did = arr ? arr[1] : did;
const obj = await lookupAppByPhoneNumber(did);
logger.info({obj}, `lookup app for phone number ${did}`);
if (obj) {
logger.info({did, obj}, 'Found app for DID');
app = obj;
break;
}
}
if (!app) {
logger.info({payload}, 'No application found for incoming SMS');
return res.sendStatus(404);
}
if (!app.messaging_hook) {
logger.info({payload}, `app "${app.name}" found for incoming SMS does not have an associated messaging hook`);
return res.sendStatus(404);
}
payload.app = app;
logger.debug({body: req.body, payload}, 'filtered incoming SMS');
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${ip}`);
request({
url: serviceUrl,
method: 'POST',
json: true,
body: payload,
},
async(err, response, body) => {
if (err) {
logger.error(err, `Error sending incomingSms POST to ${ip}`);
return res.sendStatus(500);
}
if (200 === response.statusCode) {
// success
logger.info({body}, 'sending response to provider for incomingSMS');
return doSendResponse(res, respondFn, body);
}
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${ip}`);
return res.sendStatus(500);
});
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;