Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Horton
dcd8b378b2 use docker/build-push-action@v4 2023-04-10 08:49:44 -04:00
Quan HL
552a4e9fd1 feat: upload docker image to docker hub 2023-04-10 15:12:50 +07:00
36 changed files with 186 additions and 593 deletions

View File

@@ -5,6 +5,9 @@ on:
tags:
- '*'
env:
IMAGE_NAME: feature-server
jobs:
push:
@@ -15,10 +18,15 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: prepare tag
id: prepare_tag
run: |
IMAGE_ID=$GITHUB_REPOSITORY
IMAGE_ID=jambonz/$IMAGE_NAME
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
@@ -32,21 +40,12 @@ jobs:
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.prepare_tag.outputs.image_id }}:${{ steps.prepare_tag.outputs.version }}
tags: jambonz/$IMAGE_NAME:$VERSION
build-args: |
GITHUB_REPOSITORY=$GITHUB_REPOSITORY
GITHUB_REF=$GITHUB_REF

View File

@@ -1,4 +1,4 @@
FROM --platform=linux/amd64 node:18.15-alpine3.16 as base
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
RUN apk --update --no-cache add --virtual .builds-deps build-base python3

View File

@@ -89,4 +89,4 @@ module.exports = {
#### Running the test suite
Please [see this](./docs/contributing.md#run-the-regression-test-suite).
Please [see this]](./docs/contributing.md#run-the-regression-test-suite).

48
app.js
View File

@@ -1,26 +1,22 @@
const {
DRACHTIO_PORT,
DRACHTIO_HOST,
DRACHTIO_SECRET,
JAMBONES_OTEL_SERVICE_NAME,
JAMBONES_LOGLEVEL,
JAMBONES_CLUSTER_ID,
JAMBONZ_CLEANUP_INTERVAL_MINS,
getCleanupIntervalMins,
K8S,
NODE_ENV,
checkEnvs,
} = require('./lib/config');
checkEnvs();
const assert = require('assert');
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_USER &&
process.env.JAMBONES_MYSQL_PASSWORD &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_FREESWITCH, 'missing JAMBONES_FREESWITCH env var');
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_SUBNET env var');
assert.ok(process.env.ENCRYPTION_SECRET || process.env.JWT_SECRET, 'missing ENCRYPTION_SECRET env var');
const Srf = require('drachtio-srf');
const srf = new Srf();
const tracer = require('./tracer')(JAMBONES_OTEL_SERVICE_NAME);
const tracer = require('./tracer')(process.env.JAMBONES_OTEL_SERVICE_NAME || 'jambonz-feature-server');
const api = require('@opentelemetry/api');
srf.locals = {...srf.locals, otel: {tracer, api}};
const opts = {level: JAMBONES_LOGLEVEL};
const opts = {level: process.env.JAMBONES_LOGLEVEL || 'info'};
const pino = require('pino');
const logger = pino(opts, pino.destination({sync: false}));
const {LifeCycleEvents, FS_UUID_SET_NAME} = require('./lib/utils/constants');
@@ -40,8 +36,8 @@ const {
const InboundCallSession = require('./lib/session/inbound-call-session');
const SipRecCallSession = require('./lib/session/siprec-call-session');
if (DRACHTIO_HOST) {
srf.connect({host: DRACHTIO_HOST, port: DRACHTIO_PORT, secret: DRACHTIO_SECRET });
if (process.env.DRACHTIO_HOST) {
srf.connect({host: process.env.DRACHTIO_HOST, port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET });
srf.on('connect', (err, hp) => {
const arr = /^(.*)\/(.*)$/.exec(hp.split(',').pop());
srf.locals.localSipAddress = `${arr[2]}`;
@@ -49,10 +45,10 @@ if (DRACHTIO_HOST) {
});
}
else {
logger.info(`listening for drachtio requests on port ${DRACHTIO_PORT}`);
srf.listen({port: DRACHTIO_PORT, secret: DRACHTIO_SECRET});
logger.info(`listening for drachtio requests on port ${process.env.DRACHTIO_PORT}`);
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
}
if (NODE_ENV === 'test') {
if (process.env.NODE_ENV === 'test') {
srf.on('error', (err) => {
logger.info(err, 'Error connecting to drachtio');
});
@@ -117,13 +113,13 @@ function handle(signal) {
const {removeFromSet} = srf.locals.dbHelpers;
srf.locals.disabled = true;
logger.info(`got signal ${signal}`);
const setName = `${(JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
if (setName && srf.locals.localSipAddress) {
logger.info(`got signal ${signal}, removing ${srf.locals.localSipAddress} from set ${setName}`);
removeFromSet(setName, srf.locals.localSipAddress);
}
removeFromSet(FS_UUID_SET_NAME, srf.locals.fsUUID);
if (K8S) {
if (process.env.K8S) {
srf.locals.lifecycleEmitter.operationalState = LifeCycleEvents.ScaleIn;
}
if (getCount() === 0) {
@@ -132,7 +128,7 @@ function handle(signal) {
}
}
if (JAMBONZ_CLEANUP_INTERVAL_MINS) {
if (process.env.JAMBONZ_CLEANUP_INTERVAL_MINS) {
const {clearFiles} = require('./lib/utils/cron-jobs');
/* cleanup orphaned files or channels every so often */
@@ -142,7 +138,7 @@ if (JAMBONZ_CLEANUP_INTERVAL_MINS) {
} catch (err) {
logger.error({err}, 'app.js: error clearing files');
}
}, getCleanupIntervalMins());
}, 1000 * 60 * (process.env.JAMBONZ_CLEANUP_INTERVAL_MINS || 60));
}
module.exports = {srf, logger, disconnect};

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
const bent = require('bent');
const getJSON = bent('json');
const {PORT} = require('../lib/config')
const PORT = process.env.HTTP_PORT || 3000;
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));

View File

@@ -5,11 +5,14 @@
"at the tone",
"leave a message",
"leave me a message",
"not available",
"not available right now",
"not available to take your call",
"can't take your call",
"will get back to you",
"I will get back to you",
"I'll get back to you",
"we are unable"
"we will get back to you",
"we are unable",
"we are not available"
],
"es-ES": [
"le pasamos la llamada",
@@ -45,18 +48,5 @@
"ens posarem en contacto",
"ara no estem disponibles",
"no hi som"
],
"de-DE": [
"nicht erreichbar",
"nnruf wurde weitergeleitet",
"beim piepsen",
"am ton",
"eine nachricht hinterlassen",
"hinterlasse mir eine Nachricht",
"nicht verfügbar",
"kann ihren anruf nicht entgegennehmen",
"wird sich bei Ihnen melden",
"ich melde mich bei dir",
"wir können nicht"
]
}

View File

@@ -1,195 +0,0 @@
const assert = require('assert');
const checkEnvs = () => {
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_USER &&
process.env.JAMBONES_MYSQL_PASSWORD &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_FREESWITCH, 'missing JAMBONES_FREESWITCH env var');
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_SUBNET env var');
};
const NODE_ENV = process.env.NODE_ENV;
/* database mySQL */
const JAMBONES_MYSQL_HOST = process.env.JAMBONES_MYSQL_HOST;
const JAMBONES_MYSQL_USER = process.env.JAMBONES_MYSQL_USER;
const JAMBONES_MYSQL_PASSWORD = process.env.JAMBONES_MYSQL_PASSWORD;
const JAMBONES_MYSQL_DATABASE = process.env.JAMBONES_MYSQL_DATABASE;
const JAMBONES_MYSQL_PORT = parseInt(process.env.JAMBONES_MYSQL_PORT, 10) || 3306;
const JAMBONES_MYSQL_REFRESH_TTL = process.env.JAMBONES_MYSQL_REFRESH_TTL;
const JAMBONES_MYSQL_CONNECTION_LIMIT = parseInt(process.env.JAMBONES_MYSQL_CONNECTION_LIMIT, 10) || 10;
/* redis */
const JAMBONES_REDIS_HOST = process.env.JAMBONES_REDIS_HOST;
const JAMBONES_REDIS_PORT = parseInt(process.env.JAMBONES_REDIS_PORT, 10) || 6379;
/* gather and hints */
const JAMBONES_GATHER_EARLY_HINTS_MATCH = process.env.JAMBONES_GATHER_EARLY_HINTS_MATCH;
const JAMBONZ_GATHER_EARLY_HINTS_MATCH = process.env.JAMBONZ_GATHER_EARLY_HINTS_MATCH;
const JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS = process.env.JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS;
const SMPP_URL = process.env.SMPP_URL;
/* drachtio */
const DRACHTIO_PORT = process.env.DRACHTIO_PORT;
const DRACHTIO_HOST = process.env.DRACHTIO_HOST;
const DRACHTIO_SECRET = process.env.DRACHTIO_SECRET;
/* freeswitch */
const JAMBONES_API_BASE_URL = process.env.JAMBONES_API_BASE_URL;
const JAMBONES_FREESWITCH = process.env.JAMBONES_FREESWITCH;
const JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS = parseInt(process.env.JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS, 10)
|| 180;
const JAMBONES_SBCS = process.env.JAMBONES_SBCS;
/* websockets */
const JAMBONES_WS_HANDSHAKE_TIMEOUT_MS = parseInt(process.env.JAMBONES_WS_HANDSHAKE_TIMEOUT_MS, 10) || 1500;
const JAMBONES_WS_MAX_PAYLOAD = parseInt(process.env.JAMBONES_WS_MAX_PAYLOAD, 10) || 24 * 1024;
const MAX_RECONNECTS = 5;
const RESPONSE_TIMEOUT_MS = parseInt(process.env.JAMBONES_WS_API_MSG_RESPONSE_TIMEOUT, 10) || 5000;
const JAMBONES_NETWORK_CIDR = process.env.JAMBONES_NETWORK_CIDR;
const JAMBONES_TIME_SERIES_HOST = process.env.JAMBONES_TIME_SERIES_HOST;
const JAMBONES_CLUSTER_ID = process.env.JAMBONES_CLUSTER_ID || 'default';
const JAMBONES_ESL_LISTEN_ADDRESS = process.env.JAMBONES_ESL_LISTEN_ADDRESS;
/* tracing */
const JAMBONES_OTEL_ENABLED = process.env.JAMBONES_OTEL_ENABLED;
const JAMBONES_OTEL_SERVICE_NAME = process.env.JAMBONES_OTEL_SERVICE_NAME || 'jambonz-feature-server';
const OTEL_EXPORTER_JAEGER_AGENT_HOST = process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST;
const OTEL_EXPORTER_JAEGER_ENDPOINT = process.env.OTEL_EXPORTER_JAEGER_ENDPOINT;
const OTEL_EXPORTER_ZIPKIN_URL = process.env.OTEL_EXPORTER_ZIPKIN_URL;
const OTEL_EXPORTER_COLLECTOR_URL = process.env.OTEL_EXPORTER_COLLECTOR_URL;
const JAMBONES_LOGLEVEL = process.env.JAMBONES_LOGLEVEL || 'info';
const JAMBONES_INJECT_CONTENT = process.env.JAMBONES_INJECT_CONTENT;
const PORT = parseInt(process.env.HTTP_PORT, 10) || 3000;
const HTTP_PORT_MAX = parseInt(process.env.HTTP_PORT_MAX, 10);
const K8S = process.env.K8S;
const K8S_SBC_SIP_SERVICE_NAME = process.env.K8S_SBC_SIP_SERVICE_NAME;
const JAMBONES_SUBNET = process.env.JAMBONES_SUBNET;
/* clean up */
const JAMBONZ_CLEANUP_INTERVAL_MINS = process.env.JAMBONZ_CLEANUP_INTERVAL_MINS;
const getCleanupIntervalMins = () => {
const interval = parseInt(JAMBONZ_CLEANUP_INTERVAL_MINS, 10) || 60;
return 1000 * 60 * interval;
};
/* speech vendors */
const AWS_REGION = process.env.AWS_REGION;
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
const AWS_SNS_PORT = parseInt(process.env.AWS_SNS_PORT, 10) || 3001;
const AWS_SNS_TOPIC_ARM = process.env.AWS_SNS_TOPIC_ARM;
const AWS_SNS_PORT_MAX = parseInt(process.env.AWS_SNS_PORT_MAX, 10) || 3005;
const GCP_JSON_KEY = process.env.GCP_JSON_KEY;
const MICROSOFT_REGION = process.env.MICROSOFT_REGION;
const MICROSOFT_API_KEY = process.env.MICROSOFT_API_KEY;
const SONIOX_API_KEY = process.env.SONIOX_API_KEY;
const DEEPGRAM_API_KEY = process.env.DEEPGRAM_API_KEY;
const ANCHOR_MEDIA_ALWAYS = process.env.ANCHOR_MEDIA_ALWAYS;
const VMD_HINTS_FILE = process.env.VMD_HINTS_FILE;
/* security, secrets */
const LEGACY_CRYPTO = !!process.env.LEGACY_CRYPTO;
const JWT_SECRET = process.env.JWT_SECRET;
const ENCRYPTION_SECRET = process.env.ENCRYPTION_SECRET;
/* HTTP/1 pool dispatcher */
const HTTP_POOL = process.env.HTTP_POOL && parseInt(process.env.HTTP_POOL);
const HTTP_POOLSIZE = parseInt(process.env.HTTP_POOLSIZE, 10) || 10;
const HTTP_PIPELINING = parseInt(process.env.HTTP_PIPELINING, 10) || 1;
const HTTP_TIMEOUT = 10000;
const OPTIONS_PING_INTERVAL = parseInt(process.env.OPTIONS_PING_INTERVAL, 10) || 30000;
module.exports = {
JAMBONES_MYSQL_HOST,
JAMBONES_MYSQL_USER,
JAMBONES_MYSQL_PASSWORD,
JAMBONES_MYSQL_DATABASE,
JAMBONES_MYSQL_REFRESH_TTL,
JAMBONES_MYSQL_CONNECTION_LIMIT,
JAMBONES_MYSQL_PORT,
DRACHTIO_PORT,
DRACHTIO_HOST,
DRACHTIO_SECRET,
JAMBONES_GATHER_EARLY_HINTS_MATCH,
JAMBONZ_GATHER_EARLY_HINTS_MATCH,
JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS,
JAMBONES_FREESWITCH,
JAMBONES_REDIS_HOST,
JAMBONES_REDIS_PORT,
SMPP_URL,
JAMBONES_NETWORK_CIDR,
JAMBONES_API_BASE_URL,
JAMBONES_TIME_SERIES_HOST,
JAMBONES_INJECT_CONTENT,
JAMBONES_ESL_LISTEN_ADDRESS,
JAMBONES_SBCS,
JAMBONES_OTEL_ENABLED,
JAMBONES_OTEL_SERVICE_NAME,
OTEL_EXPORTER_JAEGER_AGENT_HOST,
OTEL_EXPORTER_JAEGER_ENDPOINT,
OTEL_EXPORTER_ZIPKIN_URL,
OTEL_EXPORTER_COLLECTOR_URL,
JAMBONES_LOGLEVEL,
JAMBONES_CLUSTER_ID,
PORT,
HTTP_PORT_MAX,
K8S,
K8S_SBC_SIP_SERVICE_NAME,
JAMBONES_SUBNET,
NODE_ENV,
JAMBONZ_CLEANUP_INTERVAL_MINS,
getCleanupIntervalMins,
checkEnvs,
AWS_REGION,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
AWS_SNS_PORT,
AWS_SNS_TOPIC_ARM,
AWS_SNS_PORT_MAX,
ANCHOR_MEDIA_ALWAYS,
VMD_HINTS_FILE,
JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS,
LEGACY_CRYPTO,
JWT_SECRET,
ENCRYPTION_SECRET,
HTTP_POOL,
HTTP_POOLSIZE,
HTTP_PIPELINING,
HTTP_TIMEOUT,
OPTIONS_PING_INTERVAL,
RESPONSE_TIMEOUT_MS,
JAMBONES_WS_HANDSHAKE_TIMEOUT_MS,
JAMBONES_WS_MAX_PAYLOAD,
MAX_RECONNECTS,
GCP_JSON_KEY,
MICROSOFT_REGION,
MICROSOFT_API_KEY,
SONIOX_API_KEY,
DEEPGRAM_API_KEY
};

View File

@@ -27,7 +27,6 @@ router.post('/', async(req, res) => {
const target = restDial.to;
const opts = {
callingNumber: restDial.from,
...(restDial.callerName && {callingName: restDial.callerName}),
headers: req.body.headers || {}
};
@@ -86,25 +85,6 @@ router.post('/', async(req, res) => {
}
}
/**
* trunk isn't specified,
* check if from-number matches any existing numbers on Jambonz
* */
if (target.type === 'phone' && !target.trunk) {
const {lookupCarrierByPhoneNumber, lookupCarrierByLcr} = dbUtils(this.logger, srf);
// firstly LCR
let voip_carrier_sid = await lookupCarrierByLcr(req.body.account_sid, to);
if (!voip_carrier_sid) {
// later by phone
voip_carrier_sid = await lookupCarrierByPhoneNumber(req.body.account_sid, restDial.from);
}
logger.info(
`createCall: selected ${voip_carrier_sid} for requested phone number: ${restDial.from || 'unspecified'})`);
if (voip_carrier_sid) {
opts.headers['X-Requested-Carrier-Sid'] = voip_carrier_sid;
}
}
/* create endpoint for outdial */
const ms = getFreeswitch();
if (!ms) throw new Error('no available Freeswitch for outbound call creation');

View File

@@ -10,9 +10,6 @@ const { normalizeJambones } = require('@jambonz/verb-specifications');
const dbUtils = require('./utils/db-utils');
const RootSpan = require('./utils/call-tracer');
const listTaskNames = require('./utils/summarize-tasks');
const {
JAMBONES_MYSQL_REFRESH_TTL,
} = require('./config');
module.exports = function(srf, logger) {
const {
@@ -245,12 +242,11 @@ module.exports = function(srf, logger) {
*/
/* allow for caching data - when caching treat retrieved data as immutable */
const app2 = JAMBONES_MYSQL_REFRESH_TTL ? JSON.parse(JSON.stringify(app)) : app;
const app2 = process.env.JAMBONES_MYSQL_REFRESH_TTL ? JSON.parse(JSON.stringify(app)) : app;
if ('WS' === app.call_hook?.method ||
app.call_hook?.url.startsWith('ws://') || app.call_hook?.url.startsWith('wss://')) {
const requestor = new WsRequestor(logger, account_sid, app.call_hook, accountInfo.account.webhook_secret) ;
app2.requestor = requestor;
app2.notifier = requestor;
app2.requestor = new WsRequestor(logger, account_sid, app.call_hook, accountInfo.account.webhook_secret) ;
app2.notifier = app.requestor;
app2.call_hook.method = 'WS';
}
else {
@@ -289,7 +285,7 @@ module.exports = function(srf, logger) {
const {rootSpan, siprec, application:app} = req.locals;
let span;
try {
if (app.tasks && !JAMBONES_MYSQL_REFRESH_TTL) {
if (app.tasks && !process.env.JAMBONES_MYSQL_REFRESH_TTL) {
app.tasks = normalizeJambones(logger, app.tasks).map((tdata) => makeTask(logger, tdata));
if (0 === app.tasks.length) throw new Error('no application provided');
return next();

View File

@@ -1,7 +1,6 @@
const {CallDirection, CallStatus} = require('../utils/constants');
const parseUri = require('drachtio-srf').parseUri;
const uuidv4 = require('uuid-random');
const {JAMBONES_API_BASE_URL} = require('../config');
/**
* @classdesc Represents the common information for all calls
* that is provided in call status webhooks
@@ -41,13 +40,13 @@ class CallInfo {
this.participants = [
{
participant: 'caller',
uriUser: caller?.user,
uriHost: caller?.host
uriUser: caller.user,
uriHost: caller.host
},
{
participant: 'callee',
uriUser: callee?.user,
uriHost: callee?.host
uriUser: callee.user,
uriHost: callee.host
}
];
}
@@ -147,8 +146,8 @@ class CallInfo {
Object.assign(obj, {customerData: this._customerData});
}
if (JAMBONES_API_BASE_URL) {
Object.assign(obj, {apiBaseUrl: JAMBONES_API_BASE_URL});
if (process.env.JAMBONES_API_BASE_URL) {
Object.assign(obj, {apiBaseUrl: process.env.JAMBONES_API_BASE_URL});
}
if (this.publicIp) {
Object.assign(obj, {fsPublicIp: this.publicIp});

View File

@@ -17,10 +17,6 @@ const { normalizeJambones } = require('@jambonz/verb-specifications');
const listTaskNames = require('../utils/summarize-tasks');
const HttpRequestor = require('../utils/http-requestor');
const WsRequestor = require('../utils/ws-requestor');
const {
JAMBONES_INJECT_CONTENT,
AWS_REGION
} = require('../config');
const BADPRECONDITIONS = 'preconditions not met';
const CALLER_CANCELLED_ERR_MSG = 'Response not sent due to unknown transaction';
@@ -529,7 +525,7 @@ class CallSession extends Emitter {
this.logger.info({currInput, newInput},
'CallSession:enableBotMode - restarting background gather to apply new input type');
this.backgroundGatherTask.sticky = false;
await this.disableBotMode();
this.disableBotMode();
}
}
this.backgroundGatherTask = task;
@@ -568,12 +564,12 @@ class CallSession extends Emitter {
this.logger.info({err, gather}, 'CallSession:enableBotMode - Error creating gather task');
}
}
async disableBotMode() {
disableBotMode() {
this._bargeInEnabled = false;
if (this.backgroundGatherTask) {
try {
this.backgroundGatherTask.removeAllListeners();
await this.backgroundGatherTask.kill();
this.backgroundGatherTask.kill().catch((err) => {});
} catch (err) {}
this.backgroundGatherTask = null;
}
@@ -630,7 +626,7 @@ class CallSession extends Emitter {
speech_credential_sid: credential.speech_credential_sid,
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
region: credential.aws_region || AWS_REGION
region: credential.aws_region || process.env.AWS_REGION
};
}
else if ('microsoft' === vendor) {
@@ -724,7 +720,7 @@ class CallSession extends Emitter {
}
else {
this.logger.info('CallSession:exec disabling bot mode to start gather with new options');
await this.disableBotMode();
this.disableBotMode();
}
}
if (!skip) {
@@ -1192,7 +1188,7 @@ class CallSession extends Emitter {
}
_onCommand({msgid, command, call_sid, queueCommand, data}) {
this.logger.info({msgid, command, queueCommand, data}, 'CallSession:_onCommand - received command');
this.logger.info({msgid, command, queueCommand}, 'CallSession:_onCommand - received command');
const resolution = {reason: 'received command', queue: queueCommand, command};
switch (command) {
case 'redirect':
@@ -1203,7 +1199,7 @@ class CallSession extends Emitter {
this.logger.info({tasks: listTaskNames(t)}, 'CallSession:_onCommand new task list');
this.replaceApplication(t);
}
else if (JAMBONES_INJECT_CONTENT) {
else if (process.env.JAMBONES_INJECT_CONTENT) {
this._injectTasks(t);
this.logger.info({tasks: listTaskNames(this.tasks)}, 'CallSession:_onCommand - updated task list');
}

View File

@@ -15,7 +15,6 @@ const DtmfCollector = require('../utils/dtmf-collector');
const dbUtils = require('../utils/db-utils');
const debug = require('debug')('jambonz:feature-server');
const {parseUri} = require('drachtio-srf');
const {ANCHOR_MEDIA_ALWAYS} = require('../config');
function parseDtmfOptions(logger, dtmfCapture) {
let parentDtmfCollector, childDtmfCollector;
@@ -85,7 +84,6 @@ class TaskDial extends Task {
this.earlyMedia = this.data.answerOnBridge === true;
this.callerId = this.data.callerId;
this.callerName = this.data.callerName;
this.dialMusic = this.data.dialMusic;
this.headers = this.data.headers || {};
this.method = this.data.method || 'POST';
@@ -136,13 +134,10 @@ class TaskDial extends Task {
get name() { return TaskName.Dial; }
get canReleaseMedia() {
const keepAnchor = this.data.anchorMedia ||
ANCHOR_MEDIA_ALWAYS ||
this.listenTask ||
this.transcribeTask ||
this.startAmd;
return !keepAnchor;
return !process.env.ANCHOR_MEDIA_ALWAYS &&
!this.listenTask &&
!this.transcribeTask &&
!this.startAmd;
}
get summary() {
@@ -399,7 +394,7 @@ class TaskDial extends Task {
const {req, srf} = cs;
const {getSBC} = srf.locals;
const {lookupTeamsByAccount, lookupAccountBySid} = srf.locals.dbHelpers;
const {lookupCarrier, lookupCarrierByLcr} = dbUtils(this.logger, cs.srf);
const {lookupCarrier} = dbUtils(this.logger, cs.srf);
const sbcAddress = this.proxy || getSBC();
const teamsInfo = {};
let fqdn;
@@ -416,8 +411,7 @@ class TaskDial extends Task {
const opts = {
headers: this.headers,
proxy: `sip:${sbcAddress}`,
callingNumber: this.callerId || req.callingNumber,
...(this.callerName && {callingName: this.callerName})
callingNumber: this.callerId || req.callingNumber
};
const t = this.target.find((t) => t.type === 'teams');
@@ -465,19 +459,6 @@ class TaskDial extends Task {
}
}
/**
* trunk isn't specified,
* check if number matches any LCR routes
* */
if (t.type === 'phone' && !t.trunk) {
const voip_carrier_sid = lookupCarrierByLcr(req.body.account_sid, t.number);
this.logger.info(
`Dial:_attemptCalls: selected ${voip_carrier_sid} for requested phone number: ${t.number})`);
if (voip_carrier_sid) {
opts.headers['X-Requested-Carrier-Sid'] = voip_carrier_sid;
}
}
if (this.killed) return;
const sd = placeCall({

View File

@@ -12,11 +12,7 @@ const {
NvidiaTranscriptionEvents,
JambonzTranscriptionEvents
} = require('../utils/constants');
const {
JAMBONES_GATHER_EARLY_HINTS_MATCH,
JAMBONZ_GATHER_EARLY_HINTS_MATCH,
JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS,
} = require('../config');
const makeTask = require('./make_task');
const assert = require('assert');
@@ -75,7 +71,7 @@ class TaskGather extends Task {
this.isContinuousAsr = this.asrTimeout > 0;
if (Array.isArray(this.data.recognizer.hints) &&
0 == this.data.recognizer.hints.length && JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS) {
0 == this.data.recognizer.hints.length && process.env.JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS) {
logger.debug('Gather: an empty hints array was supplied, so we will mask global hints');
this.maskGlobalSttHints = true;
}
@@ -166,7 +162,7 @@ class TaskGather extends Task {
asrDtmfTerminationDigit: this.asrDtmfTerminationDigit
}, 'Gather:exec - enabling continuous ASR since it is turned on for the session');
}
const {JAMBONZ_GATHER_EARLY_HINTS_MATCH, JAMBONES_GATHER_EARLY_HINTS_MATCH} = process.env;
if ((JAMBONZ_GATHER_EARLY_HINTS_MATCH || JAMBONES_GATHER_EARLY_HINTS_MATCH) && this.needsStt &&
!this.isContinuousAsr &&
this.data.recognizer?.hints?.length > 0 && this.data.recognizer?.hints?.length <= 10) {

View File

@@ -2,7 +2,7 @@ const Task = require('./task');
const {TaskName, TaskPreconditions} = require('../utils/constants');
const bent = require('bent');
const uuidv4 = require('uuid-random');
const {K8S} = require('../config');
class TaskMessage extends Task {
constructor(logger, opts) {
super(logger, opts);
@@ -42,7 +42,7 @@ class TaskMessage extends Task {
}
if (gw) {
this.logger.info({gw, accountSid}, 'Message:exec - using smpp to send message');
url = K8S ? 'http://smpp' : getSmpp();
url = process.env.K8S ? 'http://smpp' : getSmpp();
relativeUrl = '/sms';
payload = {
...payload,

View File

@@ -11,7 +11,6 @@ class TaskRestDial extends Task {
super(logger, opts);
this.from = this.data.from;
this.callerName = this.data.callerName;
this.fromHost = this.data.fromHost;
this.to = this.data.to;
this.call_hook = this.data.call_hook;

View File

@@ -8,7 +8,7 @@ const {
AvmdEvents
} = require('./constants');
const bugname = 'amd_bug';
const {VMD_HINTS_FILE} = require('../config');
const {VMD_HINTS_FILE} = process.env;
let voicemailHints = [];
const updateHints = async(file, callback) => {
@@ -184,7 +184,7 @@ module.exports = (logger) => {
const {vendor, language} = ep.amd;
ep.startTranscription({
vendor,
locale: language,
language,
interim: true,
bugname
}).catch((err) => {

View File

@@ -1,12 +1,7 @@
const Emitter = require('events');
const bent = require('bent');
const assert = require('assert');
const {
AWS_REGION,
AWS_SNS_PORT: PORT,
AWS_SNS_TOPIC_ARM,
AWS_SNS_PORT_MAX,
} = require('../config');
const PORT = process.env.AWS_SNS_PORT || 3010;
const {LifeCycleEvents} = require('./constants');
const express = require('express');
const app = express();
@@ -18,7 +13,7 @@ const {Parser} = require('xml2js');
const parser = new Parser();
const {validatePayload} = require('verify-aws-sns-signature');
AWS.config.update({region: AWS_REGION});
AWS.config.update({region: process.env.AWS_REGION});
class SnsNotifier extends Emitter {
constructor(logger) {
@@ -36,8 +31,8 @@ class SnsNotifier extends Emitter {
_handleErrors(logger, app, resolve, reject, e) {
if (e.code === 'EADDRINUSE' &&
AWS_SNS_PORT_MAX &&
e.port < AWS_SNS_PORT_MAX) {
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);
@@ -137,12 +132,12 @@ class SnsNotifier extends Emitter {
try {
const response = await sns.subscribe({
Protocol: 'http',
TopicArn: AWS_SNS_TOPIC_ARM,
TopicArn: process.env.AWS_SNS_TOPIC_ARM,
Endpoint: this.snsEndpoint
}).promise();
this.logger.info({response}, `response to SNS subscribe to ${AWS_SNS_TOPIC_ARM}`);
this.logger.info({response}, `response to SNS subscribe to ${process.env.AWS_SNS_TOPIC_ARM}`);
} catch (err) {
this.logger.error({err}, `Error subscribing to SNS topic arn ${AWS_SNS_TOPIC_ARM}`);
this.logger.error({err}, `Error subscribing to SNS topic arn ${process.env.AWS_SNS_TOPIC_ARM}`);
}
}
@@ -152,9 +147,9 @@ class SnsNotifier extends Emitter {
const response = await sns.unsubscribe({
SubscriptionArn: this.subscriptionArn
}).promise();
this.logger.info({response}, `response to SNS unsubscribe to ${AWS_SNS_TOPIC_ARM}`);
this.logger.info({response}, `response to SNS unsubscribe to ${process.env.AWS_SNS_TOPIC_ARM}`);
} catch (err) {
this.logger.error({err}, `Error unsubscribing to SNS topic arn ${AWS_SNS_TOPIC_ARM}`);
this.logger.error({err}, `Error unsubscribing to SNS topic arn ${process.env.AWS_SNS_TOPIC_ARM}`);
}
}

View File

@@ -2,7 +2,6 @@ const assert = require('assert');
const Emitter = require('events');
const crypto = require('crypto');
const timeSeries = require('@jambonz/time-series');
const {NODE_ENV, JAMBONES_TIME_SERIES_HOST} = require('../config');
let alerter ;
class BaseRequestor extends Emitter {
@@ -23,9 +22,9 @@ class BaseRequestor extends Emitter {
if (!alerter) {
alerter = timeSeries(logger, {
host: JAMBONES_TIME_SERIES_HOST,
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === NODE_ENV ? 7 : 20
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
});
}
}

View File

@@ -1,24 +1,19 @@
const {execSync} = require('child_process');
const {
JAMBONES_FREESWITCH,
NODE_ENV,
JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS,
} = require('../config');
const now = Date.now();
const fsInventory = JAMBONES_FREESWITCH
const fsInventory = process.env.JAMBONES_FREESWITCH
.split(',')
.map((fs) => {
const arr = /^([^:]*):([^:]*):([^:]*)(?::([^:]*))?/.exec(fs);
const opts = {address: arr[1], port: arr[2], secret: arr[3]};
if (arr.length > 4) opts.advertisedAddress = arr[4];
if (NODE_ENV === 'test') opts.listenAddress = '0.0.0.0';
if (process.env.NODE_ENV === 'test') opts.listenAddress = '0.0.0.0';
return opts;
});
const clearChannels = () => {
const {logger} = require('../..');
const pwd = fsInventory[0].secret;
const maxDurationMins = JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS;
const maxDurationMins = process.env.JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS || 180;
const calls = execSync(`/usr/local/freeswitch/bin/fs_cli -p ${pwd} -x "show calls"`, {encoding: 'utf8'})
.split('\n')

View File

@@ -20,16 +20,6 @@ WHERE vc.account_sid IS NULL
AND vc.service_provider_sid =
(SELECT service_provider_sid from accounts where account_sid = ?)
AND vc.name = ?`;
const sqlQueryAccountPhoneNumber = `SELECT voip_carrier_sid
FROM phone_numbers pn
WHERE pn.account_sid = ?
AND pn.number = ?`;
const sqlQuerySPPhoneNumber = `SELECT voip_carrier_sid
FROM phone_numbers pn
WHERE pn.account_sid IS NULL
AND pn.service_provider_sid =
(SELECT service_provider_sid from accounts where account_sid = ?)
AND pn.number = ?`;
const speechMapper = (cred) => {
const {credential, ...obj} = cred;
@@ -138,53 +128,9 @@ module.exports = (logger, srf) => {
}
};
const lookupCarrierByPhoneNumber = async(account_sid, phoneNumber) => {
const pp = pool.promise();
try {
const [r] = await pp.query(sqlQueryAccountPhoneNumber, [account_sid, phoneNumber]);
if (r.length) return r[0].voip_carrier_sid;
const [r2] = await pp.query(sqlQuerySPPhoneNumber, [account_sid, phoneNumber]);
if (r2.length) return r2[0].voip_carrier_sid;
} catch (err) {
logger.error({err}, `lookupPhoneNumber: Error ${account_sid}:${phoneNumber}`);
}
};
const sqlQueryLcrByAccountSid = `SELECT lcr_sid FROM lcr WHERE account_sid = ? OR
service_provider_sid = (SELECT service_provider_sid from accounts where account_sid = ?)`;
const sqlQueryLcrRouteByLcrSid = 'SELECT * FROM lcr_routes WHERE lcr_sid = ? ORDER BY priority';
const sqlQueryLcrCarrierSetEntryByLcrRouteSid = `SELECT * FROM lcr_carrier_set_entry
WHERE lcr_route_sid = ? ORDER BY priority`;
const lookupCarrierByLcr = async(account_sid, toNumber) => {
const pp = pool.promise();
try {
const [lcrs] = await pp.query(sqlQueryLcrByAccountSid, [account_sid, account_sid]);
if (lcrs.length) {
const lcr_sid = lcrs[0];
const [lcr_routes] = await pp.query(sqlQueryLcrRouteByLcrSid, [lcr_sid]);
if (lcr_routes.length) {
for (const r of lcr_routes) {
var matcher = new RegExp(r.regex);
if (matcher.test(toNumber)) {
const [entries] = await pp.query(sqlQueryLcrCarrierSetEntryByLcrRouteSid, [r.lcr_route_sid]);
// Currently just support first entry;
if (entries.length) {
return entries[0].voip_carrier_sid;
}
}
}
}
}
} catch (error) {
logger.error({error}, `lookupCarrierByLcr: Error ${account_sid}:${toNumber}`);
}
};
return {
lookupAccountDetails,
updateSpeechCredentialLastUsed,
lookupCarrier,
lookupCarrierByPhoneNumber,
lookupCarrierByLcr
lookupCarrier
};
};

View File

@@ -1,9 +1,8 @@
const crypto = require('crypto');
const {LEGACY_CRYPTO, ENCRYPTION_SECRET, JWT_SECRET} = require('../config');
const algorithm = LEGACY_CRYPTO ? 'aes-256-ctr' : 'aes-256-cbc';
const algorithm = process.env.LEGACY_CRYPTO ? 'aes-256-ctr' : 'aes-256-cbc';
const iv = crypto.randomBytes(16);
const secretKey = crypto.createHash('sha256')
.update(ENCRYPTION_SECRET || JWT_SECRET)
.update(process.env.ENCRYPTION_SECRET || process.env.JWT_SECRET)
.digest('base64')
.substring(0, 32);

View File

@@ -1,7 +1,7 @@
const express = require('express');
const httpRoutes = require('../http-routes');
const {PORT, HTTP_PORT_MAX} = require('../config');
const PORT = process.env.HTTP_PORT || 3000;
const doListen = (logger, app, port, resolve) => {
const server = app.listen(port, () => {
@@ -13,8 +13,8 @@ const doListen = (logger, app, port, resolve) => {
};
const handleErrors = (logger, app, resolve, reject, e) => {
if (e.code === 'EADDRINUSE' &&
HTTP_PORT_MAX &&
e.port < HTTP_PORT_MAX) {
process.env.HTTP_PORT_MAX &&
e.port < process.env.HTTP_PORT_MAX) {
logger.info(`HTTP server failed to bind port on ${e.port}, will try next port`);
const server = doListen(logger, app, ++e.port, resolve);

View File

@@ -5,12 +5,7 @@ const BaseRequestor = require('./base-requestor');
const {HookMsgTypes} = require('./constants.json');
const snakeCaseKeys = require('./snakecase-keys');
const pools = new Map();
const {
HTTP_POOL,
HTTP_POOLSIZE,
HTTP_PIPELINING,
HTTP_TIMEOUT,
} = require('../config');
const HTTP_TIMEOUT = 10000;
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
@@ -39,15 +34,15 @@ class HttpRequestor extends BaseRequestor {
this._resource = u.resource;
this._port = u.port;
this._search = u.search;
this._usePools = HTTP_POOL && parseInt(HTTP_POOL);
this._usePools = process.env.HTTP_POOL && parseInt(process.env.HTTP_POOL);
if (this._usePools) {
if (pools.has(this._baseUrl)) {
this.client = pools.get(this._baseUrl);
}
else {
const connections = HTTP_POOLSIZE ? parseInt(HTTP_POOLSIZE) : 10;
const pipelining = HTTP_PIPELINING ? parseInt(HTTP_PIPELINING) : 1;
const connections = process.env.HTTP_POOLSIZE ? parseInt(process.env.HTTP_POOLSIZE) : 10;
const pipelining = process.env.HTTP_PIPELINING ? parseInt(process.env.HTTP_PIPELINING) : 1;
const pool = this.client = new Pool(this._baseUrl, {
connections,
pipelining

View File

@@ -1,21 +1,6 @@
const Mrf = require('drachtio-fsmrf');
const ip = require('ip');
const {
JAMBONES_MYSQL_HOST,
JAMBONES_MYSQL_USER,
JAMBONES_MYSQL_PASSWORD,
JAMBONES_MYSQL_DATABASE,
JAMBONES_MYSQL_CONNECTION_LIMIT,
JAMBONES_MYSQL_PORT,
JAMBONES_FREESWITCH,
JAMBONES_REDIS_HOST,
JAMBONES_REDIS_PORT,
SMPP_URL,
JAMBONES_TIME_SERIES_HOST,
JAMBONES_ESL_LISTEN_ADDRESS,
PORT,
NODE_ENV,
} = require('../config');
const PORT = process.env.HTTP_PORT || 3000;
const assert = require('assert');
function initMS(logger, wrapper, ms) {
@@ -57,18 +42,18 @@ function installSrfLocals(srf, logger) {
let idxStart = 0;
(async function() {
const fsInventory = JAMBONES_FREESWITCH
const fsInventory = process.env.JAMBONES_FREESWITCH
.split(',')
.map((fs) => {
const arr = /^([^:]*):([^:]*):([^:]*)(?::([^:]*))?/.exec(fs);
assert.ok(arr, `Invalid syntax JAMBONES_FREESWITCH: ${JAMBONES_FREESWITCH}`);
assert.ok(arr, `Invalid syntax JAMBONES_FREESWITCH: ${process.env.JAMBONES_FREESWITCH}`);
const opts = {address: arr[1], port: arr[2], secret: arr[3]};
if (arr.length > 4) opts.advertisedAddress = arr[4];
/* NB: originally for testing only, but for now all jambonz deployments
have freeswitch installed locally alongside this app
*/
if (NODE_ENV === 'test') opts.listenAddress = '0.0.0.0';
else if (JAMBONES_ESL_LISTEN_ADDRESS) opts.listenAddress = JAMBONES_ESL_LISTEN_ADDRESS;
if (process.env.NODE_ENV === 'test') opts.listenAddress = '0.0.0.0';
else if (process.env.JAMBONES_ESL_LISTEN_ADDRESS) opts.listenAddress = process.env.JAMBONES_ESL_LISTEN_ADDRESS;
return opts;
});
logger.info({fsInventory}, 'freeswitch inventory');
@@ -140,12 +125,12 @@ function installSrfLocals(srf, logger) {
lookupAccountCapacitiesBySid,
lookupSmppGateways
} = require('@jambonz/db-helpers')({
host: JAMBONES_MYSQL_HOST,
user: JAMBONES_MYSQL_USER,
port: JAMBONES_MYSQL_PORT || 3306,
password: JAMBONES_MYSQL_PASSWORD,
database: JAMBONES_MYSQL_DATABASE,
connectionLimit: JAMBONES_MYSQL_CONNECTION_LIMIT || 10
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
}, logger, tracer);
const {
client,
@@ -168,24 +153,24 @@ function installSrfLocals(srf, logger) {
getListPosition,
lengthOfList,
} = require('@jambonz/realtimedb-helpers')({
host: JAMBONES_REDIS_HOST,
port: JAMBONES_REDIS_PORT || 6379
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger, tracer);
const {
synthAudio,
getNuanceAccessToken,
getIbmAccessToken,
} = require('@jambonz/speech-utils')({
host: JAMBONES_REDIS_HOST,
port: JAMBONES_REDIS_PORT || 6379
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger, tracer);
const {
writeAlerts,
AlertType
} = require('@jambonz/time-series')(logger, {
host: JAMBONES_TIME_SERIES_HOST,
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === NODE_ENV ? 7 : 20
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
});
let localIp;
@@ -233,7 +218,7 @@ function installSrfLocals(srf, logger) {
parentLogger: logger,
getSBC,
getSmpp: () => {
return SMPP_URL;
return process.env.SMPP_URL;
},
lifecycleEmitter,
getFreeswitch,

View File

@@ -1,9 +1,5 @@
const assert = require('assert');
const timeSeries = require('@jambonz/time-series');
const {
NODE_ENV,
JAMBONES_TIME_SERIES_HOST
} = require('../config');
let alerter ;
function isAbsoluteUrl(u) {
@@ -32,9 +28,9 @@ class Requestor {
if (!alerter) {
alerter = timeSeries(logger, {
host: JAMBONES_TIME_SERIES_HOST,
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === NODE_ENV ? 7 : 20
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
});
}
}
@@ -42,9 +38,9 @@ class Requestor {
get Alerter() {
if (!alerter) {
alerter = timeSeries(this.logger, {
host: JAMBONES_TIME_SERIES_HOST,
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === NODE_ENV ? 7 : 20
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
});
}
return alerter;

View File

@@ -4,38 +4,28 @@ const {LifeCycleEvents, FS_UUID_SET_NAME} = require('./constants');
const Emitter = require('events');
const debug = require('debug')('jambonz:feature-server');
const noopLogger = {info: () => {}, error: () => {}};
const {
JAMBONES_SBCS,
K8S,
K8S_SBC_SIP_SERVICE_NAME,
AWS_SNS_TOPIC_ARM,
OPTIONS_PING_INTERVAL,
AWS_REGION,
NODE_ENV,
JAMBONES_CLUSTER_ID,
} = require('../config');
module.exports = (logger) => {
logger = logger || noopLogger;
let idxSbc = 0;
let sbcs = [];
if (JAMBONES_SBCS) {
sbcs = JAMBONES_SBCS
if (process.env.JAMBONES_SBCS) {
sbcs = process.env.JAMBONES_SBCS
.split(',')
.map((sbc) => sbc.trim());
assert.ok(sbcs.length, 'JAMBONES_SBCS env var is empty or misconfigured');
logger.info({sbcs}, 'SBC inventory');
}
else if (K8S && K8S_SBC_SIP_SERVICE_NAME) {
sbcs = [`${K8S_SBC_SIP_SERVICE_NAME}:5060`];
else if (process.env.K8S && process.env.K8S_SBC_SIP_SERVICE_NAME) {
sbcs = [`${process.env.K8S_SBC_SIP_SERVICE_NAME}:5060`];
logger.info({sbcs}, 'SBC inventory');
}
// listen for SNS lifecycle changes
let lifecycleEmitter = new Emitter();
let dryUpCalls = false;
if (AWS_SNS_TOPIC_ARM && AWS_REGION) {
if (process.env.AWS_SNS_TOPIC_ARM && process.env.AWS_REGION) {
(async function() {
try {
@@ -85,13 +75,13 @@ module.exports = (logger) => {
}
})();
}
else if (K8S) {
else if (process.env.K8S) {
lifecycleEmitter.scaleIn = () => process.exit(0);
}
async function pingProxies(srf) {
if (NODE_ENV === 'test') return;
if (process.env.NODE_ENV === 'test') return;
for (const sbc of sbcs) {
try {
@@ -112,7 +102,7 @@ module.exports = (logger) => {
}
}
}
if (K8S) {
if (process.env.K8S) {
setImmediate(() => {
logger.info('disabling OPTIONS pings since we are running as a kubernetes service');
const {srf} = require('../..');
@@ -133,16 +123,16 @@ module.exports = (logger) => {
setInterval(() => {
const {srf} = require('../..');
pingProxies(srf);
}, OPTIONS_PING_INTERVAL);
}, process.env.OPTIONS_PING_INTERVAL || 30000);
// initial ping once we are up
setTimeout(async() => {
// if SBCs are auto-scaling, monitor them as they come and go
const {srf} = require('../..');
if (!JAMBONES_SBCS) {
if (!process.env.JAMBONES_SBCS) {
const {monitorSet} = srf.locals.dbHelpers;
const setName = `${(JAMBONES_CLUSTER_ID || 'default')}:active-sip`;
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-sip`;
await monitorSet(setName, 10, (members) => {
sbcs = members;
logger.info(`sbc-pinger: SBC roster has changed, list of active SBCs is now ${sbcs}`);

View File

@@ -4,12 +4,8 @@ const short = require('short-uuid');
const {HookMsgTypes} = require('./constants.json');
const Websocket = require('ws');
const snakeCaseKeys = require('./snakecase-keys');
const {
RESPONSE_TIMEOUT_MS,
MAX_RECONNECTS,
JAMBONES_WS_HANDSHAKE_TIMEOUT_MS,
JAMBONES_WS_MAX_PAYLOAD
} = require('../config');
const MAX_RECONNECTS = 5;
const RESPONSE_TIMEOUT_MS = process.env.JAMBONES_WS_API_MSG_RESPONSE_TIMEOUT || 5000;
class WsRequestor extends BaseRequestor {
constructor(logger, account_sid, hook, secret) {
@@ -196,14 +192,14 @@ class WsRequestor extends BaseRequestor {
_connect() {
assert(!this.ws);
return new Promise((resolve, reject) => {
const handshakeTimeout = JAMBONES_WS_HANDSHAKE_TIMEOUT_MS ?
parseInt(JAMBONES_WS_HANDSHAKE_TIMEOUT_MS) :
const handshakeTimeout = process.env.JAMBONES_WS_HANDSHAKE_TIMEOUT_MS ?
parseInt(process.env.JAMBONES_WS_HANDSHAKE_TIMEOUT_MS) :
1500;
let opts = {
followRedirects: true,
maxRedirects: 2,
handshakeTimeout,
maxPayload: JAMBONES_WS_MAX_PAYLOAD ? parseInt(JAMBONES_WS_MAX_PAYLOAD) : 24 * 1024,
maxPayload: process.env.JAMBONES_WS_MAX_PAYLOAD ? parseInt(process.env.JAMBONES_WS_MAX_PAYLOAD) : 24 * 1024,
};
if (this.username && this.password) opts = {...opts, auth: `${this.username}:${this.password}`};

28
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"@jambonz/speech-utils": "^0.0.12",
"@jambonz/stats-collector": "^0.1.8",
"@jambonz/time-series": "^0.2.5",
"@jambonz/verb-specifications": "^0.0.16",
"@jambonz/verb-specifications": "^0.0.11",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/exporter-jaeger": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
@@ -29,7 +29,7 @@
"bent": "^7.3.12",
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.21",
"drachtio-fsmrf": "^3.0.20",
"drachtio-srf": "^4.5.23",
"express": "^4.18.2",
"ip": "^1.1.8",
@@ -1664,9 +1664,9 @@
}
},
"node_modules/@jambonz/verb-specifications": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.16.tgz",
"integrity": "sha512-2ZHpbBmqhRT9a4Cs8HEQncvdp1zS7MbzLsFynVu3gSpvgfeepRtqWX8rxG9VF9Mr+AbJoeCfo+MO0PyJCPWTMA==",
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.11.tgz",
"integrity": "sha512-T5ZITmOkcXOIBSH/vSGIQ1PXyZfOtdpo/DpJQ82gdIfYhm4AotMVbS5YABj1RFj4ergNnkoHmmhgJttgpMf0dw==",
"dependencies": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -3220,9 +3220,9 @@
}
},
"node_modules/drachtio-fsmrf": {
"version": "3.0.21",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.21.tgz",
"integrity": "sha512-j5C1KFFEr+N8Qqp5ZjfqQTox+nDKoII3eJOaRMj0zHmFzr7tFzHbwPf8JSQS9n0OjrY6iM1zJJxHO+zDp8974g==",
"version": "3.0.20",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.20.tgz",
"integrity": "sha512-ksOe5XiR0LjASHiEIOUT7YMbowKdlB1eFoM+fmSkrBi5ogcbM7RNYg6ugt+WrSc01DMbi/5BN2QhefwdzelaEQ==",
"dependencies": {
"camel-case": "^4.1.2",
"debug": "^2.6.9",
@@ -10011,9 +10011,9 @@
}
},
"@jambonz/verb-specifications": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.16.tgz",
"integrity": "sha512-2ZHpbBmqhRT9a4Cs8HEQncvdp1zS7MbzLsFynVu3gSpvgfeepRtqWX8rxG9VF9Mr+AbJoeCfo+MO0PyJCPWTMA==",
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.11.tgz",
"integrity": "sha512-T5ZITmOkcXOIBSH/vSGIQ1PXyZfOtdpo/DpJQ82gdIfYhm4AotMVbS5YABj1RFj4ergNnkoHmmhgJttgpMf0dw==",
"requires": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -11213,9 +11213,9 @@
}
},
"drachtio-fsmrf": {
"version": "3.0.21",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.21.tgz",
"integrity": "sha512-j5C1KFFEr+N8Qqp5ZjfqQTox+nDKoII3eJOaRMj0zHmFzr7tFzHbwPf8JSQS9n0OjrY6iM1zJJxHO+zDp8974g==",
"version": "3.0.20",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.20.tgz",
"integrity": "sha512-ksOe5XiR0LjASHiEIOUT7YMbowKdlB1eFoM+fmSkrBi5ogcbM7RNYg6ugt+WrSc01DMbi/5BN2QhefwdzelaEQ==",
"requires": {
"camel-case": "^4.1.2",
"debug": "^2.6.9",

View File

@@ -21,8 +21,7 @@
"start": "node app",
"test": "NODE_ENV=test JAMBONES_HOSTING=1 HTTP_POOL=1 ENCRYPTION_SECRET=foobar DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 DRACHTIO_SECRET=cymru JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=127.0.0.1 JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error ENABLE_METRICS=0 HTTP_PORT=3000 JAMBONES_SBCS=172.38.0.10 JAMBONES_FREESWITCH=127.0.0.1:8022:JambonzR0ck$:docker-host JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_NETWORK_CIDR=172.38.0.0/16 node test/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js tracer.js lib",
"jslint:fix": "eslint app.js tracer.js lib --fix"
"jslint": "eslint app.js lib"
},
"dependencies": {
"@jambonz/db-helpers": "^0.7.4",
@@ -31,7 +30,7 @@
"@jambonz/speech-utils": "^0.0.12",
"@jambonz/stats-collector": "^0.1.8",
"@jambonz/time-series": "^0.2.5",
"@jambonz/verb-specifications": "^0.0.16",
"@jambonz/verb-specifications": "^0.0.11",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/exporter-jaeger": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
@@ -45,7 +44,7 @@
"bent": "^7.3.12",
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.21",
"drachtio-fsmrf": "^3.0.20",
"drachtio-srf": "^4.5.23",
"express": "^4.18.2",
"ip": "^1.1.8",

View File

@@ -2,14 +2,6 @@ const test = require('tape') ;
const exec = require('child_process').exec ;
const fs = require('fs');
const {encrypt} = require('../lib/utils/encrypt-decrypt');
const {
GCP_JSON_KEY,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
AWS_REGION,
MICROSOFT_REGION,
MICROSOFT_API_KEY,
} = require('../lib/config');
test('creating jambones_test database', (t) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp --port=3360 < ${__dirname}/db/create_test_db.sql`, (err, stdout, stderr) => {
@@ -27,24 +19,24 @@ test('creating schema', (t) => {
t.pass('schema and test data successfully created');
const sql = [];
if (GCP_JSON_KEY) {
const google_credential = encrypt(GCP_JSON_KEY);
if (process.env.GCP_JSON_KEY) {
const google_credential = encrypt(process.env.GCP_JSON_KEY);
t.pass('adding google credentials');
sql.push(`UPDATE speech_credentials SET credential='${google_credential}' WHERE vendor='google';`);
}
if (AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY) {
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
const aws_credential = encrypt(JSON.stringify({
access_key_id: AWS_ACCESS_KEY_ID,
secret_access_key: AWS_SECRET_ACCESS_KEY,
aws_region: AWS_REGION
access_key_id: process.env.AWS_ACCESS_KEY_ID,
secret_access_key: process.env.AWS_SECRET_ACCESS_KEY,
aws_region: process.env.AWS_REGION
}));
t.pass('adding aws credentials');
sql.push(`UPDATE speech_credentials SET credential='${aws_credential}' WHERE vendor='aws';`);
}
if (MICROSOFT_REGION && MICROSOFT_API_KEY) {
if (process.env.MICROSOFT_REGION && process.env.MICROSOFT_API_KEY) {
const microsoft_credential = encrypt(JSON.stringify({
region: MICROSOFT_REGION,
api_key: MICROSOFT_API_KEY
region: process.env.MICROSOFT_REGION,
api_key: process.env.MICROSOFT_API_KEY
}));
t.pass('adding microsoft credentials');
sql.push(`UPDATE speech_credentials SET credential='${microsoft_credential}' WHERE vendor='microsoft';`);

View File

@@ -31,7 +31,6 @@ test('\'dial-phone\'', async(t) => {
{
"verb": "dial",
"callerId": from,
"callerName": "test_callerName",
"actionHook": "/actionHook",
"timeLimit": 5,
"target": [
@@ -57,7 +56,6 @@ test('\'dial-phone\'', async(t) => {
"method": "POST",
},
"from": from,
"callerName": "Tom",
"to": {
"type": "phone",
"number": "15583084808"

View File

@@ -4,15 +4,6 @@ const bent = require('bent');
const getJSON = bent('json')
const clearModule = require('clear-module');
const {provisionCallHook} = require('./utils')
const {
GCP_JSON_KEY,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
SONIOX_API_KEY,
DEEPGRAM_API_KEY,
MICROSOFT_REGION,
MICROSOFT_API_KEY,
} = require('../lib/config');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -27,7 +18,7 @@ function connect(connectable) {
}
test('\'gather\' test - google', async(t) => {
if (!GCP_JSON_KEY) {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
@@ -67,7 +58,7 @@ test('\'gather\' test - google', async(t) => {
});
test('\'gather\' test - default (google)', async(t) => {
if (!GCP_JSON_KEY) {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
@@ -103,7 +94,7 @@ test('\'gather\' test - default (google)', async(t) => {
});
test('\'gather\' test - microsoft', async(t) => {
if (!MICROSOFT_REGION || !MICROSOFT_API_KEY) {
if (!process.env.MICROSOFT_REGION || !process.env.MICROSOFT_API_KEY) {
t.pass('skipping microsoft tests');
return t.end();
}
@@ -143,7 +134,7 @@ test('\'gather\' test - microsoft', async(t) => {
});
test('\'gather\' test - aws', async(t) => {
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
t.pass('skipping aws tests');
return t.end();
}
@@ -183,7 +174,7 @@ test('\'gather\' test - aws', async(t) => {
});
test('\'gather\' test - deepgram', async(t) => {
if (!DEEPGRAM_API_KEY ) {
if (!process.env.DEEPGRAM_API_KEY ) {
t.pass('skipping deepgram tests');
return t.end();
}
@@ -201,7 +192,7 @@ test('\'gather\' test - deepgram', async(t) => {
"vendor": "deepgram",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": DEEPGRAM_API_KEY
"apiKey": process.env.DEEPGRAM_API_KEY
}
},
"timeout": 10,
@@ -225,7 +216,7 @@ test('\'gather\' test - deepgram', async(t) => {
});
test('\'gather\' test - soniox', async(t) => {
if (!SONIOX_API_KEY ) {
if (!process.env.SONIOX_API_KEY ) {
t.pass('skipping soniox tests');
return t.end();
}
@@ -243,7 +234,7 @@ test('\'gather\' test - soniox', async(t) => {
"vendor": "deepgram",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": SONIOX_API_KEY
"apiKey": process.env.SONIOX_API_KEY
}
},
"timeout": 10,

View File

@@ -4,15 +4,6 @@ const bent = require('bent');
const getJSON = bent('json')
const clearModule = require('clear-module');
const {provisionCallHook} = require('./utils')
const {
GCP_JSON_KEY,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
MICROSOFT_REGION,
MICROSOFT_API_KEY,
SONIOX_API_KEY,
DEEPGRAM_API_KEY,
} = require('../lib/config');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -27,7 +18,7 @@ function connect(connectable) {
}
test('\'transcribe\' test - google', async(t) => {
if (!GCP_JSON_KEY) {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
@@ -64,7 +55,7 @@ test('\'transcribe\' test - google', async(t) => {
});
test('\'transcribe\' test - microsoft', async(t) => {
if (!MICROSOFT_REGION || !MICROSOFT_API_KEY) {
if (!process.env.MICROSOFT_REGION || !process.env.MICROSOFT_API_KEY) {
t.pass('skipping microsoft tests');
return t.end();
}
@@ -101,7 +92,7 @@ test('\'transcribe\' test - microsoft', async(t) => {
});
test('\'transcribe\' test - aws', async(t) => {
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
t.pass('skipping aws tests');
return t.end();
}
@@ -138,7 +129,7 @@ test('\'transcribe\' test - aws', async(t) => {
});
test('\'transcribe\' test - deepgram', async(t) => {
if (!DEEPGRAM_API_KEY ) {
if (!process.env.DEEPGRAM_API_KEY ) {
t.pass('skipping deepgram tests');
return t.end();
}
@@ -155,7 +146,7 @@ test('\'transcribe\' test - deepgram', async(t) => {
"vendor": "deepgram",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": DEEPGRAM_API_KEY
"apiKey": process.env.DEEPGRAM_API_KEY
}
},
"transcriptionHook": "/transcriptionHook"
@@ -178,7 +169,7 @@ test('\'transcribe\' test - deepgram', async(t) => {
});
test('\'transcribe\' test - soniox', async(t) => {
if (!SONIOX_API_KEY ) {
if (!process.env.SONIOX_API_KEY ) {
t.pass('skipping soniox tests');
return t.end();
}
@@ -195,7 +186,7 @@ test('\'transcribe\' test - soniox', async(t) => {
"vendor": "soniox",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": SONIOX_API_KEY
"apiKey": process.env.SONIOX_API_KEY
}
},
"transcriptionHook": "/transcriptionHook"

View File

@@ -2,17 +2,13 @@ const test = require('tape');
const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module');
const {provisionCallHook} = require('./utils')
const {
JAMBONES_LOGLEVEL,
JAMBONES_TIME_SERIES_HOST
} = require('../lib/config');
const opts = {
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;},
level: JAMBONES_LOGLEVEL
level: process.env.JAMBONES_LOGLEVEL || 'info'
};
const logger = require('pino')(opts);
const { queryAlerts } = require('@jambonz/time-series')(
logger, JAMBONES_TIME_SERIES_HOST
logger, process.env.JAMBONES_TIME_SERIES_HOST
);
process.on('unhandledRejection', (reason, p) => {

View File

@@ -3,10 +3,7 @@ const sinon = require('sinon');
const proxyquire = require("proxyquire");
proxyquire.noCallThru();
const MockWebsocket = require('./ws-mock')
const {
JAMBONES_LOGLEVEL,
} = require('../lib/config');
const logger = require('pino')({level: JAMBONES_LOGLEVEL});
const logger = require('pino')({level: process.env.JAMBONES_LOGLEVEL || 'error'});
const BaseRequestor = proxyquire(
"../lib/utils/base-requestor",

View File

@@ -7,16 +7,12 @@ const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
const { OTLPTraceExporter } = require ('@opentelemetry/exporter-trace-otlp-http');
const {
JAMBONES_OTEL_ENABLED,
OTEL_EXPORTER_JAEGER_AGENT_HOST,
OTEL_EXPORTER_JAEGER_ENDPOINT,
OTEL_EXPORTER_ZIPKIN_URL,
OTEL_EXPORTER_COLLECTOR_URL
} = require('./lib/config');
//const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
//const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
//const { PinoInstrumentation } = require('@opentelemetry/instrumentation-pino');
module.exports = (serviceName) => {
if (JAMBONES_OTEL_ENABLED) {
if (process.env.JAMBONES_OTEL_ENABLED) {
const {version} = require('./package.json');
const provider = new NodeTracerProvider({
resource: new Resource({
@@ -26,15 +22,15 @@ module.exports = (serviceName) => {
});
let exporter;
if (OTEL_EXPORTER_JAEGER_AGENT_HOST || OTEL_EXPORTER_JAEGER_ENDPOINT) {
if (process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST || process.env.OTEL_EXPORTER_JAEGER_ENDPOINT) {
exporter = new JaegerExporter();
}
else if (OTEL_EXPORTER_ZIPKIN_URL) {
exporter = new ZipkinExporter({url:OTEL_EXPORTER_ZIPKIN_URL});
else if (process.env.OTEL_EXPORTER_ZIPKIN_URL) {
exporter = new ZipkinExporter({url:process.env.OTEL_EXPORTER_ZIPKIN_URL});
}
else {
exporter = new OTLPTraceExporter({
url: OTEL_EXPORTER_COLLECTOR_URL
url: process.OTEL_EXPORTER_COLLECTOR_URL
});
}