mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-10-03 17:54:12 +00:00
6cc3dc45f2e58ea6c5cb46378bc4d20660bf08e7
* fix: carry siprec recording state across a feature server transfer transferCallToFeatureServer() stored the application, callInfo and remaining tasks, but not the SIPREC recording state, so the receiving session started at RecordingOff while the SBC was still recording. From there every live call control on the recording was rejected locally before an INFO was ever sent: stop and pause fail the guards in notifyRecordOptions, and a fresh startCallRecording is refused by the SBC as a duplicate. Carry recordOptions and the record state through the transfer and restore both on the receiving session, which also keeps propagateAnswer from issuing a second startCallRecording for a call that is already being recorded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: add standalone smoke test for siprec across a feature server transfer Drives the failing flow end to end - answer, start SIPREC, enqueue, create the agent call on a different feature server, dequeue by callSid - while acting as the SIPREC recorder, so it can tell whether the recording kept receiving media across the REFER and whether it was BYEd when the call ended. Needs two or more feature servers and a real inbound call, so it lives outside the mocha suite and is run by hand; npm test does not pick it up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: let the siprec smoke test originate both call legs itself Waiting for a human to dial in with music playing made the test hard to run and easy to make meaningless (a muted caller sends no RTP, so "the recording is silent" proves nothing). Add a built-in SIP endpoint that answers and streams a PCMU tone, and have the test place both legs through createCall, so a run needs only an account_sid and no carrier, DID or softphone. That makes the default mode exercise the transfer path in sbc-outbound; the previous flow is kept as SMOKE_MODE=inbound for the sbc-inbound path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hand the inherited recording state to one session only Review of the previous commit turned up two ways the carried state leaks, both because it was read off the shared application object and left there: - a call transferred twice kept a stale siprecRecording from the first hop. transferCallToFeatureServer copies cs.application, and the new guard only wrote the key, so a call recorded FS1->FS2, stopped there, then moved to FS3 arrived believing it was still recording: startCallRecording rejected as "already started", stopCallRecording sent an INFO for a session that was gone. - child legs got it too. dial passes cs.application straight to ConfirmCallSession and place-outdial spreads it for the adulting session, so those sessions came up with recordState=recording_on and the parent's SRS options while their own dialog had no siprec session at all. The transferred session now takes the state off the application as it adopts it, so exactly one session owns it. Also fixes the smoke test's silence measurement, which was computed per stream and reported a whole quiet window as a gap - a legitimately silent second stream failed the run - and splits the README's requirements by mode, since the default rest mode needs no portal application and no external caller. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: drop the standalone smoke script, the harness test covers it smoke-tests/siprec-fs-transfer was a self-contained driver for the cross-feature -server SIPREC flow, written before the same scenario landed in the smoke-tester harness as TestVerb_Siprec_SurvivesFeatureServerTransfer (plus a REST-leg variant for the sbc-outbound path). The harness version is the one that runs in CI, asserts the recorded audio with Deepgram rather than counting packets, and is what proved both fixes on a real cluster - keeping a second implementation of the same flow here only invites the two to drift. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jambonz-feature-server 
This application implements the core feature server of the jambones platform.
Note: If you are a developer looking to work on the code please read our how-to for that.
Configuration
Configuration is provided via environment variables:
| variable | meaning | required? |
|---|---|---|
| AWS_ACCESS_KEY_ID | aws access key id, used for TTS/STT as well SNS notifications | no |
| AWS_REGION | aws region | no |
| AWS_SECRET_ACCESS_KEY | aws secret access key, used per above | no |
| AWS_SNS_TOPIC_ARN | aws sns topic arn that scale-in lifecycle notifications will be published to | no |
| DRACHTIO_HOST | ip address of drachtio server (typically '127.0.0.1') | yes |
| DRACHTIO_PORT | listening port of drachtio server for control connections (typically 9022) | yes |
| DRACHTIO_SECRET | shared secret | yes |
| ENABLE_METRICS | if 1, metrics will be generated | no |
| ENCRYPTION_SECRET | secret for credential encryption(JWT_SECRET is deprecated) | yes |
| GOOGLE_APPLICATION_CREDENTIALS | path to gcp service key file | yes |
| HTTP_PORT | tcp port to listen on for API requests from jambonz-api-server | yes |
| HTTP_IP | IP Address for API requests from jambonz-api-server | no |
| JAMBONES_GATHER_EARLY_HINTS_MATCH | if true and hints are provided, gather will opportunistically review interim transcripts if possible to reduce ASR latency | no |
| JAMBONES_FREESWITCH | IP:port:secret for Freeswitch server (e.g. '127.0.0.1:8021:JambonzR0ck$' | yes |
| JAMBONES_HOLD_UNHOLD_EVENTS | if set, surface in-dialog hold/un-hold: emits a 'hold'/'unhold' event and, when sipRequestWithinDialogHook is configured, delivers it to the application. Disabled when unset | no |
| JAMBONES_LOGLEVEL | log level for application, 'info' or 'debug' | no |
| JAMBONES_MYSQL_HOST | mysql host | yes |
| JAMBONES_MYSQL_USER | mysql username | yes |
| JAMBONES_MYSQL_PASSWORD | mysql password | yes |
| JAMBONES_MYSQL_DATABASE | mysql data | yes |
| JAMBONES_MYSQL_CONNECTION_LIMIT | mysql connection limit | no |
| JAMBONES_NETWORK_CIDR | CIDR of private network that feature server is running in (e.g. '172.31.0.0/16') | yes |
| JAMBONES_REDIS_HOST | redis host | yes |
| JAMBONES_REDIS_PORT | redis port | yes |
| JAMBONES_SBCS | list of IP addresses (on the internal network) of SBCs, comma-separated | yes |
| STATS_HOST | ip address of metrics host (usually '127.0.0.1' since telegraf is installed locally | no |
| STATS_PORT | listening port for metrics host | no |
| STATS_PROTOCOL | 'tcp' or 'udp' | no |
| STATS_TELEGRAF | if 1, metrics will be generated in telegraf format | no |
| JAMBONZ_RECORD_WS_BASE_URL | recording websocket URL to send the recording audio | no |
| JAMBONZ_RECORD_WS_USERNAME | recording websocket username | no |
| JAMBONZ_RECORD_WS_PASSWORD | recording websocket password | no |
| ANCHOR_MEDIA_ALWAYS | keep media on media server | no |
| JAMBONZ_DISABLE_DIAL_PAI_HEADER | control P-Asserted-Identity header on B-Leg | no |
running under pm2
Typically, this application runs under pm2 using an ecosystem.config.js file similar to this:
module.exports = {
apps : [
{
name: 'jambonz-feature-server',
cwd: '/home/admin/apps/jambonz-feature-server',
script: 'app.js',
instance_var: 'INSTANCE_ID',
out_file: '/home/admin/.pm2/logs/jambonz-feature-server.log',
err_file: '/home/admin/.pm2/logs/jambonz-feature-server.log',
exec_mode: 'fork',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
GOOGLE_APPLICATION_CREDENTIALS: '/home/admin/credentials/gcp.json',
AWS_ACCESS_KEY_ID: 'XXXXXXXXXXXX',
AWS_SECRET_ACCESS_KEY: 'YYYYYYYYYYYYYYYYYYYYY',
AWS_REGION: 'us-west-1',
ENABLE_METRICS: 1,
STATS_HOST: '127.0.0.1',
STATS_PORT: 8125,
STATS_PROTOCOL: 'tcp',
STATS_TELEGRAF: 1,
AWS_SNS_TOPIC_ARN: 'arn:aws:sns:us-west-1:xxxxxxxxxxx:terraform-20201107200347128600000002',
JAMBONES_NETWORK_CIDR: '172.31.0.0/16',
JAMBONES_MYSQL_HOST: 'aurora-cluster-jambonz.cluster-yyyyyyyyyyy.us-west-1.rds.amazonaws.com',
JAMBONES_MYSQL_USER: 'admin',
JAMBONES_MYSQL_PASSWORD: 'foobarbz',
JAMBONES_MYSQL_DATABASE: 'jambones',
JAMBONES_MYSQL_CONNECTION_LIMIT: 10,
JAMBONES_REDIS_HOST: 'jambonz.zzzzzzz.0001.usw1.cache.amazonaws.com',
JAMBONES_REDIS_PORT: 6379,
JAMBONES_LOGLEVEL: 'debug',
HTTP_PORT: 3000,
DRACHTIO_HOST: '127.0.0.1',
DRACHTIO_PORT: 9022,
DRACHTIO_SECRET: 'sharedsecret',
JAMBONES_SBCS: '172.31.32.10',
JAMBONES_FREESWITCH: '127.0.0.1:8021:sharedsecret'
}
}]
};
Running the test suite
Please see this.
Languages
JavaScript
99.9%