mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-07-24 21:22:17 +00:00
9973670a6c
Applications currently have no way to observe a mid-call hold/un-hold.
The feature-server handles the in-dialog re-INVITE internally (re-modifies
the endpoint, returns 200) but never notifies the app, so a transcription
or agent-assist app can't pause/resume when a caller is held or retrieved.
This detects the hold state transition on a re-INVITE and delivers it over
the existing sipRequestWithinDialogHook channel as a verb:hook, plus emits
a local 'hold'/'unhold' event so in-process tasks (e.g. transcribe) can react.
- call-session.js: ordinary calls — reuse isOnhold() (a=sendonly/a=inactive).
- siprec-call-session.js: SIPREC overrides _onReinvite, so a separate handler
is required. SIPREC recording streams are sendonly by nature, so a stream
flipping to a=inactive is treated as the hold signal; the full multipart
body (SDP + rs-metadata) is always forwarded. Fires on transition only;
other re-INVITEs are delivered as event:"reinvite".
Delivery uses requestor.request('verb:hook', ...) — the same channel
_handleRefer uses — rather than a task-bound performHook, so events are not
dropped if the current task has ended. Enable per call with:
{"verb":"config","sipRequestWithinDialogHook":"/hold-events"}
Confirmed live against Cisco CUBE (Cisco-SIPGateway/IOS-17.12.7b) over SIPREC:
both recording streams flip sendonly<->inactive on hold/resume, each
transition emitted correctly.
The whole feature is opt-in behind the JAMBONES_HOLD_UNHOLD_EVENTS env var;
when it is unset, _notifyHoldState / _notifySipRecReinvite are no-ops (neither
the local event nor the hook fires).
Reinstates the feature previously reverted in #1563. The revert was due to the
unit test failing in the full suite: lib/config.js captures the env var once at
load time, and earlier suite files require config/session modules (flag unset)
before this test could set it, so the enabled-path assertions saw a disabled
feature. The test now force-reloads config + the session modules with the flag
set (and restores the module cache afterward), so it is robust to suite load order.
245 lines
9.4 KiB
JavaScript
245 lines
9.4 KiB
JavaScript
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');
|
|
if (process.env.JAMBONES_REDIS_SENTINELS) {
|
|
assert.ok(process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
|
|
'missing JAMBONES_REDIS_SENTINEL_MASTER_NAME env var, JAMBONES_REDIS_SENTINEL_PASSWORD env var is optional');
|
|
} else {
|
|
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 = parseInt(process.env.JAMBONES_MYSQL_REFRESH_TTL, 10) || 0;
|
|
const JAMBONES_MYSQL_CONNECTION_LIMIT = parseInt(process.env.JAMBONES_MYSQL_CONNECTION_LIMIT, 10) || 10;
|
|
|
|
/* 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 JAMBONES_WS_PING_INTERVAL_MS = parseInt(process.env.JAMBONES_WS_PING_INTERVAL_MS, 10) || 0;
|
|
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 JAMBONES_HOLD_UNHOLD_EVENTS = process.env.JAMBONES_HOLD_UNHOLD_EVENTS;
|
|
|
|
const PORT = parseInt(process.env.HTTP_PORT, 10) || 3000;
|
|
const HTTP_IP = process.env.HTTP_IP;
|
|
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_ARN = process.env.AWS_SNS_TOPIC_ARN;
|
|
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;
|
|
|
|
const JAMBONES_AWS_TRANSCRIBE_USE_GRPC = process.env.JAMBONES_AWS_TRANSCRIBE_USE_GRPC;
|
|
|
|
/* 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 = parseInt(process.env.JAMBONES_HTTP_TIMEOUT, 10) || 10000;
|
|
const HTTP_PROXY_IP = process.env.JAMBONES_HTTP_PROXY_IP;
|
|
const HTTP_PROXY_PORT = process.env.JAMBONES_HTTP_PROXY_PORT;
|
|
const HTTP_PROXY_PROTOCOL = process.env.JAMBONES_HTTP_PROXY_PROTOCOL || 'http';
|
|
const HTTP_USER_AGENT_HEADER = process.env.JAMBONES_HTTP_USER_AGENT_HEADER || 'jambonz';
|
|
|
|
const OPTIONS_PING_INTERVAL = parseInt(process.env.OPTIONS_PING_INTERVAL, 10) || 30000;
|
|
|
|
const JAMBONZ_RECORD_WS_BASE_URL = process.env.JAMBONZ_RECORD_WS_BASE_URL || process.env.JAMBONES_RECORD_WS_BASE_URL;
|
|
const JAMBONZ_RECORD_WS_USERNAME = process.env.JAMBONZ_RECORD_WS_USERNAME || process.env.JAMBONES_RECORD_WS_USERNAME;
|
|
const JAMBONZ_RECORD_WS_PASSWORD = process.env.JAMBONZ_RECORD_WS_PASSWORD || process.env.JAMBONES_RECORD_WS_PASSWORD;
|
|
const JAMBONZ_DIAL_PAI_HEADER = process.env.JAMBONZ_DIAL_PAI_HEADER || false;
|
|
const JAMBONES_DISABLE_DIRECT_P2P_CALL = process.env.JAMBONES_DISABLE_DIRECT_P2P_CALL || false;
|
|
|
|
const JAMBONES_EAGERLY_PRE_CACHE_AUDIO = parseInt(process.env.JAMBONES_EAGERLY_PRE_CACHE_AUDIO, 10) || 0;
|
|
|
|
const JAMBONES_USE_FREESWITCH_TIMER_FD = process.env.JAMBONES_USE_FREESWITCH_TIMER_FD;
|
|
const JAMBONES_DIAL_SBC_FOR_REGISTERED_USER = process.env.JAMBONES_DIAL_SBC_FOR_REGISTERED_USER || false;
|
|
const JAMBONES_MEDIA_TIMEOUT_MS = process.env.JAMBONES_MEDIA_TIMEOUT_MS || 0;
|
|
const JAMBONES_MEDIA_HOLD_TIMEOUT_MS = process.env.JAMBONES_MEDIA_HOLD_TIMEOUT_MS || 0;
|
|
const JAMBONES_WEBHOOK_ERROR_RETURN = parseInt(process.env.JAMBONES_WEBHOOK_ERROR_RETURN, 10) || 480;
|
|
|
|
/* say / tts */
|
|
const JAMBONES_SAY_CHUNK_SIZE = parseInt(process.env.JAMBONES_SAY_CHUNK_SIZE, 10) || 900;
|
|
|
|
// jambonz
|
|
const JAMBONES_TRANSCRIBE_EP_DESTROY_DELAY_MS =
|
|
process.env.JAMBONES_TRANSCRIBE_EP_DESTROY_DELAY_MS;
|
|
|
|
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,
|
|
SMPP_URL,
|
|
JAMBONES_NETWORK_CIDR,
|
|
JAMBONES_API_BASE_URL,
|
|
JAMBONES_TIME_SERIES_HOST,
|
|
JAMBONES_INJECT_CONTENT,
|
|
JAMBONES_HOLD_UNHOLD_EVENTS,
|
|
JAMBONES_EAGERLY_PRE_CACHE_AUDIO,
|
|
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,
|
|
HTTP_IP,
|
|
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_ARN,
|
|
AWS_SNS_PORT_MAX,
|
|
|
|
ANCHOR_MEDIA_ALWAYS,
|
|
VMD_HINTS_FILE,
|
|
JAMBONES_FREESWITCH_MAX_CALL_DURATION_MINS,
|
|
JAMBONES_AWS_TRANSCRIBE_USE_GRPC,
|
|
|
|
LEGACY_CRYPTO,
|
|
JWT_SECRET,
|
|
ENCRYPTION_SECRET,
|
|
HTTP_POOL,
|
|
HTTP_POOLSIZE,
|
|
HTTP_PIPELINING,
|
|
HTTP_TIMEOUT,
|
|
HTTP_PROXY_IP,
|
|
HTTP_PROXY_PORT,
|
|
HTTP_PROXY_PROTOCOL,
|
|
HTTP_USER_AGENT_HEADER,
|
|
OPTIONS_PING_INTERVAL,
|
|
RESPONSE_TIMEOUT_MS,
|
|
JAMBONES_WS_HANDSHAKE_TIMEOUT_MS,
|
|
JAMBONES_WS_MAX_PAYLOAD,
|
|
JAMBONES_WS_PING_INTERVAL_MS,
|
|
MAX_RECONNECTS,
|
|
GCP_JSON_KEY,
|
|
MICROSOFT_REGION,
|
|
MICROSOFT_API_KEY,
|
|
SONIOX_API_KEY,
|
|
DEEPGRAM_API_KEY,
|
|
JAMBONZ_RECORD_WS_BASE_URL,
|
|
JAMBONZ_RECORD_WS_USERNAME,
|
|
JAMBONZ_RECORD_WS_PASSWORD,
|
|
JAMBONZ_DIAL_PAI_HEADER,
|
|
JAMBONES_DISABLE_DIRECT_P2P_CALL,
|
|
JAMBONES_USE_FREESWITCH_TIMER_FD,
|
|
JAMBONES_DIAL_SBC_FOR_REGISTERED_USER,
|
|
JAMBONES_MEDIA_TIMEOUT_MS,
|
|
JAMBONES_MEDIA_HOLD_TIMEOUT_MS,
|
|
JAMBONES_SAY_CHUNK_SIZE,
|
|
JAMBONES_TRANSCRIBE_EP_DESTROY_DELAY_MS,
|
|
JAMBONES_WEBHOOK_ERROR_RETURN
|
|
};
|