mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-05 03:13:23 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e82d7bf14e | ||
|
|
f3fe46319d | ||
|
|
43bfeb439a | ||
|
|
31f34fbffa | ||
|
|
52d0670b88 | ||
|
|
f42128765d | ||
|
|
eac1d7a9e9 | ||
|
|
fae1732ddf | ||
|
|
c858624815 | ||
|
|
2f137f2bfc | ||
|
|
ef0dcfc021 | ||
|
|
fb8baa34af | ||
|
|
7efce18a03 | ||
|
|
84e5202607 | ||
|
|
8f856a30a6 | ||
|
|
d9881632c2 | ||
|
|
6d2b1a94e9 | ||
|
|
ded83ac39a | ||
|
|
91a29d2b2c | ||
|
|
27686a80e8 | ||
|
|
a9e79b23bd | ||
|
|
faafaa8549 | ||
|
|
b24ecf84d3 | ||
|
|
3360ddb3e6 | ||
|
|
b2d48c8954 | ||
|
|
1f1a4d2330 | ||
|
|
c34da3cfa8 | ||
|
|
5b88065925 | ||
|
|
438924ca36 | ||
|
|
c4d4d7bc0a | ||
|
|
ebcd0ce4a2 | ||
|
|
cf239ca7fa | ||
|
|
b072524585 | ||
|
|
7fb9966d20 | ||
|
|
42b3317206 | ||
|
|
db66d98d0a | ||
|
|
26169517dc | ||
|
|
c78ec892b9 | ||
|
|
6d237d43b3 | ||
|
|
44d298a4f3 | ||
|
|
030059a596 | ||
|
|
78b60525e2 | ||
|
|
dc9103cfa1 | ||
|
|
4dd4247dc2 | ||
|
|
055350903a | ||
|
|
7e83791ef0 | ||
|
|
dbedd7419e | ||
|
|
c4d07b517e | ||
|
|
2693077871 | ||
|
|
ad0912d302 | ||
|
|
e715433534 | ||
|
|
f315b1b41f | ||
|
|
bd09703732 | ||
|
|
363eb676a3 | ||
|
|
7b085e5763 | ||
|
|
04554fd60d | ||
|
|
9f1109c738 | ||
|
|
d27847747d | ||
|
|
88bdeffe4b | ||
|
|
3245fae069 | ||
|
|
213e84f59c | ||
|
|
ca0c9c157c | ||
|
|
996519404e |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on: [push, pull_request]
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 18.x
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run jslint
|
- run: npm run jslint
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=linux/amd64 node:18.6.0-alpine as base
|
FROM --platform=linux/amd64 node:18.14.0-alpine3.16 as base
|
||||||
|
|
||||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ const opts = Object.assign({
|
|||||||
const logger = require('pino')(opts);
|
const logger = require('pino')(opts);
|
||||||
const {
|
const {
|
||||||
writeCallCount,
|
writeCallCount,
|
||||||
|
writeCallCountSP,
|
||||||
|
writeCallCountApp,
|
||||||
queryCdrs,
|
queryCdrs,
|
||||||
writeCdrs,
|
writeCdrs,
|
||||||
writeAlerts,
|
writeAlerts,
|
||||||
@@ -26,6 +28,7 @@ const {
|
|||||||
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
|
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
|
||||||
});
|
});
|
||||||
const StatsCollector = require('@jambonz/stats-collector');
|
const StatsCollector = require('@jambonz/stats-collector');
|
||||||
|
const CIDRMatcher = require('cidr-matcher');
|
||||||
const stats = new StatsCollector(logger);
|
const stats = new StatsCollector(logger);
|
||||||
const {equalsIgnoreOrder, createHealthCheckApp, systemHealth} = require('./lib/utils');
|
const {equalsIgnoreOrder, createHealthCheckApp, systemHealth} = require('./lib/utils');
|
||||||
const {LifeCycleEvents} = require('./lib/constants');
|
const {LifeCycleEvents} = require('./lib/constants');
|
||||||
@@ -43,7 +46,8 @@ const {
|
|||||||
lookupAppByTeamsTenant,
|
lookupAppByTeamsTenant,
|
||||||
lookupAccountBySipRealm,
|
lookupAccountBySipRealm,
|
||||||
lookupAccountBySid,
|
lookupAccountBySid,
|
||||||
lookupAccountCapacitiesBySid
|
lookupAccountCapacitiesBySid,
|
||||||
|
queryCallLimits
|
||||||
} = require('@jambonz/db-helpers')({
|
} = require('@jambonz/db-helpers')({
|
||||||
host: process.env.JAMBONES_MYSQL_HOST,
|
host: process.env.JAMBONES_MYSQL_HOST,
|
||||||
user: process.env.JAMBONES_MYSQL_USER,
|
user: process.env.JAMBONES_MYSQL_USER,
|
||||||
@@ -63,14 +67,18 @@ const {
|
|||||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||||
}, logger);
|
}, logger);
|
||||||
|
|
||||||
|
const ngProtocol = process.env.JAMBONES_NG_PROTOCOL || 'udp';
|
||||||
|
const ngPort = process.env.RTPENGINE_PORT || ('udp' === ngProtocol ? 22222 : 8080);
|
||||||
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
|
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
|
||||||
emitter: stats,
|
//emitter: stats,
|
||||||
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22224,
|
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22224,
|
||||||
protocol: 'udp'
|
protocol: ngProtocol
|
||||||
});
|
});
|
||||||
srf.locals = {...srf.locals,
|
srf.locals = {...srf.locals,
|
||||||
stats,
|
stats,
|
||||||
writeCallCount,
|
writeCallCount,
|
||||||
|
writeCallCountSP,
|
||||||
|
writeCallCountApp,
|
||||||
queryCdrs,
|
queryCdrs,
|
||||||
writeCdrs,
|
writeCdrs,
|
||||||
writeAlerts,
|
writeAlerts,
|
||||||
@@ -86,7 +94,8 @@ srf.locals = {...srf.locals,
|
|||||||
lookupAppByTeamsTenant,
|
lookupAppByTeamsTenant,
|
||||||
lookupAccountBySid,
|
lookupAccountBySid,
|
||||||
lookupAccountBySipRealm,
|
lookupAccountBySipRealm,
|
||||||
lookupAccountCapacitiesBySid
|
lookupAccountCapacitiesBySid,
|
||||||
|
queryCallLimits
|
||||||
},
|
},
|
||||||
realtimeDbHelpers: {
|
realtimeDbHelpers: {
|
||||||
createSet,
|
createSet,
|
||||||
@@ -96,12 +105,14 @@ srf.locals = {...srf.locals,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
|
getSPForAccount,
|
||||||
wasOriginatedFromCarrier,
|
wasOriginatedFromCarrier,
|
||||||
getApplicationForDidAndCarrier,
|
getApplicationForDidAndCarrier,
|
||||||
getOutboundGatewayForRefer
|
getOutboundGatewayForRefer
|
||||||
} = require('./lib/db-utils')(srf, logger);
|
} = require('./lib/db-utils')(srf, logger);
|
||||||
srf.locals = {
|
srf.locals = {
|
||||||
...srf.locals,
|
...srf.locals,
|
||||||
|
getSPForAccount,
|
||||||
wasOriginatedFromCarrier,
|
wasOriginatedFromCarrier,
|
||||||
getApplicationForDidAndCarrier,
|
getApplicationForDidAndCarrier,
|
||||||
getOutboundGatewayForRefer,
|
getOutboundGatewayForRefer,
|
||||||
@@ -119,7 +130,6 @@ const {
|
|||||||
const CallSession = require('./lib/call-session');
|
const CallSession = require('./lib/call-session');
|
||||||
|
|
||||||
if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
||||||
const CIDRMatcher = require('cidr-matcher');
|
|
||||||
const cidrs = process.env.JAMBONES_NETWORK_CIDR
|
const cidrs = process.env.JAMBONES_NETWORK_CIDR
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => s.trim());
|
.map((s) => s.trim());
|
||||||
@@ -154,6 +164,23 @@ else {
|
|||||||
logger.info(`listening in outbound mode on port ${process.env.DRACHTIO_PORT}`);
|
logger.info(`listening in outbound mode on port ${process.env.DRACHTIO_PORT}`);
|
||||||
});
|
});
|
||||||
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
|
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
|
||||||
|
srf.on('connect', (err, hp) => {
|
||||||
|
if (err) return this.logger.error({err}, 'Error connecting to drachtio server');
|
||||||
|
logger.info(`connected to drachtio listening on ${hp}`);
|
||||||
|
|
||||||
|
if (process.env.K8S_FEATURE_SERVER_TRANSPORT === 'tcp') {
|
||||||
|
const matcher = new CIDRMatcher(['192.168.0.0/24', '172.16.0.0/16', '10.0.0.0/8']);
|
||||||
|
const hostports = hp.split(',');
|
||||||
|
for (const hp of hostports) {
|
||||||
|
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||||
|
if (arr && matcher.contains(arr[2])) {
|
||||||
|
const hostport = `${arr[2]}:${arr[3]}`;
|
||||||
|
logger.info(`using sbc private address when sending to feature-server: ${hostport}`);
|
||||||
|
srf.locals.privateSipAddress = hostport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
srf.on('error', (err) => {
|
srf.on('error', (err) => {
|
||||||
@@ -225,7 +252,6 @@ if ('test' !== process.env.NODE_ENV) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
||||||
logger.debug(`dns lookup for ${serviceName}..`);
|
|
||||||
lookup(serviceName, {family: 4, all: true}, (err, addresses) => {
|
lookup(serviceName, {family: 4, all: true}, (err, addresses) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error({err}, `Error looking up ${serviceName}`);
|
logger.error({err}, `Error looking up ${serviceName}`);
|
||||||
@@ -237,7 +263,7 @@ const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
|||||||
rtpServers.length = 0;
|
rtpServers.length = 0;
|
||||||
Array.prototype.push.apply(rtpServers, addrs);
|
Array.prototype.push.apply(rtpServers, addrs);
|
||||||
logger.info({rtpServers}, 'rtpserver endpoints have been updated');
|
logger.info({rtpServers}, 'rtpserver endpoints have been updated');
|
||||||
setRtpEngines(rtpServers.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
|
setRtpEngines(rtpServers.map((a) => `${a}:${ngPort}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -264,7 +290,7 @@ else {
|
|||||||
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
|
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
|
||||||
if (!equalsIgnoreOrder(newArray, rtpServers)) {
|
if (!equalsIgnoreOrder(newArray, rtpServers)) {
|
||||||
logger.info({newArray}, 'resetting active rtpengines');
|
logger.info({newArray}, 'resetting active rtpengines');
|
||||||
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
|
setRtpEngines(newArray.map((a) => `${a}:${ngPort}`));
|
||||||
rtpServers.length = 0;
|
rtpServers.length = 0;
|
||||||
Array.prototype.push.apply(rtpServers, newArray);
|
Array.prototype.push.apply(rtpServers, newArray);
|
||||||
}
|
}
|
||||||
@@ -295,8 +321,18 @@ process.on('SIGUSR2', handle.bind(null, removeFromSet, setName));
|
|||||||
process.on('SIGTERM', handle.bind(null, removeFromSet, setName));
|
process.on('SIGTERM', handle.bind(null, removeFromSet, setName));
|
||||||
|
|
||||||
function handle(removeFromSet, setName, signal) {
|
function handle(removeFromSet, setName, signal) {
|
||||||
logger.info(`got signal ${signal}, removing ${srf.locals.privateSipAddress} from set ${setName}`);
|
logger.info(`got signal ${signal}`);
|
||||||
removeFromSet(setName, srf.locals.privateSipAddress);
|
if (srf.locals.privateSipAddress && setName) {
|
||||||
|
logger.info(`removing ${srf.locals.privateSipAddress} from set ${setName}`);
|
||||||
|
removeFromSet(setName, srf.locals.privateSipAddress);
|
||||||
|
}
|
||||||
|
if (process.env.K8S) {
|
||||||
|
lifecycleEmitter.operationalState = LifeCycleEvents.ScaleIn;
|
||||||
|
if (0 === activeCallIds.size) {
|
||||||
|
logger.info('exiting immediately since we have no calls in progress');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {srf, logger};
|
module.exports = {srf, logger};
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"default": {
|
"default": {
|
||||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||||
"ICE": "force",
|
"ICE": "default",
|
||||||
"SDES": "off",
|
"SDES": "off",
|
||||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||||
"rtcp-mux": ["require"]
|
"rtcp-mux": ["require"]
|
||||||
},
|
},
|
||||||
"teams": {
|
"teams": {
|
||||||
"transport-protocol": "RTP/SAVP",
|
"transport-protocol": "RTP/SAVP",
|
||||||
"ICE": "force",
|
"ICE": "default",
|
||||||
"SDES": "off",
|
"SDES": "off",
|
||||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||||
"rtcp-mux": ["accept"]
|
"rtcp-mux": ["accept"]
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ module.exports = (logger) => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
else if (process.env.K8S) {
|
||||||
|
lifecycleEmitter.scaleIn = () => process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
return {lifecycleEmitter};
|
return {lifecycleEmitter};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const bent = require('bent');
|
const bent = require('bent');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const PORT = process.env.AWS_SNS_PORT || 3001;
|
const PORT = process.env.AWS_SNS_PORT || 3010;
|
||||||
const {LifeCycleEvents} = require('./constants');
|
const {LifeCycleEvents} = require('./constants');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -21,6 +21,26 @@ class SnsNotifier extends Emitter {
|
|||||||
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
_doListen(logger, app, port, resolve) {
|
||||||
|
return app.listen(port, () => {
|
||||||
|
this.snsEndpoint = `http://${this.publicIp}:${port}`;
|
||||||
|
logger.info(`SNS lifecycle server listening on http://localhost:${port}`);
|
||||||
|
resolve(app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleErrors(logger, app, resolve, reject, e) {
|
||||||
|
if (e.code === 'EADDRINUSE' &&
|
||||||
|
process.env.AWS_SNS_PORT_MAX &&
|
||||||
|
e.port < process.env.AWS_SNS_PORT_MAX) {
|
||||||
|
|
||||||
|
logger.info(`SNS lifecycle server failed to bind port on ${e.port}, will try next port`);
|
||||||
|
const server = this._doListen(logger, app, ++e.port, resolve);
|
||||||
|
server.on('error', this._handleErrors.bind(this, logger, app, resolve, reject));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
|
||||||
async _handlePost(req, res) {
|
async _handlePost(req, res) {
|
||||||
try {
|
try {
|
||||||
@@ -45,6 +65,7 @@ class SnsNotifier extends Emitter {
|
|||||||
}, 'response from SNS SubscribeURL');
|
}, 'response from SNS SubscribeURL');
|
||||||
const data = await this.describeInstance();
|
const data = await this.describeInstance();
|
||||||
this.lifecycleState = data.AutoScalingInstances[0].LifecycleState;
|
this.lifecycleState = data.AutoScalingInstances[0].LifecycleState;
|
||||||
|
this.emit('SubscriptionConfirmation', {publicIp: this.publicIp});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Notification':
|
case 'Notification':
|
||||||
@@ -80,14 +101,12 @@ class SnsNotifier extends Emitter {
|
|||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
this.logger.info('SnsNotifier: retrieving instance data');
|
this.logger.debug('SnsNotifier: retrieving instance data');
|
||||||
this.instanceId = await getString('http://169.254.169.254/latest/meta-data/instance-id');
|
this.instanceId = await getString('http://169.254.169.254/latest/meta-data/instance-id');
|
||||||
this.publicIp = await getString('http://169.254.169.254/latest/meta-data/public-ipv4');
|
this.publicIp = await getString('http://169.254.169.254/latest/meta-data/public-ipv4');
|
||||||
this.snsEndpoint = `http://${this.publicIp}:${PORT}`;
|
|
||||||
this.logger.info({
|
this.logger.info({
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
publicIp: this.publicIp,
|
publicIp: this.publicIp
|
||||||
snsEndpoint: this.snsEndpoint
|
|
||||||
}, 'retrieved AWS instance data');
|
}, 'retrieved AWS instance data');
|
||||||
|
|
||||||
// start listening
|
// start listening
|
||||||
@@ -99,7 +118,10 @@ class SnsNotifier extends Emitter {
|
|||||||
this.logger.error(err, 'burped error');
|
this.logger.error(err, 'burped error');
|
||||||
res.status(err.status || 500).json({msg: err.message});
|
res.status(err.status || 500).json({msg: err.message});
|
||||||
});
|
});
|
||||||
app.listen(PORT);
|
return new Promise((resolve, reject) => {
|
||||||
|
const server = this._doListen(this.logger, app, PORT, resolve);
|
||||||
|
server.on('error', this._handleErrors.bind(this, this.logger, app, resolve, reject));
|
||||||
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error({err}, 'Error retrieving AWS instance metadata');
|
this.logger.error({err}, 'Error retrieving AWS instance metadata');
|
||||||
|
|||||||
+131
-50
@@ -1,6 +1,14 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const SrsClient = require('@jambonz/siprec-client-utils');
|
const SrsClient = require('@jambonz/siprec-client-utils');
|
||||||
const {makeRtpEngineOpts, SdpWantsSrtp, makeCallCountKey} = require('./utils');
|
const {
|
||||||
|
makeRtpEngineOpts,
|
||||||
|
SdpWantsSrtp,
|
||||||
|
SdpWantsSDES,
|
||||||
|
nudgeCallCounts,
|
||||||
|
roundTripTime,
|
||||||
|
parseConnectionIp
|
||||||
|
} = require('./utils');
|
||||||
|
|
||||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||||
const {parseUri, stringifyUri, SipError} = require('drachtio-srf');
|
const {parseUri, stringifyUri, SipError} = require('drachtio-srf');
|
||||||
const debug = require('debug')('jambonz:sbc-inbound');
|
const debug = require('debug')('jambonz:sbc-inbound');
|
||||||
@@ -49,8 +57,11 @@ class CallSession extends Emitter {
|
|||||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||||
|
|
||||||
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
||||||
this.callCountKey = makeCallCountKey(req.locals.account_sid);
|
|
||||||
this._mediaReleased = false;
|
this._mediaReleased = false;
|
||||||
|
|
||||||
|
this.application_sid = req.locals.application_sid;
|
||||||
|
this.account_sid = req.locals.account_sid;
|
||||||
|
this.service_provider_sid = req.locals.service_provider_sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFromMSTeams() {
|
get isFromMSTeams() {
|
||||||
@@ -70,6 +81,24 @@ class CallSession extends Emitter {
|
|||||||
return tp && -1 !== tp.indexOf('SAVP');
|
return tp && -1 !== tp.indexOf('SAVP');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isFive9VoiceStream() {
|
||||||
|
return this.req.has('X-Five9-StreamingPairId');
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeForDTMF(dlg) {
|
||||||
|
if (!this._subscribedForDTMF) {
|
||||||
|
this._subscribedForDTMF = true;
|
||||||
|
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag,
|
||||||
|
this._onDTMF.bind(this, dlg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsubscribeForDTMF() {
|
||||||
|
if (this._subscribedForDTMF) {
|
||||||
|
this._subscribedForDTMF = false;
|
||||||
|
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
const {sdp} = this.req.locals;
|
const {sdp} = this.req.locals;
|
||||||
this.logger.info('inbound call accepted for routing');
|
this.logger.info('inbound call accepted for routing');
|
||||||
@@ -119,7 +148,9 @@ class CallSession extends Emitter {
|
|||||||
}
|
}
|
||||||
this.logger.debug(`using feature server ${featureServer}`);
|
this.logger.debug(`using feature server ${featureServer}`);
|
||||||
|
|
||||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(sdp), false, this.isFromMSTeams);
|
const wantsSrtp = this.req.locals.possibleWebRtcClient = SdpWantsSrtp(sdp);
|
||||||
|
const wantsSDES = SdpWantsSDES(sdp);
|
||||||
|
this.rtpEngineOpts = makeRtpEngineOpts(this.req, wantsSrtp, false, this.isFromMSTeams || wantsSDES);
|
||||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||||
const obj = parseUri(this.req.uri);
|
const obj = parseUri(this.req.uri);
|
||||||
let proxy, host, uri;
|
let proxy, host, uri;
|
||||||
@@ -146,8 +177,13 @@ class CallSession extends Emitter {
|
|||||||
direction: ['public', 'private'],
|
direction: ['public', 'private'],
|
||||||
sdp
|
sdp
|
||||||
};
|
};
|
||||||
|
const startAt = process.hrtime();
|
||||||
const response = await this.offer(opts);
|
const response = await this.offer(opts);
|
||||||
this.logger.debug({opts, response}, 'response from rtpengine to offer');
|
this.rtpengineIp = opts.sdp ? parseConnectionIp(opts.sdp) : 'undefined';
|
||||||
|
const rtt = roundTripTime(startAt);
|
||||||
|
this.stats.histogram('app.rtpengine.response_time', rtt, [
|
||||||
|
'direction:inbound', 'command:offer', `rtpengine:${this.rtpengineIp}`]);
|
||||||
|
this.logger.debug({opts, response, rtt, rtpengine: this.rtpengineIp}, 'response from rtpengine to offer');
|
||||||
if ('ok' !== response.result) {
|
if ('ok' !== response.result) {
|
||||||
this.logger.error({}, `rtpengine offer failed with ${JSON.stringify(response)}`);
|
this.logger.error({}, `rtpengine offer failed with ${JSON.stringify(response)}`);
|
||||||
throw new Error('rtpengine failed: answer');
|
throw new Error('rtpengine failed: answer');
|
||||||
@@ -222,11 +258,27 @@ class CallSession extends Emitter {
|
|||||||
'to-tag': this.rtpEngineOpts.uac.tag,
|
'to-tag': this.rtpEngineOpts.uac.tag,
|
||||||
sdp
|
sdp
|
||||||
};
|
};
|
||||||
|
const startAt = process.hrtime();
|
||||||
const response = await this.answer(opts);
|
const response = await this.answer(opts);
|
||||||
|
this.logger.debug({response, opts}, 'response from rtpengine to answer');
|
||||||
|
const rtt = roundTripTime(startAt);
|
||||||
|
this.stats.histogram('app.rtpengine.response_time', rtt, [
|
||||||
|
'direction:inbound', 'command:answer', `rtpengine:${this.rtpengineIp}`]);
|
||||||
if ('ok' !== response.result) {
|
if ('ok' !== response.result) {
|
||||||
this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`);
|
this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`);
|
||||||
throw new Error('rtpengine failed: answer');
|
throw new Error('rtpengine failed: answer');
|
||||||
}
|
}
|
||||||
|
/* special case: Five9 Voicestream calls do not advertise a:sendonly, though they should */
|
||||||
|
if (this.isFive9VoiceStream) {
|
||||||
|
const opts = {
|
||||||
|
...this.rtpEngineOpts.common,
|
||||||
|
'from-tag':this.rtpEngineOpts.uac.tag
|
||||||
|
};
|
||||||
|
this.logger.info('Voicestream call from Five9, blocking audio in the reverse direction');
|
||||||
|
const response = await Promise.all([this.blockMedia(opts), this.blockDTMF(opts)]);
|
||||||
|
this.logger.debug({response}, 'response to blockMedia/blockDTMF');
|
||||||
|
}
|
||||||
|
|
||||||
return response.sdp;
|
return response.sdp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -256,8 +308,7 @@ class CallSession extends Emitter {
|
|||||||
_setDlgHandlers(dlg) {
|
_setDlgHandlers(dlg) {
|
||||||
const {callId} = dlg.sip;
|
const {callId} = dlg.sip;
|
||||||
this.activeCallIds.set(callId, this);
|
this.activeCallIds.set(callId, this);
|
||||||
this.subscribeDTMF(this.logger, callId, this.rtpEngineOpts.uas.tag,
|
this.subscribeForDTMF(this.uac);
|
||||||
this._onDTMF.bind(this));
|
|
||||||
dlg.on('destroy', () => {
|
dlg.on('destroy', () => {
|
||||||
debug('call ended with normal termination');
|
debug('call ended with normal termination');
|
||||||
this.logger.info('call ended with normal termination');
|
this.logger.info('call ended with normal termination');
|
||||||
@@ -280,6 +331,8 @@ class CallSession extends Emitter {
|
|||||||
_setHandlers({uas, uac}) {
|
_setHandlers({uas, uac}) {
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
const callStart = Date.now();
|
const callStart = Date.now();
|
||||||
|
const call_sid = uac.res?.get('X-Call-Sid');
|
||||||
|
const application_sid = this.application_sid || uac.res?.get('X-Application-Sid');
|
||||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||||
this.stats.increment('sbc.terminations', tags);
|
this.stats.increment('sbc.terminations', tags);
|
||||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||||
@@ -288,6 +341,8 @@ class CallSession extends Emitter {
|
|||||||
...this.req.locals.cdr,
|
...this.req.locals.cdr,
|
||||||
answered: true,
|
answered: true,
|
||||||
answered_at: callStart,
|
answered_at: callStart,
|
||||||
|
...(call_sid && {call_sid}),
|
||||||
|
...(application_sid && {application_sid}),
|
||||||
trace_id: uac.res?.get('X-Trace-ID') || '00000000000000000000000000000000'
|
trace_id: uac.res?.get('X-Trace-ID') || '00000000000000000000000000000000'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -301,17 +356,21 @@ class CallSession extends Emitter {
|
|||||||
try {
|
try {
|
||||||
await other.destroy();
|
await other.destroy();
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
this.unsubscribeForDTMF();
|
||||||
if (process.env.JAMBONES_HOSTING || process.env.JAMBONES_TRACK_ACCOUNT_CALLS) {
|
|
||||||
const {account_sid} = this.req.locals;
|
|
||||||
this.decrKey(this.callCountKey)
|
const trackingOn = process.env.JAMBONES_TRACK_ACCOUNT_CALLS ||
|
||||||
.then((count) => {
|
process.env.JAMBONES_TRACK_SP_CALLS ||
|
||||||
this.logger.info(
|
process.env.JAMBONES_TRACK_APP_CALLS;
|
||||||
{key: this.callCountKey},
|
|
||||||
`after hangup there are ${count} active calls for this account`);
|
if (process.env.JAMBONES_HOSTING || trackingOn) {
|
||||||
return this.req.srf.locals.writeCallCount({account_sid, calls_in_progress: count});
|
const {writeCallCount, writeCallCountSP, writeCallCountApp} = this.req.srf.locals;
|
||||||
})
|
await nudgeCallCounts(this.logger, {
|
||||||
.catch((err) => this.logger.error({err}, 'Error decrementing call count'));
|
service_provider_sid: this.service_provider_sid,
|
||||||
|
account_sid: this.account_sid,
|
||||||
|
application_sid: this.application_sid
|
||||||
|
}, this.decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||||
|
.catch((err) => this.logger.error(err, 'Error decrementing call counts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write cdr for connected call */
|
/* write cdr for connected call */
|
||||||
@@ -320,13 +379,23 @@ class CallSession extends Emitter {
|
|||||||
const trunk = ['trunk', 'teams'].includes(this.req.locals.originator) ?
|
const trunk = ['trunk', 'teams'].includes(this.req.locals.originator) ?
|
||||||
this.req.locals.carrier :
|
this.req.locals.carrier :
|
||||||
this.req.locals.originator;
|
this.req.locals.originator;
|
||||||
|
const cdr = {...this.req.locals.cdr,
|
||||||
|
terminated_at: now,
|
||||||
|
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
|
||||||
|
sip_status: 200,
|
||||||
|
duration: Math.floor((now - callStart) / 1000),
|
||||||
|
trunk
|
||||||
|
};
|
||||||
|
this.logger.info({cdr}, 'going to write a cdr now..');
|
||||||
this.writeCdrs({...this.req.locals.cdr,
|
this.writeCdrs({...this.req.locals.cdr,
|
||||||
terminated_at: now,
|
terminated_at: now,
|
||||||
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
|
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
|
||||||
sip_status: 200,
|
sip_status: 200,
|
||||||
duration: Math.floor((now - callStart) / 1000),
|
duration: Math.floor((now - callStart) / 1000),
|
||||||
trunk
|
trunk
|
||||||
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
|
})
|
||||||
|
.then(() => this.logger.debug('successfully wrote cdr'))
|
||||||
|
.catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
|
||||||
}
|
}
|
||||||
/* de-link the 2 Dialogs for GC */
|
/* de-link the 2 Dialogs for GC */
|
||||||
dlg.removeAllListeners();
|
dlg.removeAllListeners();
|
||||||
@@ -344,8 +413,7 @@ class CallSession extends Emitter {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag,
|
this.subscribeForDTMF(uac);
|
||||||
this._onDTMF.bind(this, uac));
|
|
||||||
|
|
||||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||||
uac.on('modify', this._onReinvite.bind(this, uac));
|
uac.on('modify', this._onReinvite.bind(this, uac));
|
||||||
@@ -439,7 +507,7 @@ Duration=${payload.duration} `
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
this.unsubscribeForDTMF();
|
||||||
|
|
||||||
const uas = await this.srf.createUAS(req, res, {
|
const uas = await this.srf.createUAS(req, res, {
|
||||||
localSdp: response.sdp,
|
localSdp: response.sdp,
|
||||||
@@ -472,11 +540,16 @@ Duration=${payload.duration} `
|
|||||||
req.body;
|
req.body;
|
||||||
|
|
||||||
const reason = req.get('X-Reason');
|
const reason = req.get('X-Reason');
|
||||||
|
const isReleasingMedia = reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason);
|
||||||
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
|
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
|
||||||
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
|
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
|
||||||
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
|
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
|
||||||
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
|
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
|
||||||
const direction = dlg.type === 'uas' ? ['public', 'private'] : ['private', 'public'];
|
const direction = dlg.type === 'uas' ? ['public', 'private'] : ['private', 'public'];
|
||||||
|
if (isReleasingMedia) {
|
||||||
|
if (!offerMedia.flags.includes('asymmetric')) offerMedia.flags.push('asymmetric');
|
||||||
|
offerMedia.flags = offerMedia.flags.filter((f) => f !== 'media handover');
|
||||||
|
}
|
||||||
let opts = {
|
let opts = {
|
||||||
...this.rtpEngineOpts.common,
|
...this.rtpEngineOpts.common,
|
||||||
...offerMedia,
|
...offerMedia,
|
||||||
@@ -495,11 +568,11 @@ Duration=${payload.duration} `
|
|||||||
|
|
||||||
/* if this is a re-invite from the FS to change media anchoring, avoid sending the reinvite out */
|
/* if this is a re-invite from the FS to change media anchoring, avoid sending the reinvite out */
|
||||||
let sdp;
|
let sdp;
|
||||||
if (reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason) &&
|
if (isReleasingMedia && !this.callerIsUsingSrtp) {
|
||||||
!this.callerIsUsingSrtp) {
|
|
||||||
this.logger.info({response}, `got a reinvite from FS to ${reason}`);
|
this.logger.info({response}, `got a reinvite from FS to ${reason}`);
|
||||||
sdp = dlg.other.remote.sdp;
|
sdp = dlg.other.remote.sdp;
|
||||||
answerMedia.flags = ['asymmetric', 'port latching'];
|
if (!answerMedia.flags.includes('asymmetric')) answerMedia.flags.push('asymmetric');
|
||||||
|
answerMedia.flags = answerMedia.flags.filter((f) => f !== 'media handover');
|
||||||
this._mediaReleased = 'release-media' === reason;
|
this._mediaReleased = 'release-media' === reason;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -633,7 +706,7 @@ Duration=${payload.duration} `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dlg.type === 'uas' && ['application/dtmf-relay', 'application/dtmf'].includes(contentType)) {
|
else if (dlg.type === 'uas' && ['application/dtmf-relay', 'application/dtmf'].includes(contentType)) {
|
||||||
const arr = /Signal=\s*([1-9#*])/.exec(req.body);
|
const arr = /Signal=\s*([0-9#*])/.exec(req.body);
|
||||||
if (!arr) {
|
if (!arr) {
|
||||||
this.logger.info({body: req.body}, '_onInfo: invalid INFO dtmf request');
|
this.logger.info({body: req.body}, '_onInfo: invalid INFO dtmf request');
|
||||||
throw new Error(`_onInfo: no dtmf in body for ${contentType}`);
|
throw new Error(`_onInfo: no dtmf in body for ${contentType}`);
|
||||||
@@ -700,30 +773,38 @@ Duration=${payload.duration} `
|
|||||||
const referredBy = req.getParsedHeader('Referred-By');
|
const referredBy = req.getParsedHeader('Referred-By');
|
||||||
if (!referredBy) return res.send(400);
|
if (!referredBy) return res.send(400);
|
||||||
const u = parseUri(referredBy.uri);
|
const u = parseUri(referredBy.uri);
|
||||||
|
const leaveReferToAlone = req.has('X-Refer-To-Leave-Untouched');
|
||||||
let selectedGateway = false;
|
if (leaveReferToAlone) {
|
||||||
let e164 = false;
|
this.logger.debug({referTo}, 'passing Refer-To header through untouched');
|
||||||
if (gateway) {
|
}
|
||||||
/* host of Refer-to to an outbound gateway */
|
else {
|
||||||
const gw = await this.srf.locals.getOutboundGatewayForRefer(gateway.voip_carrier_sid);
|
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
||||||
if (gw) {
|
let selectedGateway = false;
|
||||||
selectedGateway = true;
|
let e164 = false;
|
||||||
e164 = gw.e164_leading_plus;
|
if (gateway && isDotDecimal) {
|
||||||
uri.host = gw.ipv4;
|
/* host of Refer-to to an outbound gateway */
|
||||||
uri.port = gw.port;
|
const gw = await this.srf.locals.getOutboundGatewayForRefer(gateway.voip_carrier_sid);
|
||||||
|
if (gw) {
|
||||||
|
selectedGateway = true;
|
||||||
|
e164 = gw.e164_leading_plus;
|
||||||
|
uri.host = gw.ipv4;
|
||||||
|
uri.port = gw.port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!selectedGateway && isDotDecimal) {
|
||||||
|
uri.host = this.req.source_address;
|
||||||
|
uri.port = this.req.source_port;
|
||||||
|
}
|
||||||
|
if (e164 && !uri.user.startsWith('+')) {
|
||||||
|
uri.user = `+${uri.user}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!selectedGateway) {
|
|
||||||
uri.host = this.req.source_address;
|
|
||||||
uri.port = this.req.source_port;
|
|
||||||
}
|
|
||||||
if (e164 && !uri.user.startsWith('+')) {
|
|
||||||
uri.user = `+${uri.user}`;
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const {via, from, to, 'call-id':callid, cseq, 'max-forwards':maxforwards,
|
const {via, from, to, 'call-id':callid, cseq, 'max-forwards':maxforwards,
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
'content-length':contentlength, 'refer-to':_referto, 'referred-by':_referredby,
|
'content-length':contentlength, 'refer-to':_referto, 'referred-by':_referredby,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
'X-Refer-To-Leave-Untouched': _leave,
|
||||||
...customHeaders
|
...customHeaders
|
||||||
} = req.headers;
|
} = req.headers;
|
||||||
|
|
||||||
@@ -740,15 +821,15 @@ Duration=${payload.duration} `
|
|||||||
res.send(202);
|
res.send(202);
|
||||||
|
|
||||||
// invite to new fs
|
// invite to new fs
|
||||||
const headers = {};
|
const headers = {
|
||||||
if (req.has('X-Retain-Call-Sid')) {
|
...(req.has('X-Retain-Call-Sid') && {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')}),
|
||||||
Object.assign(headers, {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')});
|
...(req.has('X-Account-Sid') && {'X-Account-Sid': req.get('X-Account-Sid')})
|
||||||
}
|
};
|
||||||
const uac = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
|
const uac = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
|
||||||
this.uac = uac;
|
this.uac = uac;
|
||||||
uac.other = this.uas;
|
uac.other = this.uas;
|
||||||
this.uas.other = uac;
|
this.uas.other = uac;
|
||||||
uac.on('modify', this._onFeatureServerReinvite.bind(this, uac));
|
uac.on('modify', this._onReinvite.bind(this, uac));
|
||||||
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
||||||
uac.on('destroy', () => {
|
uac.on('destroy', () => {
|
||||||
this.logger.info('call ended with normal termination');
|
this.logger.info('call ended with normal termination');
|
||||||
@@ -766,7 +847,7 @@ Duration=${payload.duration} `
|
|||||||
const response = await this.answer(opts);
|
const response = await this.answer(opts);
|
||||||
if ('ok' !== response.result) {
|
if ('ok' !== response.result) {
|
||||||
res.send(488);
|
res.send(488);
|
||||||
throw new Error(`_onFeatureServerReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
throw new Error(`_onFeatureServerTransfer: rtpengine failed: ${JSON.stringify(response)}`);
|
||||||
}
|
}
|
||||||
this.logger.info('successfully moved call to new feature server');
|
this.logger.info('successfully moved call to new feature server');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -827,7 +908,7 @@ Duration=${payload.duration} `
|
|||||||
|
|
||||||
// successfully connected
|
// successfully connected
|
||||||
this.logger.info('successfully connected new call leg for REFER');
|
this.logger.info('successfully connected new call leg for REFER');
|
||||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
this.unsubscribeForDTMF();
|
||||||
this.referInvite = null;
|
this.referInvite = null;
|
||||||
sendNotify(this.uas, '200 OK');
|
sendNotify(this.uas, '200 OK');
|
||||||
this.uas.destroy();
|
this.uas.destroy();
|
||||||
|
|||||||
+159
-54
@@ -3,6 +3,8 @@ const CIDRMatcher = require('cidr-matcher');
|
|||||||
const {parseUri} = require('drachtio-srf');
|
const {parseUri} = require('drachtio-srf');
|
||||||
const {normalizeDID} = require('./utils');
|
const {normalizeDID} = require('./utils');
|
||||||
|
|
||||||
|
const sqlSelectSPForAccount = 'SELECT service_provider_sid FROM accounts WHERE account_sid = ?';
|
||||||
|
|
||||||
const sqlSelectAllCarriersForAccountByRealm =
|
const sqlSelectAllCarriersForAccountByRealm =
|
||||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
||||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||||
@@ -22,7 +24,7 @@ AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
|||||||
|
|
||||||
const sqlSelectAllGatewaysForSP =
|
const sqlSelectAllGatewaysForSP =
|
||||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
||||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||||
FROM sip_gateways sg, voip_carriers vc
|
FROM sip_gateways sg, voip_carriers vc
|
||||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||||
AND vc.service_provider_sid IS NOT NULL
|
AND vc.service_provider_sid IS NOT NULL
|
||||||
@@ -55,6 +57,16 @@ WHERE sg.voip_carrier_sid = ?
|
|||||||
AND sg.voip_carrier_sid = vc.voip_carrier_sid
|
AND sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||||
AND outbound = 1`;
|
AND outbound = 1`;
|
||||||
|
|
||||||
|
const sqlSelectCarrierRequiringRegistration = `
|
||||||
|
SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid, vc.account_sid,
|
||||||
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||||
|
FROM sip_gateways sg, voip_carriers vc
|
||||||
|
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||||
|
AND vc.requires_register = 1
|
||||||
|
AND vc.is_active = 1
|
||||||
|
AND vc.register_sip_realm = ?
|
||||||
|
AND vc.register_username = ?`;
|
||||||
|
|
||||||
const gatewayMatchesSourceAddress = (source_address, gw) => {
|
const gatewayMatchesSourceAddress = (source_address, gw) => {
|
||||||
if (32 === gw.netmask && gw.ipv4 === source_address) return true;
|
if (32 === gw.netmask && gw.ipv4 === source_address) return true;
|
||||||
if (gw.netmask < 32) {
|
if (gw.netmask < 32) {
|
||||||
@@ -68,6 +80,12 @@ module.exports = (srf, logger) => {
|
|||||||
const {pool} = srf.locals.dbHelpers;
|
const {pool} = srf.locals.dbHelpers;
|
||||||
const pp = pool.promise();
|
const pp = pool.promise();
|
||||||
|
|
||||||
|
const getSPForAccount = async(account_sid) => {
|
||||||
|
const [r] = await pp.query(sqlSelectSPForAccount, [account_sid]);
|
||||||
|
if (0 === r.length) return null;
|
||||||
|
return r[0].service_provider_sid;
|
||||||
|
};
|
||||||
|
|
||||||
const getOutboundGatewayForRefer = async(voip_carrier_sid) => {
|
const getOutboundGatewayForRefer = async(voip_carrier_sid) => {
|
||||||
try {
|
try {
|
||||||
const [r] = await pp.query(sqlSelectOutboundGatewayForCarrier, [voip_carrier_sid]);
|
const [r] = await pp.query(sqlSelectOutboundGatewayForCarrier, [voip_carrier_sid]);
|
||||||
@@ -75,14 +93,14 @@ module.exports = (srf, logger) => {
|
|||||||
|
|
||||||
/* if multiple, prefer a DNS name */
|
/* if multiple, prefer a DNS name */
|
||||||
const hasDns = r.find((row) => row.ipv4.match(/^[A-Za-z]/));
|
const hasDns = r.find((row) => row.ipv4.match(/^[A-Za-z]/));
|
||||||
return hasDns || r[0];
|
return hasDns /* || r[0] */;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error({err}, 'getOutboundGatewayForRefer');
|
logger.error({err}, 'getOutboundGatewayForRefer');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
|
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
|
||||||
const did = normalizeDID(req.calledNumber);
|
const did = normalizeDID(req.calledNumber) || 'anonymous';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* straight DID match */
|
/* straight DID match */
|
||||||
@@ -107,81 +125,167 @@ module.exports = (srf, logger) => {
|
|||||||
const uri = parseUri(req.uri);
|
const uri = parseUri(req.uri);
|
||||||
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
||||||
|
|
||||||
if (isDotDecimal) {
|
if (!isDotDecimal) {
|
||||||
if (process.env.JAMBONES_HOSTING) {
|
/**
|
||||||
if (!process.env.SBC_ACCOUNT_SID) return failure;
|
* The host part of the SIP URI is not a dot-decimal IP address,
|
||||||
|
* so this can be one of two things:
|
||||||
|
* (1) a sip realm value associate with an account, or
|
||||||
|
* (2) a carrier name for a carrier that we send outbound registrations to
|
||||||
|
*
|
||||||
|
* Let's look for case #1 first...
|
||||||
|
*/
|
||||||
|
|
||||||
/* look for carrier only within that account */
|
/* get all the carriers and gateways for the account owning this sip realm */
|
||||||
const [r] = await pp.query(sqlCarriersForAccountBySid,
|
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
||||||
[process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]);
|
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
||||||
if (0 === r.length) return failure;
|
const gw = gwAcc.concat(gwSP);
|
||||||
|
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||||
|
if (selected) {
|
||||||
|
const [a] = await pp.query(sqlAccountByRealm, uri.host);
|
||||||
|
if (0 === a.length) return failure;
|
||||||
return {
|
return {
|
||||||
fromCarrier: true,
|
fromCarrier: true,
|
||||||
gateway: r[0],
|
gateway: selected,
|
||||||
account_sid: process.env.SBC_ACCOUNT_SID
|
service_provider_sid: a[0].service_provider_sid,
|
||||||
|
account_sid: a[0].account_sid,
|
||||||
|
application_sid: selected.application_sid,
|
||||||
|
account: a[0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
/* we may have a carrier at the service provider level */
|
|
||||||
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
|
||||||
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
|
||||||
if (matches.length) {
|
|
||||||
/* we have one or more carriers that match. Now we need to find one with a provisioned phone number */
|
|
||||||
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
|
|
||||||
const did = normalizeDID(req.calledNumber);
|
|
||||||
const sql = `SELECT * FROM phone_numbers WHERE number = ${did} AND voip_carrier_sid IN (${vc_sids})`;
|
|
||||||
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
|
|
||||||
|
|
||||||
const [r] = await pp.query(sql);
|
/* no match, so let's look for case #2 */
|
||||||
if (0 === r.length) {
|
try {
|
||||||
/* came from a carrier, but number is not provisioned..
|
logger.info({
|
||||||
check if we only have a single account, otherwise we have no
|
host: uri.host,
|
||||||
way of knowing which account this is for
|
user: uri.user
|
||||||
|
}, 'sip realm is not associated with an account, checking carriers');
|
||||||
|
const [gw] = await pp.query(sqlSelectCarrierRequiringRegistration, [uri.host, uri.user]);
|
||||||
|
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||||
|
if (1 === matches.length) {
|
||||||
|
// bingo
|
||||||
|
//TODO: this assumes the carrier is associate to an account, not an SP
|
||||||
|
//if the carrier is associated with an SP (which would mean we
|
||||||
|
//must see a dialed number in the To header, not the register username),
|
||||||
|
//then we need to look up the account based on the dialed number in the To header
|
||||||
|
const [a] = await pp.query(sqlAccountBySid, matches[0].account_sid);
|
||||||
|
if (0 === a.length) return failure;
|
||||||
|
logger.debug({matches}, `found registration carrier using ${uri.host} and ${uri.user}`);
|
||||||
|
return {
|
||||||
|
fromCarrier: true,
|
||||||
|
gateway: matches[0],
|
||||||
|
service_provider_sid: a[0].service_provider_sid,
|
||||||
|
account_sid: a[0].account_sid,
|
||||||
|
application_sid: matches[0].application_sid,
|
||||||
|
account: a[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (matches.length > 1) {
|
||||||
|
logger.warn({matches, source_address: req.source_address}, 'multiple gateways match source address');
|
||||||
|
return {
|
||||||
|
fromCarrier: true,
|
||||||
|
error: 'Multiple gateways match registration carrier source address'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.info({err, host: uri.host, user: uri.user}, 'Error looking up carrier by host and user');
|
||||||
|
}
|
||||||
|
/* no match, so fall through */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDotDecimal && process.env.JAMBONES_HOSTING) {
|
||||||
|
if (!process.env.SBC_ACCOUNT_SID) return failure;
|
||||||
|
|
||||||
|
/* look for carrier only within that account */
|
||||||
|
const [r] = await pp.query(sqlCarriersForAccountBySid,
|
||||||
|
[process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]);
|
||||||
|
if (0 === r.length) return failure;
|
||||||
|
const service_provider_sid = await getSPForAccount(process.env.SBC_ACCOUNT_SID);
|
||||||
|
return {
|
||||||
|
fromCarrier: true,
|
||||||
|
gateway: r[0],
|
||||||
|
account_sid: process.env.SBC_ACCOUNT_SID,
|
||||||
|
service_provider_sid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* find all carrier entries that have an inbound gateway matching the source IP */
|
||||||
|
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
||||||
|
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||||
|
if (matches.length) {
|
||||||
|
/* we have one or more matches. Now check for one with a provisioned phone number matching the DID */
|
||||||
|
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
|
||||||
|
const did = normalizeDID(req.calledNumber) || 'anonymous';
|
||||||
|
const sql = `SELECT * FROM phone_numbers WHERE number = '${did}' AND voip_carrier_sid IN (${vc_sids})`;
|
||||||
|
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
|
||||||
|
|
||||||
|
const [r] = await pp.query(sql);
|
||||||
|
if (0 === r.length) {
|
||||||
|
/* came from a provisioned carrier, but the dialed number is not provisioned.
|
||||||
|
check if we have an account with default routing of that carrier to an application
|
||||||
|
*/
|
||||||
|
const accountLevelGateways = matches.filter((m) => m.account_sid && m.application_sid);
|
||||||
|
if (accountLevelGateways.length > 1) {
|
||||||
|
logger.info({accounts: accountLevelGateways.map((m) => m.account_sid)},
|
||||||
|
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||||
|
return {
|
||||||
|
fromCarrier: true,
|
||||||
|
error: 'Multiple accounts are attempting to default route this carrier'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (accountLevelGateways.length === 1) {
|
||||||
|
const [accounts] = await pp.query('SELECT * from accounts where account_sid = ?',
|
||||||
|
accountLevelGateways[0].account_sid);
|
||||||
|
return {
|
||||||
|
fromCarrier: true,
|
||||||
|
gateway: accountLevelGateways[0],
|
||||||
|
service_provider_sid: accountLevelGateways[0].service_provider_sid,
|
||||||
|
account_sid: accountLevelGateways[0].account_sid,
|
||||||
|
application_sid: accountLevelGateways[0].application_sid,
|
||||||
|
account: accounts[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* check if we only have a single account, otherwise we have no
|
||||||
|
- way of knowing which account this is for
|
||||||
*/
|
*/
|
||||||
const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?',
|
const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?',
|
||||||
matches[0].service_provider_sid);
|
matches[0].service_provider_sid);
|
||||||
if (r[0].count === 0) return {fromCarrier: true};
|
if (r[0].count === 0 || r[0].count > 1) return {fromCarrier: true};
|
||||||
else {
|
else {
|
||||||
const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?',
|
const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?',
|
||||||
matches[0].service_provider_sid);
|
matches[0].service_provider_sid);
|
||||||
return {
|
return {
|
||||||
fromCarrier: true,
|
fromCarrier: true,
|
||||||
gateway: matches[0],
|
gateway: matches[0],
|
||||||
|
service_provider_sid: accounts[0].service_provider_sid,
|
||||||
account_sid: accounts[0].account_sid,
|
account_sid: accounts[0].account_sid,
|
||||||
account: accounts[0]
|
account: accounts[0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid);
|
}
|
||||||
const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid);
|
else if (r.length > 1) {
|
||||||
assert(accounts.length);
|
logger.info({r},
|
||||||
|
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||||
return {
|
return {
|
||||||
fromCarrier: true,
|
fromCarrier: true,
|
||||||
gateway,
|
error: 'Multiple accounts are attempting to route the same phone number from the same carrier'
|
||||||
account_sid: r[0].account_sid,
|
|
||||||
application_sid: r[0].application_sid,
|
|
||||||
account: accounts[0]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return failure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* get all the carriers and gateways for the account owning this sip realm */
|
/* we have a route for this phone number and carrier combination */
|
||||||
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid);
|
||||||
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid);
|
||||||
const gw = gwAcc.concat(gwSP);
|
assert(accounts.length);
|
||||||
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
return {
|
||||||
if (selected) {
|
fromCarrier: true,
|
||||||
const [a] = await pp.query(sqlAccountByRealm, uri.host);
|
gateway,
|
||||||
if (0 === a.length) return failure;
|
service_provider_sid: accounts[0].service_provider_sid,
|
||||||
return {
|
account_sid: r[0].account_sid,
|
||||||
fromCarrier: true,
|
application_sid: r[0].application_sid,
|
||||||
gateway: selected,
|
account: accounts[0]
|
||||||
account_sid: a[0].account_sid,
|
};
|
||||||
application_sid: selected.application_sid,
|
}
|
||||||
account: a[0]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return failure;
|
return failure;
|
||||||
};
|
};
|
||||||
@@ -189,6 +293,7 @@ module.exports = (srf, logger) => {
|
|||||||
return {
|
return {
|
||||||
wasOriginatedFromCarrier,
|
wasOriginatedFromCarrier,
|
||||||
getApplicationForDidAndCarrier,
|
getApplicationForDidAndCarrier,
|
||||||
getOutboundGatewayForRefer
|
getOutboundGatewayForRefer,
|
||||||
|
getSPForAccount
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-1
@@ -15,7 +15,9 @@ module.exports = (srf, logger) => {
|
|||||||
return async() => {
|
return async() => {
|
||||||
try {
|
try {
|
||||||
if (process.env.K8S) {
|
if (process.env.K8S) {
|
||||||
return process.env.K8S_FEATURE_SERVER_SERVICE_NAME;
|
return process.env.K8S_FEATURE_SERVER_TRANSPORT ?
|
||||||
|
`${process.env.K8S_FEATURE_SERVER_SERVICE_NAME};transport=${process.env.K8S_FEATURE_SERVER_TRANSPORT}` :
|
||||||
|
process.env.K8S_FEATURE_SERVER_SERVICE_NAME;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const fs = await retrieveSet(setName);
|
const fs = await retrieveSet(setName);
|
||||||
|
|||||||
+104
-40
@@ -2,7 +2,7 @@ const debug = require('debug')('jambonz:sbc-inbound');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const parseUri = require('drachtio-srf').parseUri;
|
const parseUri = require('drachtio-srf').parseUri;
|
||||||
const {makeCallCountKey} = require('./utils');
|
const {nudgeCallCounts, roundTripTime} = require('./utils');
|
||||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
||||||
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
||||||
[];
|
[];
|
||||||
@@ -30,10 +30,10 @@ module.exports = function(srf, logger) {
|
|||||||
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
|
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
|
||||||
})
|
})
|
||||||
.on('error', async(err, req) => {
|
.on('error', async(err, req) => {
|
||||||
const {account_sid} = req.locals;
|
const {account_sid, account} = req.locals;
|
||||||
const {writeAlerts, AlertType} = req.srf.locals;
|
const {writeAlerts, AlertType} = req.srf.locals;
|
||||||
if (account_sid) {
|
if (account_sid) {
|
||||||
let opts = {account_sid};
|
let opts = {account_sid, service_provider_sid: account.service_provider_sid};
|
||||||
if (err.code === 'ECONNREFUSED') {
|
if (err.code === 'ECONNREFUSED') {
|
||||||
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
|
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,8 @@ module.exports = function(srf, logger) {
|
|||||||
lookupAppByTeamsTenant,
|
lookupAppByTeamsTenant,
|
||||||
lookupAccountBySipRealm,
|
lookupAccountBySipRealm,
|
||||||
lookupAccountBySid,
|
lookupAccountBySid,
|
||||||
lookupAccountCapacitiesBySid
|
lookupAccountCapacitiesBySid,
|
||||||
|
queryCallLimits
|
||||||
} = srf.locals.dbHelpers;
|
} = srf.locals.dbHelpers;
|
||||||
const {stats, writeCdrs} = srf.locals;
|
const {stats, writeCdrs} = srf.locals;
|
||||||
const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, {
|
const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, {
|
||||||
@@ -134,14 +135,20 @@ module.exports = function(srf, logger) {
|
|||||||
const identifyAccount = async(req, res, next) => {
|
const identifyAccount = async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const {siprec, callId} = req.locals;
|
const {siprec, callId} = req.locals;
|
||||||
const {wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = req.srf.locals;
|
const {getSPForAccount, wasOriginatedFromCarrier, getApplicationForDidAndCarrier, stats} = req.srf.locals;
|
||||||
|
const startAt = process.hrtime();
|
||||||
const {
|
const {
|
||||||
fromCarrier,
|
fromCarrier,
|
||||||
gateway,
|
gateway,
|
||||||
account_sid,
|
account_sid,
|
||||||
application_sid,
|
application_sid,
|
||||||
account
|
service_provider_sid,
|
||||||
|
account,
|
||||||
|
error
|
||||||
} = await wasOriginatedFromCarrier(req);
|
} = await wasOriginatedFromCarrier(req);
|
||||||
|
const rtt = roundTripTime(startAt);
|
||||||
|
stats.histogram('app.mysql.response_time', rtt, [
|
||||||
|
'query:wasOriginatedFromCarrier', 'app:sbc-inbound']);
|
||||||
/**
|
/**
|
||||||
* calls come from 3 sources:
|
* calls come from 3 sources:
|
||||||
* (1) A carrier
|
* (1) A carrier
|
||||||
@@ -149,6 +156,13 @@ module.exports = function(srf, logger) {
|
|||||||
* (3) A SIP user
|
* (3) A SIP user
|
||||||
*/
|
*/
|
||||||
if (fromCarrier) {
|
if (fromCarrier) {
|
||||||
|
if (error) {
|
||||||
|
return res.send(503, {
|
||||||
|
headers: {
|
||||||
|
'X-Reason': error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!gateway) {
|
if (!gateway) {
|
||||||
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
|
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
|
||||||
return res.send(404, 'Number Not Provisioned');
|
return res.send(404, 'Number Not Provisioned');
|
||||||
@@ -173,6 +187,7 @@ module.exports = function(srf, logger) {
|
|||||||
gateway,
|
gateway,
|
||||||
voip_carrier_sid: gateway.voip_carrier_sid,
|
voip_carrier_sid: gateway.voip_carrier_sid,
|
||||||
application_sid: sid || gateway.application_sid,
|
application_sid: sid || gateway.application_sid,
|
||||||
|
service_provider_sid,
|
||||||
account_sid,
|
account_sid,
|
||||||
account,
|
account,
|
||||||
...req.locals
|
...req.locals
|
||||||
@@ -188,12 +203,13 @@ module.exports = function(srf, logger) {
|
|||||||
res.send(404, {headers: {'X-Reason': 'no configured application'}});
|
res.send(404, {headers: {'X-Reason': 'no configured application'}});
|
||||||
return req.srf.endSession(req);
|
return req.srf.endSession(req);
|
||||||
}
|
}
|
||||||
|
const service_provider_sid = await getSPForAccount(app.account_sid);
|
||||||
req.locals = {
|
req.locals = {
|
||||||
originator: 'teams',
|
originator: 'teams',
|
||||||
carrier: 'Microsoft Teams',
|
carrier: 'Microsoft Teams',
|
||||||
msTeamsTenantFqdn: uri.host,
|
msTeamsTenantFqdn: uri.host,
|
||||||
account_sid: app.account_sid,
|
account_sid: app.account_sid,
|
||||||
|
service_provider_sid,
|
||||||
...req.locals
|
...req.locals
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -219,18 +235,22 @@ module.exports = function(srf, logger) {
|
|||||||
return req.srf.endSession(req);
|
return req.srf.endSession(req);
|
||||||
}
|
}
|
||||||
req.locals = {
|
req.locals = {
|
||||||
|
service_provider_sid: account.service_provider_sid,
|
||||||
account_sid: account.account_sid,
|
account_sid: account.account_sid,
|
||||||
account,
|
account,
|
||||||
|
application_sid: account.device_calling_application_sid,
|
||||||
webhook_secret: account.webhook_secret,
|
webhook_secret: account.webhook_secret,
|
||||||
...req.locals
|
...req.locals
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
assert(req.locals.service_provider_sid);
|
||||||
assert(req.locals.account_sid);
|
assert(req.locals.account_sid);
|
||||||
req.locals.cdr.account_sid = req.locals.account_sid;
|
req.locals.cdr.account_sid = req.locals.account_sid;
|
||||||
|
|
||||||
if (!req.locals.account) {
|
if (!req.locals.account) {
|
||||||
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
|
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
|
||||||
}
|
}
|
||||||
|
req.locals.cdr.service_provider_sid = req.locals.account?.service_provider_sid;
|
||||||
|
|
||||||
if (!req.locals.account.is_active) {
|
if (!req.locals.account.is_active) {
|
||||||
stats.increment('sbc.terminations', ['sipStatus:503']);
|
stats.increment('sbc.terminations', ['sipStatus:503']);
|
||||||
@@ -242,7 +262,11 @@ module.exports = function(srf, logger) {
|
|||||||
delete req.locals.cdr;
|
delete req.locals.cdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.locals.logger = logger.child({callId: req.get('Call-ID'), account_sid: req.locals.account_sid});
|
req.locals.logger = logger.child({
|
||||||
|
callId: req.get('Call-ID'),
|
||||||
|
service_provider_sid: req.locals.service_provider_sid,
|
||||||
|
account_sid: req.locals.account_sid
|
||||||
|
});
|
||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -253,35 +277,36 @@ module.exports = function(srf, logger) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkLimits = async(req, res, next) => {
|
const checkLimits = async(req, res, next) => {
|
||||||
if (!process.env.JAMBONES_HOSTING && !process.env.JAMBONES_TRACK_ACCOUNT_CALLS) return next(); // skip
|
const trackingOn = process.env.JAMBONES_TRACK_ACCOUNT_CALLS ||
|
||||||
|
process.env.JAMBONES_TRACK_SP_CALLS ||
|
||||||
|
process.env.JAMBONES_TRACK_APP_CALLS;
|
||||||
|
if (!process.env.JAMBONES_HOSTING && !trackingOn) return next(); // skip
|
||||||
|
|
||||||
const {incrKey, decrKey} = req.srf.locals.realtimeDbHelpers;
|
const {incrKey, decrKey} = req.srf.locals.realtimeDbHelpers;
|
||||||
const {logger, account_sid} = req.locals;
|
const {logger, account_sid, account, service_provider_sid, application_sid} = req.locals;
|
||||||
const {writeCallCount, writeAlerts, AlertType} = req.srf.locals;
|
const {writeCallCount, writeCallCountSP, writeCallCountApp, writeAlerts, AlertType} = req.srf.locals;
|
||||||
assert(account_sid);
|
assert(account_sid);
|
||||||
const key = makeCallCountKey(account_sid);
|
assert(service_provider_sid);
|
||||||
|
|
||||||
/* decrement count if INVITE is later rejected */
|
/* decrement count if INVITE is later rejected */
|
||||||
res.once('end', ({status}) => {
|
res.once('end', async({status}) => {
|
||||||
if (status > 200) {
|
if (status > 200) {
|
||||||
decrKey(key)
|
nudgeCallCounts(logger, {
|
||||||
.then((count) => {
|
service_provider_sid,
|
||||||
logger.info({key}, `after rejection there are ${count} active calls for this account`);
|
account_sid,
|
||||||
debug({key}, `after rejection there are ${count} active calls for this account`);
|
application_sid
|
||||||
return count;
|
}, decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||||
})
|
.catch((err) => logger.error(err, 'Error decrementing call counts'));
|
||||||
.then((count) => writeCallCount({account_sid, calls_in_progress: count}))
|
|
||||||
.catch((err) => logger.error({err}, 'checkLimits: decrKey err'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* increment the call count */
|
/* increment the call count */
|
||||||
const calls = await incrKey(key);
|
const {callsSP, calls} = await nudgeCallCounts(logger, {
|
||||||
writeCallCount({account_sid, calls_in_progress: calls})
|
service_provider_sid,
|
||||||
.then(() => logger.info(`checkLimits: after incrementing there are ${calls} active calls for this account`))
|
account_sid,
|
||||||
.catch((err) => logger.error({err}, 'checkLimits: error writing call count'));
|
application_sid
|
||||||
if (!process.env.JAMBONES_HOSTING) return next();
|
}, incrKey, {writeCallCountSP, writeCallCount, writeCallCountApp});
|
||||||
|
|
||||||
/* compare to account's limit, though avoid db hit when call count is low */
|
/* compare to account's limit, though avoid db hit when call count is low */
|
||||||
const minLimit = process.env.MIN_CALL_LIMIT ?
|
const minLimit = process.env.MIN_CALL_LIMIT ?
|
||||||
@@ -290,20 +315,59 @@ module.exports = function(srf, logger) {
|
|||||||
logger.debug(`checkLimits: call count is now ${calls}, limit is ${minLimit}`);
|
logger.debug(`checkLimits: call count is now ${calls}, limit is ${minLimit}`);
|
||||||
if (calls <= minLimit) return next();
|
if (calls <= minLimit) return next();
|
||||||
|
|
||||||
const capacities = await lookupAccountCapacitiesBySid(account_sid);
|
if (process.env.JAMBONES_HOSTING) {
|
||||||
const limit = capacities.find((c) => c.category == 'voice_call_session');
|
const accountCapacities = await lookupAccountCapacitiesBySid(account_sid);
|
||||||
if (!limit) throw new Error('no account_capacities found');
|
const accountLimit = accountCapacities.find((c) => c.category == 'voice_call_session');
|
||||||
const limit_sessions = limit.quantity;
|
if (accountLimit) {
|
||||||
if (calls > limit_sessions) {
|
/* check account limit */
|
||||||
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
|
const limit_sessions = accountLimit.quantity;
|
||||||
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
|
if (calls > limit_sessions) {
|
||||||
writeAlerts({
|
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
|
||||||
alert_type: AlertType.CALL_LIMIT,
|
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
|
||||||
account_sid,
|
writeAlerts({
|
||||||
count: limit_sessions
|
alert_type: AlertType.ACCOUNT_CALL_LIMIT,
|
||||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
service_provider_sid: account.service_provider_sid,
|
||||||
res.send(503, 'Maximum Calls In Progress');
|
account_sid,
|
||||||
return req.srf.endSession(req);
|
count: limit_sessions
|
||||||
|
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||||
|
res.send(503, 'Maximum Calls In Progress');
|
||||||
|
return req.srf.endSession(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (trackingOn) {
|
||||||
|
const {account_limit, sp_limit} = await queryCallLimits(service_provider_sid, account_sid);
|
||||||
|
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS && account_limit > 0 && calls > account_limit) {
|
||||||
|
logger.info({calls, account_limit}, 'checkLimits: account limits exceeded');
|
||||||
|
writeAlerts({
|
||||||
|
alert_type: AlertType.ACCOUNT_CALL_LIMIT,
|
||||||
|
service_provider_sid: service_provider_sid,
|
||||||
|
account_sid,
|
||||||
|
count: calls
|
||||||
|
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||||
|
res.send(503, 'Max Account Calls In Progress', {
|
||||||
|
headers: {
|
||||||
|
'X-Account-Sid': account_sid,
|
||||||
|
'X-Call-Limit': account_limit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return req.srf.endSession(req);
|
||||||
|
}
|
||||||
|
if (process.env.JAMBONES_TRACK_SP_CALLS && sp_limit > 0 && callsSP > sp_limit) {
|
||||||
|
logger.info({callsSP, sp_limit}, 'checkLimits: service provider limits exceeded');
|
||||||
|
writeAlerts({
|
||||||
|
alert_type: AlertType.SP_CALL_LIMIT,
|
||||||
|
service_provider_sid: service_provider_sid,
|
||||||
|
count: callsSP
|
||||||
|
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||||
|
res.send(503, 'Max Service Provider Calls In Progress', {
|
||||||
|
headers: {
|
||||||
|
'X-Service-Provider-Sid': service_provider_sid,
|
||||||
|
'X-Call-Limit': sp_limit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return req.srf.endSession(req);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+115
-12
@@ -12,10 +12,12 @@ const getAppserver = (srf) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||||
|
const rtpCopy = JSON.parse(JSON.stringify(rtpCharacteristics));
|
||||||
|
const srtpCopy = JSON.parse(JSON.stringify(srtpCharacteristics));
|
||||||
const from = req.getParsedHeader('from');
|
const from = req.getParsedHeader('from');
|
||||||
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
|
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
||||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCharacteristics;
|
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCharacteristics;
|
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||||
|
|
||||||
/* webrtc clients (e.g. sipjs) send DMTF via SIP INFO */
|
/* webrtc clients (e.g. sipjs) send DMTF via SIP INFO */
|
||||||
if ((srcIsUsingSrtp || dstIsUsingSrtp) && !teams) {
|
if ((srcIsUsingSrtp || dstIsUsingSrtp) && !teams) {
|
||||||
@@ -24,7 +26,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
|||||||
}
|
}
|
||||||
const common = {
|
const common = {
|
||||||
'call-id': req.get('Call-ID'),
|
'call-id': req.get('Call-ID'),
|
||||||
'replace': ['origin', 'session-connection']
|
'replace': ['origin', 'session-connection'],
|
||||||
|
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
common,
|
common,
|
||||||
@@ -39,11 +42,16 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SdpWantsSDES = (sdp) => {
|
||||||
|
return /m=audio.*\s+RTP\/SAVP/.test(sdp);
|
||||||
|
};
|
||||||
const SdpWantsSrtp = (sdp) => {
|
const SdpWantsSrtp = (sdp) => {
|
||||||
return /m=audio.*SAVP/.test(sdp);
|
return /m=audio.*SAVP/.test(sdp);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeCallCountKey = (sid) => `${sid}:incalls`;
|
const makeAccountCallCountKey = (sid) => `incalls:account:${sid}`;
|
||||||
|
const makeSPCallCountKey = (sid) => `incalls:sp:${sid}:`;
|
||||||
|
const makeAppCallCountKey = (sid) => `incalls:app${sid}:`;
|
||||||
|
|
||||||
const normalizeDID = (tel) => {
|
const normalizeDID = (tel) => {
|
||||||
const regex = /^\+(\d+)$/;
|
const regex = /^\+(\d+)$/;
|
||||||
@@ -67,6 +75,26 @@ const systemHealth = async(redisClient, ping, getCount) => {
|
|||||||
return getCount();
|
return getCount();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const doListen = (logger, app, port, resolve) => {
|
||||||
|
return app.listen(port, () => {
|
||||||
|
logger.info(`Health check server listening on http://localhost:${port}`);
|
||||||
|
resolve(app);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleErrors = (logger, app, resolve, reject, e) => {
|
||||||
|
if (e.code === 'EADDRINUSE' &&
|
||||||
|
process.env.HTTP_PORT_MAX &&
|
||||||
|
e.port < process.env.HTTP_PORT_MAX) {
|
||||||
|
|
||||||
|
logger.info(`Health check server failed to bind port on ${e.port}, will try next port`);
|
||||||
|
const server = doListen(logger, app, ++e.port, resolve);
|
||||||
|
server.on('error', handleErrors.bind(null, logger, app, resolve, reject));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const createHealthCheckApp = (port, logger) => {
|
const createHealthCheckApp = (port, logger) => {
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -74,22 +102,97 @@ const createHealthCheckApp = (port, logger) => {
|
|||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
app.listen(port, () => {
|
const server = doListen(logger, app, port, resolve);
|
||||||
logger.info(`Health check server started at http://localhost:${port}`);
|
server.on('error', handleErrors.bind(null, logger, app, resolve, reject));
|
||||||
resolve(app);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
|
||||||
|
const {service_provider_sid, account_sid, application_sid} = sids;
|
||||||
|
const {writeCallCount, writeCallCountSP, writeCallCountApp} = writers;
|
||||||
|
const nudges = [];
|
||||||
|
const writes = [];
|
||||||
|
|
||||||
|
if (process.env.JAMBONES_TRACK_SP_CALLS) {
|
||||||
|
const key = makeSPCallCountKey(service_provider_sid);
|
||||||
|
nudges.push(nudgeOperator(key));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nudges.push(() => Promise.resolve(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) {
|
||||||
|
const key = makeAccountCallCountKey(account_sid);
|
||||||
|
nudges.push(nudgeOperator(key));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nudges.push(() => Promise.resolve(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) {
|
||||||
|
const key = makeAppCallCountKey(application_sid);
|
||||||
|
nudges.push(nudgeOperator(key));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nudges.push(() => Promise.resolve(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [callsSP, calls, callsApp] = await Promise.all(nudges);
|
||||||
|
logger.debug({
|
||||||
|
calls, callsSP, callsApp,
|
||||||
|
service_provider_sid, account_sid, application_sid}, 'call counts after adjustment');
|
||||||
|
if (process.env.JAMBONES_TRACK_SP_CALLS) {
|
||||||
|
writes.push(writeCallCountSP({service_provider_sid, calls_in_progress: callsSP}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) {
|
||||||
|
writes.push(writeCallCount({service_provider_sid, account_sid, calls_in_progress: calls}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) {
|
||||||
|
writes.push(writeCallCountApp({service_provider_sid, account_sid, application_sid, calls_in_progress: callsApp}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write the call counts to the database */
|
||||||
|
Promise.all(writes).catch((err) => logger.error({err}, 'Error writing call counts'));
|
||||||
|
|
||||||
|
return {callsSP, calls, callsApp};
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err, 'error incrementing call counts');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {callsSP: null, calls: null, callsApp: null};
|
||||||
|
};
|
||||||
|
|
||||||
|
const roundTripTime = (startAt) => {
|
||||||
|
const diff = process.hrtime(startAt);
|
||||||
|
const time = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||||
|
return time.toFixed(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseConnectionIp = (sdp) => {
|
||||||
|
const regex = /c=IN IP4 ([0-9.]+)/;
|
||||||
|
const arr = regex.exec(sdp);
|
||||||
|
return arr ? arr[1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isWSS,
|
isWSS,
|
||||||
SdpWantsSrtp,
|
SdpWantsSrtp,
|
||||||
|
SdpWantsSDES,
|
||||||
getAppserver,
|
getAppserver,
|
||||||
makeRtpEngineOpts,
|
makeRtpEngineOpts,
|
||||||
makeCallCountKey,
|
makeAccountCallCountKey,
|
||||||
|
makeSPCallCountKey,
|
||||||
|
makeAppCallCountKey,
|
||||||
normalizeDID,
|
normalizeDID,
|
||||||
equalsIgnoreOrder,
|
equalsIgnoreOrder,
|
||||||
systemHealth,
|
systemHealth,
|
||||||
createHealthCheckApp
|
createHealthCheckApp,
|
||||||
|
nudgeCallCounts,
|
||||||
|
roundTripTime,
|
||||||
|
parseConnectionIp
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+5246
-1890
File diff suppressed because it is too large
Load Diff
+11
-13
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sbc-inbound",
|
"name": "sbc-inbound",
|
||||||
"version": "v0.7.6",
|
"version": "v0.8.0",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
@@ -20,30 +20,28 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app",
|
"start": "node app",
|
||||||
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 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_LOGLEVEL=info DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
|
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 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_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
|
||||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||||
"jslint": "eslint app.js lib"
|
"jslint": "eslint app.js lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jambonz/db-helpers": "^0.6.18",
|
"@jambonz/db-helpers": "^0.7.4",
|
||||||
"@jambonz/http-authenticator": "^0.2.1",
|
"@jambonz/http-authenticator": "^0.2.2",
|
||||||
"@jambonz/http-health-check": "^0.0.1",
|
"@jambonz/http-health-check": "^0.0.1",
|
||||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
"@jambonz/realtimedb-helpers": "^0.6.3",
|
||||||
"@jambonz/rtpengine-utils": "^0.3.1",
|
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||||
"@jambonz/siprec-client-utils": "^0.1.4",
|
"@jambonz/siprec-client-utils": "^0.2.4",
|
||||||
"@jambonz/stats-collector": "^0.1.6",
|
"@jambonz/stats-collector": "^0.1.6",
|
||||||
"@jambonz/time-series": "^0.1.12",
|
"@jambonz/time-series": "^0.2.5",
|
||||||
"aws-sdk": "^2.1152.0",
|
"aws-sdk": "^2.1261.0",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"cidr-matcher": "^2.1.1",
|
"cidr-matcher": "^2.1.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||||
"drachtio-srf": "^4.5.1",
|
"drachtio-srf": "^4.5.21",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"pino": "^7.11.0",
|
"pino": "^7.11.0",
|
||||||
"sdp-transform": "^2.14.1",
|
"verify-aws-sns-signature": "^0.1.0",
|
||||||
"uuid": "^8.3.2",
|
|
||||||
"verify-aws-sns-signature": "^0.0.7",
|
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+93
-18
@@ -4,6 +4,8 @@ SET FOREIGN_KEY_CHECKS=0;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS account_static_ips;
|
DROP TABLE IF EXISTS account_static_ips;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS account_limits;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS account_products;
|
DROP TABLE IF EXISTS account_products;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS account_subscriptions;
|
DROP TABLE IF EXISTS account_subscriptions;
|
||||||
@@ -18,20 +20,28 @@ DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS lcr_routes;
|
DROP TABLE IF EXISTS lcr_routes;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS password_settings;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS predefined_carriers;
|
DROP TABLE IF EXISTS predefined_carriers;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS account_offers;
|
DROP TABLE IF EXISTS account_offers;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS products;
|
DROP TABLE IF EXISTS products;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS schema_version;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS api_keys;
|
DROP TABLE IF EXISTS api_keys;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS sbc_addresses;
|
DROP TABLE IF EXISTS sbc_addresses;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS service_provider_limits;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS signup_history;
|
DROP TABLE IF EXISTS signup_history;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS smpp_addresses;
|
DROP TABLE IF EXISTS smpp_addresses;
|
||||||
@@ -65,6 +75,15 @@ private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
|||||||
PRIMARY KEY (account_static_ip_sid)
|
PRIMARY KEY (account_static_ip_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE account_limits
|
||||||
|
(
|
||||||
|
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
account_sid CHAR(36) NOT NULL,
|
||||||
|
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (account_limits_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE account_subscriptions
|
CREATE TABLE account_subscriptions
|
||||||
(
|
(
|
||||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -119,6 +138,13 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
|
|||||||
PRIMARY KEY (lcr_route_sid)
|
PRIMARY KEY (lcr_route_sid)
|
||||||
) COMMENT='Least cost routing table';
|
) COMMENT='Least cost routing table';
|
||||||
|
|
||||||
|
CREATE TABLE password_settings
|
||||||
|
(
|
||||||
|
min_password_length INTEGER NOT NULL DEFAULT 8,
|
||||||
|
require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE predefined_carriers
|
CREATE TABLE predefined_carriers
|
||||||
(
|
(
|
||||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -148,6 +174,20 @@ predefined_carrier_sid CHAR(36) NOT NULL,
|
|||||||
PRIMARY KEY (predefined_sip_gateway_sid)
|
PRIMARY KEY (predefined_sip_gateway_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE predefined_smpp_gateways
|
||||||
|
(
|
||||||
|
predefined_smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. ',
|
||||||
|
port INTEGER NOT NULL DEFAULT 2775 COMMENT 'smpp signaling port',
|
||||||
|
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound SMS from the gateway',
|
||||||
|
outbound BOOLEAN NOT NULL COMMENT 'i',
|
||||||
|
netmask INTEGER NOT NULL DEFAULT 32,
|
||||||
|
is_primary BOOLEAN NOT NULL DEFAULT 1,
|
||||||
|
use_tls BOOLEAN DEFAULT 0,
|
||||||
|
predefined_carrier_sid CHAR(36) NOT NULL,
|
||||||
|
PRIMARY KEY (predefined_smpp_gateway_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE products
|
CREATE TABLE products
|
||||||
(
|
(
|
||||||
product_sid CHAR(36) NOT NULL UNIQUE ,
|
product_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -174,6 +214,11 @@ stripe_product_id VARCHAR(56) NOT NULL,
|
|||||||
PRIMARY KEY (account_offer_sid)
|
PRIMARY KEY (account_offer_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE schema_version
|
||||||
|
(
|
||||||
|
version VARCHAR(16)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE api_keys
|
CREATE TABLE api_keys
|
||||||
(
|
(
|
||||||
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -205,6 +250,15 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
|||||||
PRIMARY KEY (ms_teams_tenant_sid)
|
PRIMARY KEY (ms_teams_tenant_sid)
|
||||||
) COMMENT='A Microsoft Teams customer tenant';
|
) COMMENT='A Microsoft Teams customer tenant';
|
||||||
|
|
||||||
|
CREATE TABLE service_provider_limits
|
||||||
|
(
|
||||||
|
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
service_provider_sid CHAR(36) NOT NULL,
|
||||||
|
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (service_provider_limits_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE signup_history
|
CREATE TABLE signup_history
|
||||||
(
|
(
|
||||||
email VARCHAR(255) NOT NULL,
|
email VARCHAR(255) NOT NULL,
|
||||||
@@ -260,6 +314,7 @@ email_activation_code VARCHAR(16),
|
|||||||
email_validated BOOLEAN NOT NULL DEFAULT false,
|
email_validated BOOLEAN NOT NULL DEFAULT false,
|
||||||
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
||||||
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
PRIMARY KEY (user_sid)
|
PRIMARY KEY (user_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -287,6 +342,9 @@ smpp_password VARCHAR(64),
|
|||||||
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
||||||
smpp_inbound_system_id VARCHAR(255),
|
smpp_inbound_system_id VARCHAR(255),
|
||||||
smpp_inbound_password VARCHAR(64),
|
smpp_inbound_password VARCHAR(64),
|
||||||
|
register_from_user VARCHAR(128),
|
||||||
|
register_from_domain VARCHAR(255),
|
||||||
|
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||||
PRIMARY KEY (voip_carrier_sid)
|
PRIMARY KEY (voip_carrier_sid)
|
||||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||||
|
|
||||||
@@ -395,6 +453,11 @@ disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
|
|||||||
trial_end_date DATETIME,
|
trial_end_date DATETIME,
|
||||||
deactivated_reason VARCHAR(255),
|
deactivated_reason VARCHAR(255),
|
||||||
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
||||||
|
subspace_client_id VARCHAR(255),
|
||||||
|
subspace_client_secret VARCHAR(255),
|
||||||
|
subspace_sip_teleport_id VARCHAR(255),
|
||||||
|
subspace_sip_teleport_destinations VARCHAR(255),
|
||||||
|
siprec_hook_sid CHAR(36),
|
||||||
PRIMARY KEY (account_sid)
|
PRIMARY KEY (account_sid)
|
||||||
) COMMENT='An enterprise that uses the platform for comm services';
|
) COMMENT='An enterprise that uses the platform for comm services';
|
||||||
|
|
||||||
@@ -402,24 +465,31 @@ CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_
|
|||||||
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
||||||
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
|
CREATE INDEX account_sid_idx ON account_limits (account_sid);
|
||||||
|
ALTER TABLE account_limits ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
|
||||||
|
|
||||||
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
||||||
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
||||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
||||||
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
||||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
||||||
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
|
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
|
||||||
|
|
||||||
|
CREATE INDEX predefined_smpp_gateway_sid_idx ON predefined_smpp_gateways (predefined_smpp_gateway_sid);
|
||||||
|
CREATE INDEX predefined_carrier_sid_idx ON predefined_smpp_gateways (predefined_carrier_sid);
|
||||||
|
ALTER TABLE predefined_smpp_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk_1 (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
|
||||||
|
|
||||||
CREATE INDEX product_sid_idx ON products (product_sid);
|
CREATE INDEX product_sid_idx ON products (product_sid);
|
||||||
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
|
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
|
||||||
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
|
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
|
||||||
@@ -429,14 +499,14 @@ ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REF
|
|||||||
|
|
||||||
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
||||||
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
||||||
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
||||||
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
||||||
|
|
||||||
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
||||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
@@ -450,41 +520,44 @@ ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_
|
|||||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||||
|
CREATE INDEX service_provider_sid_idx ON service_provider_limits (service_provider_sid);
|
||||||
|
ALTER TABLE service_provider_limits ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid) ON DELETE CASCADE;
|
||||||
|
|
||||||
CREATE INDEX email_idx ON signup_history (email);
|
CREATE INDEX email_idx ON signup_history (email);
|
||||||
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
||||||
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||||
|
|
||||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||||
CREATE INDEX email_idx ON users (email);
|
CREATE INDEX email_idx ON users (email);
|
||||||
CREATE INDEX phone_idx ON users (phone);
|
CREATE INDEX phone_idx ON users (phone);
|
||||||
CREATE INDEX account_sid_idx ON users (account_sid);
|
CREATE INDEX account_sid_idx ON users (account_sid);
|
||||||
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
||||||
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
||||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||||
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
@@ -497,12 +570,12 @@ CREATE INDEX number_idx ON phone_numbers (number);
|
|||||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||||
|
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||||
|
|
||||||
@@ -518,10 +591,10 @@ CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
|||||||
|
|
||||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
||||||
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_12 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||||
|
|
||||||
@@ -537,7 +610,7 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
|
|||||||
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
||||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_10 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||||
|
|
||||||
@@ -545,4 +618,6 @@ ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hoo
|
|||||||
|
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
|
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS=1;
|
SET FOREIGN_KEY_CHECKS=1;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2
|
|||||||
|
|
||||||
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
|
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
|
||||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||||
|
insert into service_provider_limits (service_provider_limits_sid, service_provider_sid, category, quantity)
|
||||||
|
values ('a79d3ade-e0da-4461-80f3-7c73f01e18b4', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'voice_call_session', 1);
|
||||||
|
|
||||||
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
|
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
|
||||||
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
|
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
|
||||||
@@ -38,6 +40,7 @@ insert into account_subscriptions(account_subscription_sid, account_sid, pending
|
|||||||
values ('73bbcc5d-512f-4cea-8535-9a6e3d2bd19d','d7cc37cb-d152-49ef-a51b-485f6e917089',0);
|
values ('73bbcc5d-512f-4cea-8535-9a6e3d2bd19d','d7cc37cb-d152-49ef-a51b-485f6e917089',0);
|
||||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||||
values ('92f137f7-4bc3-4157-b096-6817e54b1874', '73bbcc5d-512f-4cea-8535-9a6e3d2bd19d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 0);
|
values ('92f137f7-4bc3-4157-b096-6817e54b1874', '73bbcc5d-512f-4cea-8535-9a6e3d2bd19d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 0);
|
||||||
|
insert into account_limits(account_limits_sid, account_sid, category, quantity) values('a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d', 'd7cc37cb-d152-49ef-a51b-485f6e917089', 'voice_call_session', 0);
|
||||||
|
|
||||||
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('9b1abdc7-0220-4964-bc66-32b5c70cd9ab', 'westco', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('9b1abdc7-0220-4964-bc66-32b5c70cd9ab', 'westco', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
||||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||||
@@ -64,4 +67,25 @@ insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_s
|
|||||||
values ('d458bf7a-bcea-47b2-ac96-66dfc9c5c220', '150822233*', '287c1452-620d-4195-9f19-c9814ef90d78', 'ed649e33-e771-403a-8c99-1780eabbc803');
|
values ('d458bf7a-bcea-47b2-ac96-66dfc9c5c220', '150822233*', '287c1452-620d-4195-9f19-c9814ef90d78', 'ed649e33-e771-403a-8c99-1780eabbc803');
|
||||||
|
|
||||||
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
|
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
|
||||||
values ('f7ad205d-b92f-4363-8160-f8b5216b40d3', '15083871234', '287c1452-620d-4195-9f19-c9814ef90d78', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
values ('f7ad205d-b92f-4363-8160-f8b5216b40d3', '15083871234', '287c1452-620d-4195-9f19-c9814ef90d78', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
||||||
|
|
||||||
|
-- two accounts that both have the same carrier with default routing (ambiguity test)
|
||||||
|
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||||
|
values ('239d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account B1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'echo2.sip.jambonz.org');
|
||||||
|
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||||
|
values ('909d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account B2', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'foxtrot.sip.jambonz.org');
|
||||||
|
|
||||||
|
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
|
||||||
|
values ('8843e39f-4346-4218-8434-a53130e8be49', 'test', '239d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
|
||||||
|
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
|
||||||
|
values ('7743e39f-4346-4218-8434-a53130e8be49', 'test', '909d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
|
||||||
|
|
||||||
|
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, account_sid, application_sid)
|
||||||
|
values ('731abdc7-0220-4964-bc66-32b5c70cd9ab', 'twilio-1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '239d7d49-b3e4-4fdb-9d66-661149f717e8', '8843e39f-4346-4218-8434-a53130e8be49');
|
||||||
|
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, account_sid, application_sid)
|
||||||
|
values ('987abdc7-0220-4964-bc66-32b5c70cd9ab', 'twilio-2', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '909d7d49-b3e4-4fdb-9d66-661149f717e8', '7743e39f-4346-4218-8434-a53130e8be49');
|
||||||
|
|
||||||
|
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||||
|
values ('664a5339-c62c-4075-9e19-f4de70a96597', '731abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.40', true, false);
|
||||||
|
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||||
|
values ('554a5339-c62c-4075-9e19-f4de70a96597', '987abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.40', true, false);
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
<scenario name="UAC with media">
|
||||||
|
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||||
|
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
INVITE sip:+15083871234@172.38.0.10 SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||||
|
To: <sip:15083871234@172.38.0.10>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 INVITE
|
||||||
|
Contact: sip:sipp@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-pcap-carrier-fail-ambiguous
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[local_ip_type] [local_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [auto_media_port] RTP/AVP 8 101
|
||||||
|
a=rtpmap:8 PCMA/8000
|
||||||
|
a=rtpmap:101 telephone-event/8000
|
||||||
|
a=fmtp:101 0-11,16
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="100" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
|
||||||
|
<!-- are saved and used for following messages sent. Useful to test -->
|
||||||
|
<!-- against stateful SIP proxies/B2BUAs. -->
|
||||||
|
<recv response="503" rtd="true" crlf="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||||
|
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
ACK sip:15083871234@172.38.0.10 SIP/2.0
|
||||||
|
[last_Via]
|
||||||
|
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||||
|
To: <sip:15083871234@172.38.0.10>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 ACK
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-pcap-carrier-fail-ambiguous
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- definition of the response time repartition table (unit is ms) -->
|
||||||
|
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
|
||||||
|
|
||||||
|
<!-- definition of the call length repartition table (unit is ms) -->
|
||||||
|
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
+10
-5
@@ -24,6 +24,7 @@ function waitFor(ms) {
|
|||||||
test('incoming call tests', async(t) => {
|
test('incoming call tests', async(t) => {
|
||||||
const {srf} = require('../app');
|
const {srf} = require('../app');
|
||||||
const { queryCdrs } = srf.locals;
|
const { queryCdrs } = srf.locals;
|
||||||
|
let res;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await connect(srf);
|
await connect(srf);
|
||||||
@@ -32,10 +33,9 @@ test('incoming call tests', async(t) => {
|
|||||||
t.ok(obj.calls === 0, 'HTTP GET / works (current call count)')
|
t.ok(obj.calls === 0, 'HTTP GET / works (current call count)')
|
||||||
obj = await getJSON('http://127.0.0.1:3050/system-health');
|
obj = await getJSON('http://127.0.0.1:3050/system-health');
|
||||||
t.ok(obj.calls === 0, 'HTTP GET /system-health works (health check)')
|
t.ok(obj.calls === 0, 'HTTP GET /system-health works (health check)')
|
||||||
|
|
||||||
await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
||||||
t.pass('incoming call from carrier completed successfully');
|
t.pass('incoming call from carrier completed successfully');
|
||||||
|
|
||||||
await sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
await sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
||||||
t.pass('incoming call from account-level carrier completed successfully');
|
t.pass('incoming call from account-level carrier completed successfully');
|
||||||
|
|
||||||
@@ -58,11 +58,16 @@ test('incoming call tests', async(t) => {
|
|||||||
t.pass('handles in-dialog requests');
|
t.pass('handles in-dialog requests');
|
||||||
|
|
||||||
await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
|
await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
|
||||||
t.pass('rejects incoming call with 503 when max calls reached')
|
t.pass('rejects incoming call with 503 when max calls per account reached');
|
||||||
|
|
||||||
|
/* switch off this env for remaining tests (JAMBONES_HOSTING is for Saas sts) */
|
||||||
|
delete process.env.JAMBONES_HOSTING;
|
||||||
|
await sippUac('uac-pcap-carrier-fail-ambiguous.xml', '172.38.0.40');
|
||||||
|
t.pass('rejects incoming call with 503 when multiple accounts have same carrier witrh default routing')
|
||||||
|
|
||||||
await waitFor(10);
|
await waitFor(12);
|
||||||
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
||||||
console.log(`cdrs: ${JSON.stringify(res)}`);
|
//console.log(`cdrs: ${JSON.stringify(res)}`);
|
||||||
t.ok(7 === res.total, 'successfully wrote 7 cdrs for calls');
|
t.ok(7 === res.total, 'successfully wrote 7 cdrs for calls');
|
||||||
|
|
||||||
srf.disconnect();
|
srf.disconnect();
|
||||||
|
|||||||
Reference in New Issue
Block a user