mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 08:40:38 +00:00
added initial support for REST-initiated outdials
This commit is contained in:
155
lib/http-routes/api/create-call.js
Normal file
155
lib/http-routes/api/create-call.js
Normal 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;
|
||||
Reference in New Issue
Block a user