mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 02:04:19 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
591b8f2f8a | ||
|
|
16ac56e50e | ||
|
|
9412790366 | ||
|
|
453b27d6e1 | ||
|
|
4ba3d5b2b0 | ||
|
|
bc3e2334ed | ||
|
|
d70a01578d |
+81
-21
@@ -7,11 +7,12 @@ const {SipError, stringifyUri, parseUri} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-outbound');
|
||||
|
||||
const makeInviteInProgressKey = (callid) => `sbc-out-iip${callid}`;
|
||||
const IMMUTABLE_HEADERS = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length'];
|
||||
/**
|
||||
* this is to make sure the outgoing From has the number in the incoming From
|
||||
* and not the incoming PAI
|
||||
*/
|
||||
const createBLegFromHeader = (req, teams) => {
|
||||
const createBLegFromHeader = (req, teams, register_from_domain = null) => {
|
||||
const from = req.getParsedHeader('From');
|
||||
const uri = parseUri(from.uri);
|
||||
let user = uri.user || 'anonymous';
|
||||
@@ -22,6 +23,8 @@ const createBLegFromHeader = (req, teams) => {
|
||||
else if (req.has('X-Preferred-From-User') || req.has('X-Preferred-From-Host')) {
|
||||
user = req.get('X-Preferred-From-User') || user;
|
||||
host = req.get('X-Preferred-From-Host') || host;
|
||||
} else if (register_from_domain) {
|
||||
host = register_from_domain;
|
||||
}
|
||||
if (from.name) {
|
||||
return `${from.name} <sip:${user}@${host}>`;
|
||||
@@ -100,6 +103,7 @@ class CallSession extends Emitter {
|
||||
this.lookupCarrierByAccountLcr = lookupCarrierByAccountLcr;
|
||||
|
||||
this._mediaReleased = false;
|
||||
this.recordingNoAnswerTimeout = (process.env.JAMBONES_RECORDING_NO_ANSWER_TIMEOUT || 2) * 1000;
|
||||
}
|
||||
|
||||
get service_provider_sid() {
|
||||
@@ -289,13 +293,14 @@ class CallSession extends Emitter {
|
||||
const hostport = !o.port || 5060 === o.port ? o.ipv4 : `${o.ipv4}:${o.port}`;
|
||||
const prependPlus = vc.e164_leading_plus && !this.req.calledNumber.startsWith('0') ? '+' : '';
|
||||
const transport = `transport=${protocol}`;
|
||||
const scheme = protocol === 'tls' ? 'sips' : 'sip';
|
||||
const scheme = (protocol === 'tls' && !process.env.JAMBONES_USE_BEST_EFFORT_TLS) ? 'sips' : 'sip';
|
||||
const u = `${scheme}:${prefix}${prependPlus}${calledNumber}@${hostport};${transport}`;
|
||||
const obj = {
|
||||
name: vc.name,
|
||||
diversion: vc.diversion,
|
||||
hostport,
|
||||
protocol: o.protocol
|
||||
protocol: o.protocol,
|
||||
register_from_domain: vc.register_from_domain
|
||||
};
|
||||
if (vc.register_username && vc.register_password) {
|
||||
obj.auth = {
|
||||
@@ -410,6 +415,12 @@ class CallSession extends Emitter {
|
||||
'Diversion': div
|
||||
};
|
||||
}
|
||||
if (gw.register_from_domain) {
|
||||
hdrs = {
|
||||
...hdrs,
|
||||
'From': createBLegFromHeader(this.req, teams, gw.register_from_domain)
|
||||
};
|
||||
}
|
||||
}
|
||||
else this.logger.info(`sending INVITE to ${uri} via proxy ${proxy})`);
|
||||
try {
|
||||
@@ -565,13 +576,18 @@ class CallSession extends Emitter {
|
||||
this.uas = uas;
|
||||
this.uac = uac;
|
||||
[uas, uac].forEach((dlg) => {
|
||||
dlg.on('destroy', async() => {
|
||||
dlg.on('destroy', async(bye) => {
|
||||
const other = dlg.other;
|
||||
this.rtpEngineResource.destroy();
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this.unsubscribeForDTMF();
|
||||
//this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uac.tag);
|
||||
try {
|
||||
const headers = {};
|
||||
Object.keys(bye.headers).forEach((h) => {
|
||||
if (!IMMUTABLE_HEADERS.includes(h)) headers[h] = bye.headers[h];
|
||||
});
|
||||
await other.destroy({headers});
|
||||
await other.destroy();
|
||||
} catch (err) {}
|
||||
|
||||
@@ -637,6 +653,22 @@ class CallSession extends Emitter {
|
||||
forwardInDialogRequests(uac, ['notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
_startRecordingNoAnswerTimer(res) {
|
||||
this._clearRecordingNoAnswerTimer();
|
||||
this.recordingNoAnswerTimer = setTimeout(() => {
|
||||
this.logger.info('No response from SipRec server, return error to feature server');
|
||||
this.isRecordingNoAnswerResponded = true;
|
||||
res.send(400);
|
||||
}, this.recordingNoAnswerTimeout);
|
||||
}
|
||||
|
||||
_clearRecordingNoAnswerTimer() {
|
||||
if (this.recordingNoAnswerTimer) {
|
||||
clearTimeout(this.recordingNoAnswerTimer);
|
||||
this.recordingNoAnswerTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
_stopRecording() {
|
||||
if (this.srsClients.length) {
|
||||
this.srsClients.forEach((c) => c.stop());
|
||||
@@ -750,6 +782,7 @@ Duration=${payload.duration} `
|
||||
this.logger.debug({opts, sdp: response.sdp}, 'CallSession:_onReinvite: (answer) sending back upstream');
|
||||
res.send(200, {body: response.sdp});
|
||||
} catch (err) {
|
||||
res.send(err.status || 500);
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
}
|
||||
}
|
||||
@@ -777,12 +810,12 @@ Duration=${payload.duration} `
|
||||
}
|
||||
else if (reason.includes('CallRecording')) {
|
||||
let succeeded = false;
|
||||
const headers = contentType === 'application/json' && req.body ? JSON.parse(req.body) : {};
|
||||
if (reason === 'startCallRecording') {
|
||||
const from = this.req.getParsedHeader('From');
|
||||
const to = this.req.getParsedHeader('To');
|
||||
const aorFrom = from.uri;
|
||||
const aorTo = to.uri;
|
||||
const headers = contentType === 'application/json' && req.body ? JSON.parse(req.body) : {};
|
||||
this.logger.info({to, from}, 'startCallRecording request for an outbound call');
|
||||
|
||||
const srsUrl = req.get('X-Srs-Url');
|
||||
@@ -825,11 +858,14 @@ Duration=${payload.duration} `
|
||||
headers
|
||||
}));
|
||||
try {
|
||||
succeeded = (await Promise.all(
|
||||
this.srsClients.map((c) => c.start())
|
||||
)).every((r) => r);
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.start()));
|
||||
// Only take who success accept request.
|
||||
this.srsClients = this.srsClients.filter((c) => c.activated);
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error starting SipRec call recording');
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
else if (reason === 'stopCallRecording') {
|
||||
@@ -839,11 +875,12 @@ Duration=${payload.duration} `
|
||||
return;
|
||||
}
|
||||
try {
|
||||
succeeded = (await Promise.all(
|
||||
this.srsClients.map((c) => c.stop())
|
||||
)).every((r) => r);
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.stop()));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error stopping SipRec call recording');
|
||||
succeeded = false;
|
||||
}
|
||||
this.srsClients = [];
|
||||
}
|
||||
@@ -853,9 +890,14 @@ Duration=${payload.duration} `
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
succeeded = (await Promise.all(
|
||||
this.srsClients.map((c) => c.pause())
|
||||
)).every((r) => r);
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.pause({headers})));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error pausing SipRec call recording');
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
else if (reason === 'resumeCallRecording') {
|
||||
if (!this.srsClients.length || !this.srsClients.every((c) => c.paused)) {
|
||||
@@ -863,11 +905,19 @@ Duration=${payload.duration} `
|
||||
this.logger.info('discarding invalid resumeCallRecording request');
|
||||
return;
|
||||
}
|
||||
succeeded = (await Promise.all(
|
||||
this.srsClients.map((c) => c.resume())
|
||||
)).every((r) => r);
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.resume({headers})));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error resuming SipRec call recording');
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
if (!this.isRecordingNoAnswerResponded) {
|
||||
this._clearRecordingNoAnswerTimer();
|
||||
res.send(succeeded ? 200 : 503);
|
||||
}
|
||||
res.send(succeeded ? 200 : 503);
|
||||
} else if (reason.includes('Dtmf')) {
|
||||
const arr = /Signal=\s*([0-9#*])/.exec(req.body);
|
||||
if (!arr) {
|
||||
@@ -925,10 +975,9 @@ Duration=${payload.duration} `
|
||||
}
|
||||
}
|
||||
else {
|
||||
const immutableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length'];
|
||||
const headers = {};
|
||||
Object.keys(req.headers).forEach((h) => {
|
||||
if (!immutableHdrs.includes(h)) headers[h] = req.headers[h];
|
||||
if (!IMMUTABLE_HEADERS.includes(h)) headers[h] = req.headers[h];
|
||||
});
|
||||
const response = await dlg.other.request({method: 'INFO', headers, body: req.body});
|
||||
const responseHeaders = {};
|
||||
@@ -944,6 +993,16 @@ Duration=${payload.duration} `
|
||||
|
||||
async _onFeatureServerTransfer(dlg, req, res) {
|
||||
try {
|
||||
// the following properties are ignored in the REFER headers
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { via, from, to, 'call-id': callid, cseq, 'max-forwards': maxforwards,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
'content-length': _contentlength, 'refer-to': _referto, 'referred-by': _referredby,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
'X-Refer-To-Leave-Untouched': _leave,
|
||||
...customHeaders
|
||||
} = req.headers;
|
||||
|
||||
const referTo = req.getParsedHeader('Refer-To');
|
||||
const uri = parseUri(referTo.uri);
|
||||
this.logger.info({uri, referTo}, 'received REFER from feature server');
|
||||
@@ -961,7 +1020,8 @@ Duration=${payload.duration} `
|
||||
method: 'REFER',
|
||||
headers: {
|
||||
'Refer-To': stringifyUri(uri),
|
||||
'Referred-By': stringifyUri(u)
|
||||
'Referred-By': stringifyUri(u),
|
||||
...customHeaders
|
||||
}
|
||||
});
|
||||
return res.send(response.status);
|
||||
|
||||
Generated
+14
-14
@@ -9,12 +9,12 @@
|
||||
"version": "0.8.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.9.0",
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.4",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/siprec-client-utils": "^0.2.6",
|
||||
"@jambonz/siprec-client-utils": "^0.2.7",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
@@ -723,9 +723,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/db-helpers": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.0.tgz",
|
||||
"integrity": "sha512-4fvwONj4jQNIHyG76WGdE7AuMt9vDl4sfHmHrY3PSgOh+kf2BCtBEYqoxyJ96/NH9OeUYkuXoM6fjSvpS7GYVw==",
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.3.tgz",
|
||||
"integrity": "sha512-3XFs7NC7J7Q/eb1CwG1YJHa6N4elh8IP/4hMMDgoM9U5Loplx61XI4nZ58FIrY3C/F6gEF4UdjqKvbusEVw7cQ==",
|
||||
"dependencies": {
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
@@ -773,9 +773,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/siprec-client-utils": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.6.tgz",
|
||||
"integrity": "sha512-z2x6nghLaCfOBPcr36f+1vxsV557X3z5JW1L1RxZg8YPWkTboNIHyHxtEcAua7V9g6EB4G1qv2KDwA+iE5n5Ww==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.7.tgz",
|
||||
"integrity": "sha512-VztOToBfXnOphg/y6kO+lBYqoDH7X5Ci0daXT5zQ8v5NnaZw+UwxGeFPX3knIR3r5pmzUld0f2giFOuD4ZOF8w==",
|
||||
"dependencies": {
|
||||
"sdp-transform": "^2.14.1",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -5506,9 +5506,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@jambonz/db-helpers": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.0.tgz",
|
||||
"integrity": "sha512-4fvwONj4jQNIHyG76WGdE7AuMt9vDl4sfHmHrY3PSgOh+kf2BCtBEYqoxyJ96/NH9OeUYkuXoM6fjSvpS7GYVw==",
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.3.tgz",
|
||||
"integrity": "sha512-3XFs7NC7J7Q/eb1CwG1YJHa6N4elh8IP/4hMMDgoM9U5Loplx61XI4nZ58FIrY3C/F6gEF4UdjqKvbusEVw7cQ==",
|
||||
"requires": {
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
@@ -5553,9 +5553,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/siprec-client-utils": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.6.tgz",
|
||||
"integrity": "sha512-z2x6nghLaCfOBPcr36f+1vxsV557X3z5JW1L1RxZg8YPWkTboNIHyHxtEcAua7V9g6EB4G1qv2KDwA+iE5n5Ww==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.7.tgz",
|
||||
"integrity": "sha512-VztOToBfXnOphg/y6kO+lBYqoDH7X5Ci0daXT5zQ8v5NnaZw+UwxGeFPX3knIR3r5pmzUld0f2giFOuD4ZOF8w==",
|
||||
"requires": {
|
||||
"sdp-transform": "^2.14.1",
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
+3
-3
@@ -24,15 +24,15 @@
|
||||
"start": "node app",
|
||||
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_HOSTING=1 JAMBONES_NETWORK_CIDR=127.0.0.1/32 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 node test/ ",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
"jslint": "eslint app.js lib --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.9.0",
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.4",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/siprec-client-utils": "^0.2.6",
|
||||
"@jambonz/siprec-client-utils": "^0.2.7",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
|
||||
Reference in New Issue
Block a user