diff --git a/app.js b/app.js index ac7f856..0c11259 100644 --- a/app.js +++ b/app.js @@ -70,7 +70,7 @@ app.use(express.urlencoded({ extended: true })); app.use(express.json()); -app.use('/v1', unless(['/login', '/Users', '/messaging'], passport.authenticate('bearer', { +app.use('/v1', unless(['/login', '/Users', '/messaging', '/outboundSMS'], passport.authenticate('bearer', { session: false }))); app.use('/', routes); diff --git a/lib/routes/api/index.js b/lib/routes/api/index.js index 715ae93..639a003 100644 --- a/lib/routes/api/index.js +++ b/lib/routes/api/index.js @@ -20,6 +20,7 @@ api.use('/Sbcs', isAdminScope, require('./sbcs')); api.use('/Users', require('./users')); api.use('/login', require('./login')); -api.use('/messaging', require('./messaging')); +api.use('/messaging', require('./sms-inbound')); +api.use('/outboundSMS', require('./sms-outbound')); module.exports = api; diff --git a/lib/routes/api/messaging.js b/lib/routes/api/sms-inbound.js similarity index 79% rename from lib/routes/api/messaging.js rename to lib/routes/api/sms-inbound.js index 54da629..d39b7cd 100644 --- a/lib/routes/api/messaging.js +++ b/lib/routes/api/sms-inbound.js @@ -1,30 +1,10 @@ const router = require('express').Router(); const request = require('request'); +const smsPartnerFn = require('../../utils/sms-partner'); const uuidv4 = require('uuid/v4'); const sysError = require('./error'); -const partners = {}; -let initialized = false; let idx = 0; -function initPartners(logger) { - if (initialized) return; - initialized = true; - - if (process.env.JAMBONES_MESSAGING) { - try { - const obj = JSON.parse(process.env.JAMBONES_MESSAGING); - Object.assign(partners, obj); - logger.info({ - partners - }, 'Messaging partners configuration'); - } catch (err) { - logger.error(err, `expected JSON for JAMBONES_MESSAGING : ${process.env.JAMBONES_MESSAGING}`); - } - } - else { - logger.info('no JAMBONES_MESSAGING env var, messaging is disabled'); - } -} async function doSendResponse(res, respondFn, body) { if (typeof respondFn === 'number') res.sendStatus(respondFn); @@ -36,21 +16,28 @@ async function doSendResponse(res, respondFn, body) { } router.post('/:partner', async(req, res) => { - const partner = req.params.partner; + const partnerName = req.params.partner; const { retrieveSet, lookupAppByPhoneNumber, logger } = req.app.locals; const setName = `${process.env.JAMBONES_CLUSTER_ID || 'default'}:active-fs`; + const getPartner = smsPartnerFn(logger); // search for partner module - initPartners(logger); - - const partnerData = partners[partner]; - if (!partnerData || !partnerData.module || !partnerData.options) { + const arr = getPartner(partnerName); + if (!arr) { logger.info( - `rejecting incomingSms request from unknown or badly configured partner ${partner}` + `rejecting incomingSms request from unknown or badly configured partner ${partnerName}` + ); + return res.sendStatus(404); + } + + const partnerData = arr[1]; + if (!partnerData || !partnerData.module) { + logger.info( + `rejecting incomingSms request from unknown or badly configured partner ${partnerName}` ); return res.sendStatus(404); } @@ -65,7 +52,7 @@ router.post('/:partner', async(req, res) => { // must at least provide a filter function if (!fromProviderFormat) { logger.info( - `missing fromProviderFormat function in module ${partnerData.module} for partner ${partner}` + `missing fromProviderFormat function in module ${partnerData.module} for partner ${partnerName}` ); return res.sendStatus(404); } @@ -74,7 +61,7 @@ router.post('/:partner', async(req, res) => { } catch (err) { logger.info( err, - `failure loading module ${partnerData.module} for partner ${partner}` + `failure loading module ${partnerData.module} for partner ${partnerName}` ); return res.sendStatus(500); } @@ -90,7 +77,7 @@ router.post('/:partner', async(req, res) => { .status(480); } const ip = fs[idx++ % fs.length]; - const serviceUrl = `http://${ip}:3000/v1/messaging/${partner}`; + const serviceUrl = `http://${ip}:3000/v1/messaging/${partnerName}`; const messageSid = uuidv4(); const payload = await Promise.resolve(filterFn({messageSid}, req.body)); @@ -120,10 +107,7 @@ router.post('/:partner', async(req, res) => { logger.info({payload}, `app "${app.name}" found for incoming SMS does not have an associated messaging hook`); return res.sendStatus(404); } - - payload.applicationSid = app.application_sid; - payload.accountSid = app.account_sid; - payload.messaging_hook = app.messaging_hook; + payload.app = app; logger.debug({body: req.body, payload}, 'filtered incoming SMS'); diff --git a/lib/routes/api/sms-outbound.js b/lib/routes/api/sms-outbound.js new file mode 100644 index 0000000..54963cc --- /dev/null +++ b/lib/routes/api/sms-outbound.js @@ -0,0 +1,42 @@ +const router = require('express').Router(); +const smsPartnerFn = require('../../utils/sms-partner'); +const sysError = require('./error'); + +router.post('/', async(req, res) => { + const { logger } = req.app.locals; + const getPartner = smsPartnerFn(logger); + + try { + // if partner specified use it, otherwise use first in list + const arr = getPartner(req.body.partner); + if (!Array.isArray(arr)) { + throw new Error('outboundSMS - unable to locate sms provider to use to send message'); + } + + const partnerData = arr[1]; + if (!partnerData || !partnerData.module) { + throw new Error(`rejecting outgoingSms request for unknown or badly configured partner ${req.body.partner}`); + } + + const partnerName = arr[0]; + const opts = partnerData.options; + if (!opts || !opts.url) { + throw new Error(`rejecting outgoingSms request -- no HTTP url for ${req.body.partner}`); + } + + // load partner module + const { sendSms } = require(partnerData.module); + if (!sendSms) { + throw new Error(`missing sendSms function in module ${partnerData.module} for partner ${partnerName}`); + } + + // send the SMS + const response = await sendSms(opts, req.body); + logger.info({response, payload: req.body}, `outboundSMS - sent to ${opts.url}`); + res.status(200).json({'status': 'ok'}); + } catch (err) { + sysError(logger, res, err); + } +}); + +module.exports = router; diff --git a/lib/utils/sms-partner.js b/lib/utils/sms-partner.js new file mode 100644 index 0000000..c938f3a --- /dev/null +++ b/lib/utils/sms-partner.js @@ -0,0 +1,38 @@ +const partners = new Map(); + +function initPartners(logger) { + if (process.env.JAMBONES_MESSAGING) { + try { + const obj = JSON.parse(process.env.JAMBONES_MESSAGING); + for (const [key, value] of Object.entries(obj)) { + logger.debug({config: value}, `Adding SMS provider ${key}`); + partners.set(key, value); + } + logger.info(`Configured ${partners.size} SMS partners`); + } catch (err) { + logger.error(err, `expected JSON for JAMBONES_MESSAGING : ${process.env.JAMBONES_MESSAGING}`); + } + } + else { + logger.info('no JAMBONES_MESSAGING env var, messaging is disabled'); + } +} + +function getPartner(logger, partner) { + if (typeof partner === 'string') { + const config = partners.get(partner); + const arr = [partner, config]; + logger.debug({arr}, 'getPartner by name'); + return arr; + } + else if (partners.size) { + const arr = partners.entries().next().value; + logger.debug({arr}, 'getPartner by first available'); + return arr; + } +} + +module.exports = (logger) => { + initPartners(logger); + return getPartner.bind(null, logger); +}; diff --git a/package.json b/package.json index f82d29d..1a170a5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@jambonz/db-helpers": "^0.5.0", - "@jambonz/messaging-peerless": "0.0.4", + "@jambonz/messaging-peerless": "0.0.5", "@jambonz/realtimedb-helpers": "0.2.16", "cors": "^2.8.5", "express": "^4.17.1",