Files
sbc-sip-sidecar/lib/server-control/index.js
T
Dave HortonandClaude Fable 5.1 9f00a513f8 feat: include jambonz and drachtio versions in X-Jambonz-Discover response (#152)
- jambonzVersion is read from the schema_version table via the existing
  db-helpers pool (now exposed on srf.locals); a DB error yields null
  rather than failing discovery
- drachtioVersion is captured from the drachtio connect handshake and
  stored on srf.locals
- README example updated

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:23:59 -04:00

90 lines
3.0 KiB
JavaScript

const debug = require('debug')('jambonz:sbc-server-control');
const { JAMBONES_SERVER_CONTROL, JAMBONES_CLUSTER_ID } = require('../config');
/* SIP header a client sets (X-Jambonz-Discover: true) to request a topology discovery response */
const DISCOVER_HEADER = 'X-Jambonz-Discover';
/**
* Whether the server-control features are enabled.
* Gated behind the JAMBONES_SERVER_CONTROL env var; treats the usual truthy
* strings ('1', 'true', 'yes') as enabled, everything else as disabled.
*/
const isEnabled = () => {
if (!JAMBONES_SERVER_CONTROL) return false;
return /^(1|true|yes)$/i.test(`${JAMBONES_SERVER_CONTROL}`.trim());
};
/* redis set names holding the active servers of each type for this cluster */
const _setNames = () => {
const prefix = JAMBONES_CLUSTER_ID || 'default';
return {
featureServers: `${prefix}:active-fs`,
sipServers: `${prefix}:active-sip`,
rtpServers: `${prefix}:active-rtp`
};
};
/**
* Return true if the request is a discovery request, i.e. it carries the
* `X-Jambonz-Discover: true` header. Header presence/value is checked
* case-insensitively.
*/
const isDiscoverRequest = (req) => {
if (!req.has(DISCOVER_HEADER)) return false;
return /^true$/i.test(`${req.get(DISCOVER_HEADER)}`.trim());
};
/**
* Read the installed jambonz version from the schema_version table.
* A failure here must not fail discovery, so it resolves to null on any error.
* @param {object} pool - mysql connection pool (may be undefined in tests)
* @returns {Promise<string|null>}
*/
const _lookupJambonzVersion = (pool) => new Promise((resolve) => {
if (!pool) return resolve(null);
pool.query('SELECT version FROM schema_version', (err, results) => {
if (err) {
debug({err}, 'failed reading schema_version');
return resolve(null);
}
resolve(results && results.length ? results[0].version : null);
});
});
/**
* Read the current cluster topology from redis (the IPs of the feature
* servers, SIP servers and RTP servers) plus the jambonz version
* (schema_version table) and the drachtio server version (captured on connect).
* @param {object} srf - drachtio srf instance (uses srf.locals.retrieveSet, srf.locals.pool)
* @returns {Promise<{jambonzVersion: string|null, drachtioVersion: string|null,
* featureServers: string[], sipServers: string[], rtpServers: string[]}>}
*/
const discoverServers = async(srf) => {
const { retrieveSet, pool, drachtioVersion } = srf.locals;
const { featureServers, sipServers, rtpServers } = _setNames();
const [fs, sip, rtp, jambonzVersion] = await Promise.all([
retrieveSet(featureServers),
retrieveSet(sipServers),
retrieveSet(rtpServers),
_lookupJambonzVersion(pool)
]);
const topology = {
jambonzVersion,
drachtioVersion: drachtioVersion || null,
featureServers: fs || [],
sipServers: sip || [],
rtpServers: rtp || []
};
debug({topology}, 'discovered cluster topology from redis');
return topology;
};
module.exports = {
DISCOVER_HEADER,
isEnabled,
isDiscoverRequest,
discoverServers
};