Feature/centralized configs (#310)

* [snyk] fix vulnerabilities

* move all process.env in one config

* update log level in config

* check envs

* fix imports in tests for microsoft, soniox, deepgram

* fix import in gather-test

* fix missing imports

---------

Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
This commit is contained in:
Markus Frindt
2023-04-11 18:46:52 +02:00
committed by GitHub
parent 5d50f68725
commit 86df53f8c4
28 changed files with 430 additions and 133 deletions

48
app.js
View File

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

View File

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

193
lib/config.js Normal file
View File

@@ -0,0 +1,193 @@
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_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_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

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

View File

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

View File

@@ -17,6 +17,10 @@ const { normalizeJambones } = require('@jambonz/verb-specifications');
const listTaskNames = require('../utils/summarize-tasks'); const listTaskNames = require('../utils/summarize-tasks');
const HttpRequestor = require('../utils/http-requestor'); const HttpRequestor = require('../utils/http-requestor');
const WsRequestor = require('../utils/ws-requestor'); const WsRequestor = require('../utils/ws-requestor');
const {
JAMBONES_INJECT_CONTENT,
AWS_REGION
} = require('../config');
const BADPRECONDITIONS = 'preconditions not met'; const BADPRECONDITIONS = 'preconditions not met';
const CALLER_CANCELLED_ERR_MSG = 'Response not sent due to unknown transaction'; const CALLER_CANCELLED_ERR_MSG = 'Response not sent due to unknown transaction';
@@ -626,7 +630,7 @@ class CallSession extends Emitter {
speech_credential_sid: credential.speech_credential_sid, speech_credential_sid: credential.speech_credential_sid,
accessKeyId: credential.access_key_id, accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key, secretAccessKey: credential.secret_access_key,
region: credential.aws_region || process.env.AWS_REGION region: credential.aws_region || AWS_REGION
}; };
} }
else if ('microsoft' === vendor) { else if ('microsoft' === vendor) {
@@ -1199,7 +1203,7 @@ class CallSession extends Emitter {
this.logger.info({tasks: listTaskNames(t)}, 'CallSession:_onCommand new task list'); this.logger.info({tasks: listTaskNames(t)}, 'CallSession:_onCommand new task list');
this.replaceApplication(t); this.replaceApplication(t);
} }
else if (process.env.JAMBONES_INJECT_CONTENT) { else if (JAMBONES_INJECT_CONTENT) {
this._injectTasks(t); this._injectTasks(t);
this.logger.info({tasks: listTaskNames(this.tasks)}, 'CallSession:_onCommand - updated task list'); this.logger.info({tasks: listTaskNames(this.tasks)}, 'CallSession:_onCommand - updated task list');
} }

View File

@@ -15,6 +15,7 @@ const DtmfCollector = require('../utils/dtmf-collector');
const dbUtils = require('../utils/db-utils'); const dbUtils = require('../utils/db-utils');
const debug = require('debug')('jambonz:feature-server'); const debug = require('debug')('jambonz:feature-server');
const {parseUri} = require('drachtio-srf'); const {parseUri} = require('drachtio-srf');
const {ANCHOR_MEDIA_ALWAYS} = require('../config');
function parseDtmfOptions(logger, dtmfCapture) { function parseDtmfOptions(logger, dtmfCapture) {
let parentDtmfCollector, childDtmfCollector; let parentDtmfCollector, childDtmfCollector;
@@ -135,7 +136,7 @@ class TaskDial extends Task {
get canReleaseMedia() { get canReleaseMedia() {
const keepAnchor = this.data.anchorMedia || const keepAnchor = this.data.anchorMedia ||
process.env.ANCHOR_MEDIA_ALWAYS || ANCHOR_MEDIA_ALWAYS ||
this.listenTask || this.listenTask ||
this.transcribeTask || this.transcribeTask ||
this.startAmd; this.startAmd;

View File

@@ -12,7 +12,11 @@ const {
NvidiaTranscriptionEvents, NvidiaTranscriptionEvents,
JambonzTranscriptionEvents JambonzTranscriptionEvents
} = require('../utils/constants'); } = 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 makeTask = require('./make_task');
const assert = require('assert'); const assert = require('assert');
@@ -71,7 +75,7 @@ class TaskGather extends Task {
this.isContinuousAsr = this.asrTimeout > 0; this.isContinuousAsr = this.asrTimeout > 0;
if (Array.isArray(this.data.recognizer.hints) && if (Array.isArray(this.data.recognizer.hints) &&
0 == this.data.recognizer.hints.length && process.env.JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS) { 0 == this.data.recognizer.hints.length && JAMBONES_GATHER_CLEAR_GLOBAL_HINTS_ON_EMPTY_HINTS) {
logger.debug('Gather: an empty hints array was supplied, so we will mask global hints'); logger.debug('Gather: an empty hints array was supplied, so we will mask global hints');
this.maskGlobalSttHints = true; this.maskGlobalSttHints = true;
} }
@@ -162,7 +166,7 @@ class TaskGather extends Task {
asrDtmfTerminationDigit: this.asrDtmfTerminationDigit asrDtmfTerminationDigit: this.asrDtmfTerminationDigit
}, 'Gather:exec - enabling continuous ASR since it is turned on for the session'); }, '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 && if ((JAMBONZ_GATHER_EARLY_HINTS_MATCH || JAMBONES_GATHER_EARLY_HINTS_MATCH) && this.needsStt &&
!this.isContinuousAsr && !this.isContinuousAsr &&
this.data.recognizer?.hints?.length > 0 && this.data.recognizer?.hints?.length <= 10) { 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 {TaskName, TaskPreconditions} = require('../utils/constants');
const bent = require('bent'); const bent = require('bent');
const uuidv4 = require('uuid-random'); const uuidv4 = require('uuid-random');
const {K8S} = require('../config');
class TaskMessage extends Task { class TaskMessage extends Task {
constructor(logger, opts) { constructor(logger, opts) {
super(logger, opts); super(logger, opts);
@@ -42,7 +42,7 @@ class TaskMessage extends Task {
} }
if (gw) { if (gw) {
this.logger.info({gw, accountSid}, 'Message:exec - using smpp to send message'); this.logger.info({gw, accountSid}, 'Message:exec - using smpp to send message');
url = process.env.K8S ? 'http://smpp' : getSmpp(); url = K8S ? 'http://smpp' : getSmpp();
relativeUrl = '/sms'; relativeUrl = '/sms';
payload = { payload = {
...payload, ...payload,

View File

@@ -8,7 +8,7 @@ const {
AvmdEvents AvmdEvents
} = require('./constants'); } = require('./constants');
const bugname = 'amd_bug'; const bugname = 'amd_bug';
const {VMD_HINTS_FILE} = process.env; const {VMD_HINTS_FILE} = require('../config');
let voicemailHints = []; let voicemailHints = [];
const updateHints = async(file, callback) => { const updateHints = async(file, callback) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,8 @@
"start": "node app", "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/ ", "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", "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib" "jslint": "eslint app.js lib",
"jslint:fix": "eslint app.js lib --fix"
}, },
"dependencies": { "dependencies": {
"@jambonz/db-helpers": "^0.7.4", "@jambonz/db-helpers": "^0.7.4",

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const Websocket = require('ws'); const Websocket = require('ws');
const listenPort = process.env.HTTP_PORT || 3000; const {PORT} = require('../../lib/config');
const listenPort = PORT;
let json_mapping = new Map(); let json_mapping = new Map();
let hook_mapping = new Map(); let hook_mapping = new Map();
let ws_packet_count = new Map(); let ws_packet_count = new Map();

View File

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

View File

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

View File

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