mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2025-12-19 04:37:43 +00:00
add support for ms teams
This commit is contained in:
17
app.js
17
app.js
@@ -20,14 +20,14 @@ const {getRtpEngine} = require('jambonz-rtpengine-utils')(process.env.JAMBONES_R
|
||||
emitter: srf.locals.stats
|
||||
});
|
||||
srf.locals.getRtpEngine = getRtpEngine;
|
||||
const activeCallIds = srf.locals.activeCallIds = new Set();
|
||||
const activeCallIds = srf.locals.activeCallIds = new Map();
|
||||
logger.info('starting..');
|
||||
|
||||
const {
|
||||
lookupAuthHook,
|
||||
lookupSipGatewayBySignalingAddress,
|
||||
addSbcAddress
|
||||
} = require('jambonz-db-helpers')({
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
@@ -64,6 +64,17 @@ if (process.env.NODE_ENV === 'test') {
|
||||
srf.use('invite', [initLocals, challengeDeviceCalls]);
|
||||
|
||||
srf.invite((req, res) => {
|
||||
if (req.has('Replaces')) {
|
||||
const arr = /^(.*);from/.exec(req.get('Replaces'));
|
||||
if (arr) logger.info(`replacing call-id ${arr}`);
|
||||
else logger.info(`failed parsing ${req.get('Replaces')}`);
|
||||
const session = arr ? activeCallIds.get(arr[1]) : null;
|
||||
if (!session) {
|
||||
logger.info(`failed to find session in Replaces header: ${req.has('Replaces')}`);
|
||||
return res.send(404);
|
||||
}
|
||||
return session.replaces(req, res);
|
||||
}
|
||||
const session = new CallSession(logger, req, res);
|
||||
session.connect();
|
||||
});
|
||||
@@ -75,6 +86,6 @@ srf.use((req, res, next, err) => {
|
||||
|
||||
setInterval(() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
||||
}, 5000);
|
||||
}, 20000);
|
||||
|
||||
module.exports = {srf, logger};
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"DTLS": "off",
|
||||
"SDES": "off",
|
||||
"ICE": "remove",
|
||||
"rtcp-mux": ["demux"]
|
||||
"flags": ["media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
{
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no"],
|
||||
"rtcp-mux": ["require"]
|
||||
"default": {
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["require"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
const Emitter = require('events');
|
||||
const {isWSS, makeRtpEngineOpts} = require('./utils');
|
||||
const {makeRtpEngineOpts, SdpWantsSrtp} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {parseUri, SipError} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const MS_TEAMS_USER_AGENT = 'Microsoft.PSTNHub.SIPProxy';
|
||||
const MS_TEAMS_SIP_ENDPOINT = 'sip.pstnhub.microsoft.com';
|
||||
|
||||
/**
|
||||
* this is to make sure the outgoing From has the number in the incoming From
|
||||
@@ -29,6 +31,10 @@ class CallSession extends Emitter {
|
||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||
}
|
||||
|
||||
get isFromMSTeams() {
|
||||
return !!this.req.locals.msTeamsTenantFqdn;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.logger.info('inbound call accepted for routing');
|
||||
const engine = this.getRtpEngine();
|
||||
@@ -53,22 +59,24 @@ class CallSession extends Emitter {
|
||||
}
|
||||
debug(`using feature server ${featureServer}`);
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, isWSS(this.req), false);
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams);
|
||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||
const obj = parseUri(this.req.uri);
|
||||
let proxy, host, uri;
|
||||
|
||||
// replace host part of uri if its an ipv4 address, leave it otherwise
|
||||
if (/\d{1-3}\.\d{1-3}\.\d{1-3}\.\d{1-3}/.test(obj.host)) {
|
||||
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(obj.host)) {
|
||||
debug(`replacing host: was ${obj.host} is ${featureServer}`);
|
||||
host = featureServer;
|
||||
}
|
||||
else {
|
||||
debug(`not replacing host: "${obj.host}"`);
|
||||
host = obj.host;
|
||||
proxy = `sip:${featureServer}`;
|
||||
}
|
||||
else {
|
||||
host = featureServer;
|
||||
}
|
||||
if (obj.user) uri = `${obj.scheme}:${obj.user}@${host}`;
|
||||
else uri = `${obj.scheme}:${host}`;
|
||||
debug(`uri will be: ${uri}, proxy ${proxy}`);
|
||||
this.logger.info(`uri will be: ${uri}, proxy ${proxy}`);
|
||||
|
||||
try {
|
||||
const response = await this.offer(this.rtpEngineOpts.offer);
|
||||
@@ -81,10 +89,21 @@ class CallSession extends Emitter {
|
||||
// now send the INVITE in towards the feature servers
|
||||
const headers = {
|
||||
'From': createBLegFromHeader(this.req),
|
||||
'To': this.req.get('To'),
|
||||
'X-CID': this.req.get('Call-ID'),
|
||||
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
||||
};
|
||||
const responseHeaders = {};
|
||||
if (this.req.locals.carrier) Object.assign(headers, {'X-Originating-Carrier': this.req.locals.carrier});
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
Object.assign(headers, {'X-MS-Teams-Tenant-FQDN': this.req.locals.msTeamsTenantFqdn});
|
||||
|
||||
// for Microsoft Teams the Contact header must include the tenant FQDN
|
||||
Object.assign(responseHeaders, {
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK',
|
||||
Contact: `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
});
|
||||
}
|
||||
if (this.req.locals.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.locals.application_sid});
|
||||
}
|
||||
@@ -104,7 +123,8 @@ class CallSession extends Emitter {
|
||||
const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, {
|
||||
proxy,
|
||||
headers,
|
||||
proxyRequestHeaders: ['all', '-Authorization', '-Max-Forwards'],
|
||||
responseHeaders,
|
||||
proxyRequestHeaders: ['all', '-Authorization', '-Max-Forwards', '-Record-Route'],
|
||||
proxyResponseHeaders: ['all'],
|
||||
localSdpB: response.sdp,
|
||||
localSdpA: async(sdp, res) => {
|
||||
@@ -121,6 +141,7 @@ class CallSession extends Emitter {
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('call connected successfully to feature server');
|
||||
this.toTag = uas.sip.localTag;
|
||||
this._setHandlers({uas, uac});
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -139,11 +160,23 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
_setDlgHandlers(dlg) {
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
dlg.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
//re-invite
|
||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||
}
|
||||
|
||||
_setHandlers({uas, uac}) {
|
||||
this.emit('connected');
|
||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
this.activeCallIds.add(this.req.get('Call-ID'));
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
|
||||
this.uas = uas;
|
||||
this.uac = uac;
|
||||
@@ -151,34 +184,89 @@ class CallSession extends Emitter {
|
||||
//hangup
|
||||
dlg.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy();
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
dlg.other.destroy();
|
||||
dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
});
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
|
||||
uac.on('modify', this._onFeatureServerReinvite.bind(this, uac));
|
||||
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
||||
uas.on('refer', this._onRefer.bind(this, uas));
|
||||
|
||||
// default forwarding of other request types
|
||||
forwardInDialogRequests(uas);
|
||||
forwardInDialogRequests(uas, ['info', 'notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* handle INVITE with Replaces header from uas side (this will never come from the feature server)
|
||||
* @param {*} req incoming request
|
||||
* @param {*} res incoming response
|
||||
*/
|
||||
async replaces(req, res) {
|
||||
try {
|
||||
let opts = Object.assign(this.rtpEngineOpts.offer, {sdp: req.body});
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`replaces: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent offer for reinvite to rtpengine');
|
||||
const sdp = await this.uac.modify(response.sdp);
|
||||
opts = Object.assign(this.rtpEngineOpts.answer, {sdp, 'to-tag': this.toTag});
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`replaces: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent answer for reinvite to rtpengine');
|
||||
const headers = {};
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
Object.assign(headers, {'X-MS-Teams-Tenant-FQDN': this.req.locals.msTeamsTenantFqdn});
|
||||
|
||||
// for Microsoft Teams the Contact header must include the tenant FQDN
|
||||
Object.assign(headers, {
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY',
|
||||
Contact: `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
});
|
||||
}
|
||||
|
||||
const uas = await this.srf.createUAS(req, res, {
|
||||
localSdp: response.sdp,
|
||||
headers
|
||||
});
|
||||
this.logger.info('successfully connected new INVITE w/replaces, hanging up leg being replaced');
|
||||
this.uas.destroy();
|
||||
this.req = req;
|
||||
this.uas = uas;
|
||||
this.uas.other = this.uac;
|
||||
this.uac.other = this.uas;
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this._setDlgHandlers(uas);
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling invite with replaces');
|
||||
res.send(err.status || 500);
|
||||
}
|
||||
}
|
||||
|
||||
async _onReinvite(dlg, req, res) {
|
||||
try {
|
||||
let response = await this.offer(Object.assign({sdp: req.body}, this.rtpEngineOpts.offer));
|
||||
let opts = Object.assign(this.rtpEngineOpts.offer, {sdp: req.body});
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent offer for reinvite to rtpengine');
|
||||
const sdp = await dlg.other.modify(response.sdp);
|
||||
const opts = Object.assign({sdp, 'to-tag': this.toTag}, this.rtpEngineOpts.answer);
|
||||
opts = Object.assign({sdp, 'to-tag': this.toTag}, this.rtpEngineOpts.answer);
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent answer for reinvite to rtpengine');
|
||||
res.send(200, {body: response.sdp});
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
@@ -245,6 +333,77 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async _onRefer(dlg, req, res) {
|
||||
const ua = req.get('User-Agent');
|
||||
const referTo = req.get('Refer-To');
|
||||
const rt = req.getParsedHeader('Refer-To');
|
||||
const uri = parseUri(rt.uri);
|
||||
this.logger.info({referTo, ua, rt, uri}, 'got a REFER');
|
||||
|
||||
/**
|
||||
* send NOTIFY of INVITE status, return true if call answered
|
||||
*/
|
||||
const sendNotify = (dlg, body) => {
|
||||
const arr = /SIP\/2.0\s+(\d+).*$/.exec(body);
|
||||
const status = arr ? parseInt(arr[1]) : null;
|
||||
dlg.request({
|
||||
method: 'NOTIFY',
|
||||
headers: {
|
||||
'Content-Type': 'message/sipfrag;version=2.0',
|
||||
'Contact': `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
},
|
||||
body
|
||||
});
|
||||
this.logger.info(`sent NOTIFY for REFER with status ${status}`);
|
||||
return status === 200;
|
||||
};
|
||||
|
||||
if (this.isFromMSTeams && ua.startsWith(MS_TEAMS_USER_AGENT) &&
|
||||
referTo.startsWith(`<sip:${MS_TEAMS_SIP_ENDPOINT}`) &&
|
||||
!uri.user) {
|
||||
|
||||
// the Refer-To endpoint is within Teams itself, so we can handle
|
||||
res.send(202);
|
||||
try {
|
||||
const dlg = await this.srf.createUAC(rt.uri, {
|
||||
localSdp: this.uas.local.sdp.replace(/a=inactive/g, 'a=sendrecv'),
|
||||
headers: {
|
||||
'From': `sip:${this.req.callingNumber}@${this.req.locals.msTeamsTenantFqdn}`,
|
||||
'Contact': `sip:${this.req.callingNumber}@${this.req.locals.msTeamsTenantFqdn}`
|
||||
}
|
||||
},
|
||||
{
|
||||
cbRequest: (err, inviteSent) => {
|
||||
if (err) return sendNotify(this.uas, `SIP/2.0 ${err.status || '500'}`);
|
||||
sendNotify(this.uas, '100 Trying ');
|
||||
this.referInvite = inviteSent;
|
||||
},
|
||||
cbProvisional: (prov) => {
|
||||
sendNotify(this.uas, `${prov.status} ${prov.reason}`);
|
||||
}
|
||||
});
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('successfully connected new call leg for REFER');
|
||||
this.referInvite = null;
|
||||
sendNotify(this.uas, '200 OK');
|
||||
this.uas.destroy();
|
||||
this.uas = dlg;
|
||||
this.uas.other = this.uac;
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this._setDlgHandlers(dlg);
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error creating new call leg for REFER');
|
||||
sendNotify(this.uas, `${err.status || 500} ${err.reason || ''}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: forward on to feature server
|
||||
res.send(501);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CallSession;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const Emitter = require('events');
|
||||
const parseUri = require('drachtio-srf').parseUri;
|
||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ? process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) : [];
|
||||
|
||||
class AuthOutcomeReporter extends Emitter {
|
||||
constructor(stats) {
|
||||
@@ -35,16 +37,23 @@ module.exports = function(srf, logger) {
|
||||
async function challengeDeviceCalls(req, res, next) {
|
||||
try {
|
||||
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
|
||||
if (!gateway) {
|
||||
// TODO: if uri.host is not a domain, just reject
|
||||
req.locals.originator = 'device';
|
||||
return authenticator(req, res, next);
|
||||
if (gateway) {
|
||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
||||
req.locals.originator = 'trunk';
|
||||
req.locals.carrier = gateway.name;
|
||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
||||
return next();
|
||||
}
|
||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
||||
req.locals.originator = 'trunk';
|
||||
req.locals.carrier = gateway.name;
|
||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
||||
next();
|
||||
if (msProxyIps.includes(req.source_address)) {
|
||||
logger.debug({source_address: req.source_address}, 'challengeDeviceCalls: incoming call from Microsoft Teams');
|
||||
const uri = parseUri(req.uri);
|
||||
req.locals.originator = 'teams';
|
||||
req.locals.carrier = 'Microsoft Teams';
|
||||
req.locals.msTeamsTenantFqdn = uri.host;
|
||||
return next();
|
||||
}
|
||||
req.locals.originator = 'device';
|
||||
return authenticator(req, res, next);
|
||||
} catch (err) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
|
||||
|
||||
19
lib/utils.js
19
lib/utils.js
@@ -11,19 +11,30 @@ function getAppserver(srf) {
|
||||
return srf.locals.featureServers[ idx++ % len];
|
||||
}
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) {
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
|
||||
const from = req.getParsedHeader('from');
|
||||
const common = {'call-id': req.get('Call-ID'), 'from-tag': from.params.tag};
|
||||
return {
|
||||
common,
|
||||
offer: Object.assign({'sdp': req.body, 'replace': ['origin', 'session-connection']}, common,
|
||||
dstIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics),
|
||||
answer: Object.assign({}, common, srcIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics)
|
||||
offer: Object.assign(
|
||||
{'sdp': req.body, 'replace': ['origin', 'session-connection']},
|
||||
common,
|
||||
dstIsUsingSrtp ? srtpOpts : rtpCharacteristics),
|
||||
answer: Object.assign(
|
||||
{'replace': ['origin', 'session-connection']},
|
||||
common,
|
||||
srcIsUsingSrtp ? srtpOpts : rtpCharacteristics)
|
||||
};
|
||||
}
|
||||
|
||||
function SdpWantsSrtp(sdp) {
|
||||
return /m=audio.*SAVP/.test(sdp);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isWSS,
|
||||
SdpWantsSrtp,
|
||||
getAppserver,
|
||||
makeRtpEngineOpts
|
||||
};
|
||||
|
||||
48
package-lock.json
generated
48
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -274,6 +274,23 @@
|
||||
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
|
||||
"dev": true
|
||||
},
|
||||
"@jambonz/db-helpers": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.3.6.tgz",
|
||||
"integrity": "sha512-yKi9yX1DGH2MK4GSkllpEh1MqfdSxePn9ChOSZKmcMX0/PBe95YenUCTssLRGhr4+naKUhaWgBJkvQgkwQEsEw==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"mysql2": "^2.0.2",
|
||||
"uuid": "^7.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
|
||||
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -903,9 +920,9 @@
|
||||
}
|
||||
},
|
||||
"drachtio-srf": {
|
||||
"version": "4.4.28",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.28.tgz",
|
||||
"integrity": "sha512-gY/wmH6JFmeEv2/jhwFbky1NYUmDwgIJzjNeGlDiAkQEA6GgcI/CFi5RRHAUzghpczTwXQNDXpARPB8QDWX1JA==",
|
||||
"version": "4.4.33",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.33.tgz",
|
||||
"integrity": "sha512-2bVOObbP9m9ASZ+XXgnaeEk9v1a2Vn8d4Oaz6anrRCuRUzirUOJ/c5nJh9VQvbmXqUx+VubPmXa+AGJuFyYNow==",
|
||||
"requires": {
|
||||
"async": "^1.4.2",
|
||||
"debug": "^3.1.0",
|
||||
@@ -916,6 +933,7 @@
|
||||
"lodash": "^4.17.13",
|
||||
"node-noop": "0.0.1",
|
||||
"only": "0.0.2",
|
||||
"sdp-transform": "^2.14.0",
|
||||
"sip-methods": "^0.3.0",
|
||||
"utils-merge": "1.0.0",
|
||||
"uuid": "^3.0.0"
|
||||
@@ -1987,23 +2005,6 @@
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"jambonz-db-helpers": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/jambonz-db-helpers/-/jambonz-db-helpers-0.3.4.tgz",
|
||||
"integrity": "sha512-0ygVMhrHxO4wE30LqszKaGYXOgwoGLERQZHw1f5A4vKrpLZrcG1a/jF6W2bCpcfLfJ5owewx45AeUYeEvdvIGw==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"mysql2": "^2.0.2",
|
||||
"uuid": "^7.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
|
||||
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jambonz-http-authenticator": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/jambonz-http-authenticator/-/jambonz-http-authenticator-0.1.5.tgz",
|
||||
@@ -2880,6 +2881,11 @@
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||
},
|
||||
"sdp-transform": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.0.tgz",
|
||||
"integrity": "sha512-8ZYOau/o9PzRhY0aMuRzvmiM6/YVQR8yjnBScvZHSdBnywK5oZzAJK+412ZKkDq29naBmR3bRw8MFu0C01Gehg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.4.28",
|
||||
"jambonz-db-helpers": "^0.3.4",
|
||||
"drachtio-srf": "^4.4.33",
|
||||
"@jambonz/db-helpers": "^0.3.6",
|
||||
"jambonz-http-authenticator": "0.1.5",
|
||||
"jambonz-realtimedb-helpers": "^0.2.4",
|
||||
"jambonz-rtpengine-utils": "0.1.1",
|
||||
|
||||
Reference in New Issue
Block a user