added initial support for REST-initiated outdials

This commit is contained in:
Dave Horton
2020-02-01 16:16:00 -05:00
parent 44a1b45357
commit 2525b8c70a
28 changed files with 985 additions and 127 deletions

View File

@@ -0,0 +1,155 @@
const config = require('config');
const router = require('express').Router();
const sysError = require('./error');
const makeTask = require('../../tasks/make_task');
const RestCallSession = require('../../session/rest-call-session');
const CallInfo = require('../../session/call-info');
const {CallDirection} = require('../../utils/constants');
const parseUrl = require('parse-url');
const SipError = require('drachtio-srf').SipError;
const Srf = require('drachtio-srf');
const drachtio = config.get('outdials.drachtio');
const sbcs = config.get('outdials.sbc');
const Mrf = require('drachtio-fsmrf');
let idxDrachtio = 0;
let idxSbc = 0;
const srfs = drachtio.map((d) => {
const srf = new Srf();
srf.connect(d);
srf
.on('connect', (err, hp) => {
console.log(err, `Connected to drachtio at ${hp}`);
srf.locals.mrf = new Mrf(srf);
})
.on('error', (err) => console.log(err));
return srf;
});
async function validate(logger, payload) {
const data = Object.assign({}, {
from: payload.from,
to: payload.to,
call_hook: payload.call_hook
});
const u = parseUrl(payload.call_hook.url);
const myPort = u.port ? `:${u.port}` : '';
payload.originalRequest = {
baseUrl: `${u.protocol}://${u.resource}${myPort}`,
method: payload.call_hook.method
};
if (payload.call_hook.username && payload.call_hook.password) {
payload.originalRequest.auth = {
username: payload.call_hook.username,
password: payload.call_hook.password
};
}
return makeTask(logger, {'rest:dial': data});
}
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
logger.debug({body: req.body}, 'got createCall request');
try {
let uri;
const restDial = await validate(logger, req.body);
const sbcAddress = sbcs[idxSbc++ % sbcs.length];
const srf = srfs[idxDrachtio++ % srfs.length];
const target = restDial.to;
const opts = {
'callingNumber': restDial.from
};
switch (target.type) {
case 'phone':
uri = `sip:${target.number}@${sbcAddress}`;
break;
case 'user':
uri = `sip:${target.name}`;
break;
case 'sip':
uri = target.sipUri;
break;
}
/* create endpoint for outdial */
const mrf = srf.locals.mrf;
const ms = await mrf.connect(config.get('freeswitch'));
logger.debug('createCall: successfully connected to media server');
const ep = await ms.createEndpoint();
logger.debug(`createCall: successfully allocated endpoint, sending INVITE to ${sbcAddress}`);
ms.destroy();
/* launch outdial */
let sdp, sipLogger;
const connectStream = async(remoteSdp) => {
if (remoteSdp !== sdp) {
ep.modify(sdp = remoteSdp);
return true;
}
return false;
};
Object.assign(opts, {
proxy: `sip:${sbcAddress}`,
localSdp: ep.local.sdp
});
if (target.auth) opts.auth = this.target.auth;
const application = req.body;
try {
const dlg = await srf.createUAC(uri, opts, {
cbRequest: (err, inviteReq) => {
if (err) {
this.logger.error(err, 'createCall Error creating call');
res.status(500).send('Call Failure');
ep.destroy();
}
/* call is in flight */
const tasks = [restDial];
const callInfo = new CallInfo({
direction: CallDirection.Outbound,
req: inviteReq,
to: req.body.to,
tag: req.body.tag,
accountSid: req.body.account_sid,
applicationSid: req.body.application_sid
});
const cs = new RestCallSession({logger, application, srf, req: inviteReq, ep, tasks, callInfo});
cs.exec(req);
res.status(201).json({sid: cs.callSid});
sipLogger = logger.child({
callSid: cs.callSid,
callId: callInfo.callId
});
sipLogger.info(`outbound REST call attempt to ${JSON.stringify(target)} has been sent`);
},
cbProvisional: (prov) => {
if ([180, 183].includes(prov.status) && prov.body) connectStream(prov.body);
restDial.emit('callStatus', prov.status, !!prov.body);
}
});
connectStream(dlg.remote.sdp);
restDial.emit('callStatus', 200);
restDial.emit('connect', dlg);
}
catch (err) {
if (err instanceof SipError) {
sipLogger.info(`REST outdial failed with ${err.status}`);
}
else {
sipLogger.error({err}, 'REST outdial failed');
}
ep.destroy();
}
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;