mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 08:40:38 +00:00
282 lines
7.8 KiB
JavaScript
282 lines
7.8 KiB
JavaScript
const Mrf = require('drachtio-fsmrf');
|
|
const os = require('os');
|
|
const {
|
|
JAMBONES_MYSQL_HOST,
|
|
JAMBONES_MYSQL_USER,
|
|
JAMBONES_MYSQL_PASSWORD,
|
|
JAMBONES_MYSQL_DATABASE,
|
|
JAMBONES_MYSQL_CONNECTION_LIMIT,
|
|
JAMBONES_MYSQL_PORT,
|
|
JAMBONES_FREESWITCH,
|
|
SMPP_URL,
|
|
JAMBONES_TIME_SERIES_HOST,
|
|
JAMBONES_ESL_LISTEN_ADDRESS,
|
|
PORT,
|
|
HTTP_IP,
|
|
NODE_ENV,
|
|
} = require('../config');
|
|
const Registrar = require('@jambonz/mw-registrar');
|
|
const assert = require('assert');
|
|
|
|
function getLocalIp() {
|
|
const interfaces = os.networkInterfaces();
|
|
for (const interfaceName in interfaces) {
|
|
const interface = interfaces[interfaceName];
|
|
for (const iface of interface) {
|
|
if (iface.family === 'IPv4' && !iface.internal) {
|
|
return iface.address;
|
|
}
|
|
}
|
|
}
|
|
return '127.0.0.1'; // Fallback to localhost if no suitable interface found
|
|
}
|
|
|
|
function initMS(logger, wrapper, ms) {
|
|
Object.assign(wrapper, {ms, active: true, connects: 1});
|
|
logger.info(`connected to freeswitch at ${ms.address}`);
|
|
|
|
ms.conn
|
|
.on('esl::end', () => {
|
|
wrapper.active = false;
|
|
logger.info(`lost connection to freeswitch at ${ms.address}`);
|
|
})
|
|
.on('esl::ready', () => {
|
|
if (wrapper.connects > 0) {
|
|
logger.info(`connected to freeswitch at ${ms.address}`);
|
|
}
|
|
wrapper.connects = 1;
|
|
wrapper.active = true;
|
|
});
|
|
|
|
ms.on('channel::open', (evt) => {
|
|
logger.debug({evt}, `mediaserver ${ms.address} added endpoint`);
|
|
});
|
|
ms.on('channel::close', (evt) => {
|
|
logger.debug({evt}, `mediaserver ${ms.address} removed endpoint`);
|
|
});
|
|
}
|
|
|
|
function installSrfLocals(srf, logger) {
|
|
logger.debug('installing srf locals');
|
|
assert(!srf.locals.dbHelpers);
|
|
const {tracer} = srf.locals.otel;
|
|
const {getSBC, lifecycleEmitter} = require('./sbc-pinger')(logger);
|
|
const StatsCollector = require('@jambonz/stats-collector');
|
|
const stats = srf.locals.stats = new StatsCollector(logger);
|
|
|
|
// freeswitch connections (typically we connect to only one)
|
|
const mrf = new Mrf(srf);
|
|
const mediaservers = [];
|
|
let idxStart = 0;
|
|
|
|
(async function() {
|
|
const fsInventory = JAMBONES_FREESWITCH
|
|
.split(',')
|
|
.map((fs) => {
|
|
const arr = /^([^:]*):([^:]*):([^:]*)(?::([^:]*))?/.exec(fs);
|
|
assert.ok(arr, `Invalid syntax JAMBONES_FREESWITCH: ${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;
|
|
return opts;
|
|
});
|
|
logger.info({fsInventory}, 'freeswitch inventory');
|
|
|
|
for (const fs of fsInventory) {
|
|
const val = {opts: fs, active: false, connects: 0};
|
|
mediaservers.push(val);
|
|
try {
|
|
const ms = await mrf.connect(fs);
|
|
initMS(logger, val, ms);
|
|
}
|
|
catch (err) {
|
|
logger.info({err}, `failed connecting to freeswitch at ${fs.address}, will retry shortly: ${err.message}`);
|
|
}
|
|
}
|
|
// retry to connect to any that were initially offline
|
|
setInterval(async() => {
|
|
for (const val of mediaservers) {
|
|
if (val.connects === 0) {
|
|
try {
|
|
logger.info({mediaserver: val.opts}, 'Retrying initial connection to media server');
|
|
const ms = await mrf.connect(val.opts);
|
|
initMS(logger, val, ms);
|
|
} catch (err) {
|
|
logger.info({err}, `failed connecting to freeswitch at ${val.opts.address}, will retry shortly`);
|
|
}
|
|
}
|
|
}
|
|
}, 3000);
|
|
|
|
// if we have a single freeswitch (as is typical) report stats periodically
|
|
if (mediaservers.length === 1) {
|
|
srf.locals.mediaservers = [mediaservers[0].ms];
|
|
setInterval(() => {
|
|
try {
|
|
if (mediaservers[0].ms && mediaservers[0].active) {
|
|
const ms = mediaservers[0].ms;
|
|
stats.gauge('fs.media.channels.in_use', ms.currentSessions);
|
|
stats.gauge('fs.media.channels.free', ms.maxSessions - ms.currentSessions);
|
|
stats.gauge('fs.media.calls_per_second', ms.cps);
|
|
stats.gauge('fs.media.cpu_idle', ms.cpuIdle);
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.info(err, 'Error sending media server metrics');
|
|
}
|
|
}, 30000);
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* return an active media server
|
|
*/
|
|
function getFreeswitch() {
|
|
const active = mediaservers.filter((mediaserver) => mediaserver.active);
|
|
if (active.length === 0) return null;
|
|
return active[idxStart++ % active.length].ms;
|
|
}
|
|
|
|
const {
|
|
pool,
|
|
lookupAppByPhoneNumber,
|
|
lookupAppByRegex,
|
|
lookupAppBySid,
|
|
lookupAppByRealm,
|
|
lookupAppByTeamsTenant,
|
|
lookupTeamsByAccount,
|
|
lookupAccountBySid,
|
|
lookupAccountCapacitiesBySid,
|
|
lookupSmppGateways,
|
|
lookupClientByAccountAndUsername,
|
|
lookupSystemInformation
|
|
} = 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
|
|
}, logger);
|
|
const {
|
|
client,
|
|
updateCallStatus,
|
|
retrieveCall,
|
|
listCalls,
|
|
deleteCall,
|
|
createHash,
|
|
retrieveHash,
|
|
deleteKey,
|
|
addKey,
|
|
retrieveKey,
|
|
retrieveSet,
|
|
addToSet,
|
|
removeFromSet,
|
|
monitorSet,
|
|
pushBack,
|
|
popFront,
|
|
removeFromList,
|
|
getListPosition,
|
|
lengthOfList,
|
|
addToSortedSet,
|
|
retrieveFromSortedSet,
|
|
retrieveByPatternSortedSet,
|
|
sortedSetLength,
|
|
sortedSetPositionByPattern,
|
|
} = require('@jambonz/realtimedb-helpers')({}, logger, tracer);
|
|
const registrar = new Registrar(logger, client);
|
|
const {
|
|
synthAudio,
|
|
addFileToCache,
|
|
getNuanceAccessToken,
|
|
getIbmAccessToken,
|
|
getAwsAuthToken,
|
|
getVerbioAccessToken
|
|
} = require('@jambonz/speech-utils')({}, logger);
|
|
const {
|
|
writeAlerts,
|
|
AlertType
|
|
} = require('@jambonz/time-series')(logger, {
|
|
host: JAMBONES_TIME_SERIES_HOST,
|
|
commitSize: 50,
|
|
commitInterval: 'test' === NODE_ENV ? 7 : 20
|
|
});
|
|
|
|
let localIp;
|
|
try {
|
|
// Either use the configured IP address or discover it
|
|
localIp = HTTP_IP || getLocalIp();
|
|
} catch (err) {
|
|
logger.error({err}, 'installSrfLocals - error detecting local ipv4 address');
|
|
}
|
|
|
|
srf.locals = {...srf.locals,
|
|
dbHelpers: {
|
|
client,
|
|
registrar,
|
|
pool,
|
|
lookupAppByPhoneNumber,
|
|
lookupAppByRegex,
|
|
lookupAppBySid,
|
|
lookupAppByRealm,
|
|
lookupAppByTeamsTenant,
|
|
lookupTeamsByAccount,
|
|
lookupAccountBySid,
|
|
lookupAccountCapacitiesBySid,
|
|
lookupSmppGateways,
|
|
lookupClientByAccountAndUsername,
|
|
lookupSystemInformation,
|
|
updateCallStatus,
|
|
retrieveCall,
|
|
listCalls,
|
|
deleteCall,
|
|
synthAudio,
|
|
getAwsAuthToken,
|
|
addFileToCache,
|
|
createHash,
|
|
retrieveHash,
|
|
deleteKey,
|
|
addKey,
|
|
retrieveKey,
|
|
retrieveSet,
|
|
addToSet,
|
|
removeFromSet,
|
|
monitorSet,
|
|
pushBack,
|
|
popFront,
|
|
removeFromList,
|
|
lengthOfList,
|
|
getListPosition,
|
|
getNuanceAccessToken,
|
|
getIbmAccessToken,
|
|
addToSortedSet,
|
|
retrieveFromSortedSet,
|
|
retrieveByPatternSortedSet,
|
|
sortedSetLength,
|
|
sortedSetPositionByPattern,
|
|
getVerbioAccessToken
|
|
},
|
|
parentLogger: logger,
|
|
getSBC,
|
|
getSmpp: () => {
|
|
return SMPP_URL;
|
|
},
|
|
lifecycleEmitter,
|
|
getFreeswitch,
|
|
stats: stats,
|
|
writeAlerts,
|
|
AlertType
|
|
};
|
|
|
|
if (localIp) {
|
|
srf.locals.ipv4 = localIp;
|
|
srf.locals.serviceUrl = `http://${localIp}:${PORT}`;
|
|
}
|
|
}
|
|
|
|
module.exports = installSrfLocals;
|