77 Commits
Author SHA1 Message Date
Hoan Luu HuuandClaude Opus 5 db18b558ce add sip_reason_header to status Callback- #178 (#1579)
* feat: surface the SIP Reason header on call status events

Carriers fronting ISDN/E1 PRI trunks put the authoritative disconnect cause in
an RFC 3326 Reason header rather than in the SIP status line, e.g.

    SIP/2.0 408 Request Timeout
    Reason: Q.850 ;cause=18

Q.850 cause 18 is "no user responding" - nobody answered, not a platform fault.
Different causes also arrive under the same SIP status (503 with cause=38
network out of order, or cause=41 temporary failure), so the status code alone
cannot classify the outcome of the call.

drachtio relays the header intact and it is present on the response object, but
the feature server only read the status line from it, so the cause was lost at
the application boundary and never reached the call status webhook.

Rather than extract the header at each emit site, carry the SIP message that
caused the status change on the callStatusChange event and derive from it in one
place, so provisional responses, the 200, final failures, BYE and CANCEL are all
covered by the same code and future headers cost one line.

Note this partly overlaps the existing _extractCustomHeaders/sip_headers
passthrough: that already exposes a Reason header arriving on a BYE, but it only
runs on the hangup path, so nothing covered the outbound INVITE failure
responses where a Q.850 cause matters most.  sip_reason_header is a dedicated,
documented field that behaves the same on every status event.

sip_reason keeps meaning the status line phrase, and no key is added when there
is no Reason header, so existing consumers see an unchanged payload.

Adds a test:unit script so the smoke test runs without the docker testbed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: note that the Reason header arrives re-serialized, not verbatim

Verified live end-to-end against a cluster, capturing both the external leg and
the leg into the feature server:

    14.226.234.142 -> 10.0.197.31:5060   Reason: Q.850 ;cause=31   (as sent)
    10.0.197.31:5060 -> :5070            Reason: Q.850;cause=31    (to fs)

Proxying re-serializes the header, normalizing the optional whitespace RFC 3326
permits around ';'. The same normalization appears in customer captures from an
unrelated deployment, so this is drachtio behaviour, not cluster-specific.

sip_reason_header is therefore the header as this process received it, not the
carrier's exact bytes - worth stating outright, since the spacing inconsistency
is exactly what consumers ask about, and a consumer who string-matches on the
spaced form would silently never match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: clear a stale Reason header in redis, pass it on the alloc path, run the tests in CI

Three problems found reviewing the earlier commits.

1. The redis call record kept a stale header forever. updateCallStatus assigns
   unconditionally, but toJSON only emits truthy values, and the same object is
   written to the redis hash with hmset - a MERGE. An absent key therefore left
   the previous status change's header in place, readable via GET /Calls/:sid:
   a leg that got 183 + "Reason: Q.850;cause=31", then answered on a clean 200
   and completed on a plain BYE, reported a Q.850 temporary-failure cause for a
   call that ended normally. The webhooks were right; only the call record was
   wrong. Storing absence as '' overwrites it. The existing "does not linger"
   test passed throughout because it only exercised toJSON in memory, so this
   adds one that pins what survives the redis filter (verified: it fails
   without the fix).

2. The endpoint-allocation failure path already extracted the Reason header and
   relayed it to the SBC, but called _notifyCallStatusChange without it - so a
   FreeSWITCH 488 with "Reason: Q.850;cause=88 INCOMPATIBLE_DESTINATION" told
   the SBC the cause and the application nothing, which is exactly the case
   this feature exists to expose. That error is an fsmrf object rather than a
   SipMessage, so it cannot go through msg (the guard in
   reasonHeaderFromSipMessage would silently return undefined); the event now
   takes an explicit sipReasonHeader for callers holding the value already.

3. The test:unit script added with these tests was never wired into CI - the
   workflow runs jslint and npm test, and npm test enumerates its files
   explicitly and skips test/unit entirely, so the suite would have rotted
   unnoticed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: clear the stale header on the CallSession path too, and keep the CANCEL

Both from PR review.

1. The previous fix only worked for SingleDialer. Storing '' on the instance is
   enough for callers that write the instance itself, but CallSession writes
   toJSON(), and toJSON() drops falsy values on purpose so the key stays out of
   the webhook payload - so '' never reached hmset and the stale header
   survived. Reproduced:

       after 183, toJSON has: Q.850;cause=31
       raw instance value:    ""
       instance   -> redis has key: true    (SingleDialer, clears)
       toJSON()   -> redis has key: false   (CallSession, stale persists)

   The split is by session class, not call direction: place-outdial is required
   only by dial.js, so SingleDialer covers dial-verb child legs while inbound,
   REST-created and adulting all inherit CallSession._notifyCallStatusChange -
   including the REST outdial path this feature was written for.

   Adds CallInfo.toRedisJSON(), a named projection for the merge-semantics
   store, so the divergence from toJSON() lives in one documented place rather
   than being rediscovered at each call site. The unit test asserted against the
   raw instance, which is why it passed while the real path was broken; it now
   goes through both writers and fails if either regresses.

2. The caller-abandoned race dropped the CANCEL. middleware.js had it in hand
   and discarded it, so the constructor's _onCancel() passed nothing whenever
   the CANCEL beat the application fetch - the header landed or not depending on
   timing. Worth closing because the 487 and its 'Request Terminated' phrase are
   both ours, so every abandoned inbound call looks identical: a
   Reason: SIP;cause=200;text="Call completed elsewhere" is what separates a
   forked branch losing the race from a caller who gave up, and today both are
   just no-answer.

   Confirmed on a deployed srf (5.0.27) that this is not a no-op:
   copyUASHeaderToUACForOnlyCancel forwards a hardcoded ['Reason', 'X-Reason']
   when proxying a CANCEL, so the header does reach us.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: keep the Reason header out of the redis call record

Reversing the earlier approach after a closer look at who reads what.

The header was reaching the redis call record simply because CallInfo feeds two
sinks with opposite semantics: the status webhook is an EVENT (each POST is
independent, an absent key means "not in this event") while the redis record is
STATE written with hmset, a MERGE (an absent key means "keep what was there").
sipReasonHeader is the first field here that can legitimately go from set back
to unset - sipStatus and sipReason are only ever overwritten - so it was the
first to expose the mismatch, and two rounds of fixes had to chase it because
the two writers project from different bases.

Rather than keep managing that, exclude it: redis holds calls that are still
live, where there is usually no interesting cause yet, and by the time there is
one the call is over. Call history is served from RecentCalls (the CDR), which
is what the webapp reads - not GET /Calls. So the field earned very little there
while costing an invariant that has to be remembered forever.

Worth being explicit that this is NOT simply a revert: dropping the '' would
only have stopped the empty value being written. When the header is PRESENT it
still reached redis through both writers - toJSON includes it, and SingleDialer
writes the instance's own properties - and was then never cleared. Keeping it out
takes the same machinery as clearing it, just inverted, so this is a choice about
where the field belongs rather than a saving.

WEBHOOK_ONLY_FIELDS names that intent in one place, with the reasoning, so the
next field with the same property has somewhere obvious to go. The test asserts
both writers, since they project from different bases and checking one would
pass while the other still wrote the field (verified: it fails if the exclusion
is removed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: apply the redis projection at the boundary, and drop dead adulting plumbing

From PR review.

1. There was a THIRD call-record writer. Asking each call site to remember the
   projection was the wrong shape, and review found the proof: this class writes
   the record from the status change, the recording flag AND (private only)
   _persistConferenceState, and the last one still passed toJSON() straight
   through. A conferenced caller whose BYE carried Reason: Q.850;cause=16 would
   have that written into the call hash on the next conference-state persist,
   where hmset merges and nothing can ever clear it.

   Fixed by wrapping updateCallStatus once where it is bound, so every write is
   projected and a newly added writer cannot bypass it by forgetting to ask. The
   call sites go back to passing plain shapes. This is a class of miss that a
   unit test cannot catch - the contract test passed the whole time - so the fix
   is structural rather than another assertion.

2. The msg/byeReq parameters added to AdultingCallSession were dead code. The
   only path that reacts to the far-end BYE there is the inline
   sd.dlg.on('destroy') handler, which calls _callReleased() and discards the
   request; _hangup is reachable only from CallSession.hangup() (LCC), which
   passes nothing. The Reason header on that leg does reach the webhook, via
   SingleDialer's own destroy handler - so the plumbing was not just unused but
   misleading about which path carries it. Removed, with a comment at the handler
   recording where the event actually comes from so it does not get re-added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: correct the projection comment in SingleDialer

Copied verbatim from CallSession, where the list of writers (status change,
recording flag, conference state) is accurate. SingleDialer has one writer, so
the comment described code that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 07:35:38 -04:00
Sam MachinandDave Horton 315eb98d86 add sp_sid to alerts (#1533)
* add sp_sid to alerts

* bump time-series

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2026-03-29 16:07:08 -04:00
Ed Robbins 1560efaf03 remove sensitive data from log statement (#1522) 2026-03-05 10:04:20 -05:00
Ed Robbins 54dc172ebd Allow defining an ENV for specific webhook error return SIP code (#1476) 2025-12-16 17:14:42 -05:00
Sam Machin 4218653852 add customerData on transferred calls (#1391)
* add customerData on transferred calls

* change to if statement
2025-10-20 09:20:12 -04:00
Hoan Luu Huu 89c985b564 fixed does not send final status call back if call canceled quickly (#1393)
* fixed callsession should cleanup resource if call was canceled while fetching app

* wip

* wip

* wip

* wip

* wip
2025-10-11 03:44:42 -04:00
Dave Horton 25f58d2e43 fixes #1230 (#1231) 2025-06-04 13:42:49 -04:00
Dave Horton 8e9ab83ca4 Fix/env vars rest dial (#1225)
* rest dial needs to support env vars

* wip
2025-05-30 10:14:44 -04:00
Hoan Luu Huu 690a7fcd55 add ws msgid to telemetry span if it's ws requestor (#1215)
* add ws msgid to telemetry span if it's ws requestor

* wip
2025-05-28 07:51:59 -04:00
Dave Horton 76df58bfc2 fix logging in start task msg (#1202)
* fix logging in start task msg

* generate uuids using native crypto lib
2025-05-16 16:54:25 -04:00
Sam Machin 3098e04ed6 send env_vars in callHook (#1175)
* send env_vars in callHook

* lint

* add try/catch
2025-04-28 09:51:37 -04:00
rammohan-y 77f3d9d7ec feat/1034: sending socket close code when there is no response from the websocket app (#1035) 2025-01-16 10:13:00 -05:00
Hoan Luu Huu e800cca961 support google voice cloning (#956)
* support google voice cloning

* wip
2024-11-04 07:10:52 -05:00
Dave Horton 8e4995ec02 fix bug where middleware produces a cached app.tasks with an empty array (#947) 2024-10-24 20:43:27 -04:00
Hoan Luu Huu 2af67d8f05 support changing log level runtime (#926)
* support changing log level runtime

* support changing log level runtime

* support changing log level runtime
2024-10-07 09:51:51 -04:00
Hoan Luu Huu 62d71d2504 fix conference in cluster have correct direction in callInfo (#842)
* fix conference in cluster have correct direction

* update github action
2024-08-13 20:02:50 -04:00
Hoan Luu Huu 65ef4e6d64 fix conference in feature server cluster join, leave, end events are … (#803)
* fix conference in feature server cluster join, leave, end events are missing original data

* wip
2024-07-12 08:36:43 -06:00
Hoan Luu Huu c53ad89154 support direct call to conference (#746)
* support direct call to conference

* wip

* wip

* wip
2024-05-28 10:30:52 -04:00
Vinod Dharashive a7fa2f95dd Change regex to have fqdn and IP (#625) 2024-01-25 09:13:30 -05:00
Paulo Tellesandp.souza 859e816a8e issue #605 (#606)
Co-authored-by: p.souza <p.souza@cognigy.com>
2024-01-10 09:23:09 -05:00
Hoan Luu Huu 230fe9ea11 feat client direct call from registration webhook (#562)
* feat client direct call from registration webhook

* feat client direct call from registration webhook

* wip
2023-12-11 08:36:19 -05:00
Dave Horton bb81f9f3da minor logging 2023-12-07 13:00:25 -05:00
Hoan Luu Huu 1cdd0cf611 feat Audult call session should have its own requestor (#535)
* feat Audult call session should have its own requestor

* fix eslint

* fix eslint

* if user authenticate from http server instead of databse

* wip

* fix custom STT

* fix custom STT

* fix custom STT
2023-11-16 08:22:35 -05:00
Hoan Luu Huu 31559cbb3b user restriction (#520) 2023-11-08 12:39:56 -05:00
Dave Horton 4d0f0fe75f prevent exception referencing user.uri (#517) 2023-11-06 13:42:37 -05:00
Hoan Luu Huu 4b3234f4e4 feat disable direct p2p call by env variable (#514)
* feat disable direct p2p call by env variable

* wip

* wip
2023-11-01 09:59:01 -04:00
Hoan Luu Huu e73bef4af0 google custom voice (#506)
* google custom voice

* fixed

* wip

* wip

* wip

* wip
2023-10-30 20:10:30 -04:00
Hoan Luu Huu d6b74c3da8 Fix/dynamic apps (#499)
* refactor utilization jambonz app for queue and user

* refactor utilization jambonz app for queue and user

* refactor utilization jambonz app for queue and user

* refactor utilization jambonz app for queue and user

* refactor utilization jambonz app for queue and user
2023-10-24 20:16:01 -04:00
Hoan Luu Huu 3171b138f9 Feat/support device call (#495)
* support dequeue from registered user

* support dequeue from registered user

* wip

* wip

* wip

* wip

* fix review comments
2023-10-24 08:54:59 -04:00
Hoan Luu Huu cf0f4d405f fix device call prioritize call to registered client first (#492) 2023-10-23 07:51:16 -04:00
Hoan Luu Huu 24fccbdae5 Feat/devices call other (#491)
* calls between clients on same domain

* wip

* wip

* wip

* wip
2023-10-20 13:45:13 +02:00
Dave Horton 6587b1f758 include fallback speech selections, if any, in the initial webhook (#464) 2023-09-13 10:19:37 -04:00
Hoan Luu HuuandDave Horton aad24744f3 feat: record all calls (#352)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix jslint

* fix

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix: add file ext

* fix: record format

* fix outbound

* update to drachtio-fsmrf with support for multiple recording streams on a call

* enable DTMF during background listen recording

* fix merge commit

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2023-06-09 14:54:53 -04:00
Hoan Luu Huu 0356b996ba fix: wss requestor incase mysql cache is used (#319) 2023-04-18 06:34:39 -04:00
Markus FrindtandMarkus Frindt 86df53f8c4 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>
2023-04-11 12:46:52 -04:00
Vinod Dharashive 9587465e85 Support for Cisco NBR for Agentassist (#303)
* NBR Support

* NBR Support

* NBR Support

* re-invite Ok sdp should be sendonly

* NBR Support

* sendrecv sdp correction

* Update siprec-utils.js

* Updated comments

* Siprec participants details added to hook

* Bugfix siprec

* Update call-info.js
2023-04-07 07:33:24 -04:00
Dave Horton 690deed89d prune unused logging 2023-03-19 12:04:02 -04:00
c09425fa89 feat: use verb-specifications (#262)
* feat: use verb-specifications

* feat: use verb-specifications

* fix: verb specification v2

* remove irrelevant tests

* fix: verb-scpecification

* update to use @jambonz/verb-specifications

---------

Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
Co-authored-by: Dave Horton <daveh@beachdognet.com>
2023-02-15 09:56:23 -05:00
9d07a1354c feat: support app_json in application (#236)
* feat: support app_json in application

* feat: support app_json in application

* update db schema to latest

---------

Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
Co-authored-by: Dave Horton <daveh@beachdognet.com>
2023-01-29 14:35:29 -05:00
Dave Horton 2f8efb80d0 bugfix #220 (config w/ bargein enable followed later in flow w bargein disable) 2023-01-19 09:31:23 -05:00
Dave Horton 29f578ff5c faster uuid 2022-12-28 10:40:26 -06:00
Dave Horton 9f95fde67e faster uuid generator 2022-12-21 08:27:00 -05:00
Dave Horton 010b4d2778 bugfix: db caching had side affects of using closed http requestors 2022-12-13 14:55:23 -05:00
Dave Horton 1a1f2770b6 include service_provider_sid in call webhook 2022-11-30 13:00:35 -05:00
xquanluu 5b6f7dd3ee feat: add alert for jambonz parsing falure (#148)
* feat: add alert for jambonz parsing falure

* fix: review comment

* fix: update time-series version
2022-08-16 12:39:07 +02:00
Dave Horton 7199db5edb minor performance improvements 2022-08-14 18:28:47 +02:00
Dave Horton 3298918322 Feature/siprec server (#143)
* fixes from testing

* modify Task#exec to take resources as an object rather than argument list

* pass 2 endpoints to Transcribe when invoked in a SipRec call session

* logging

* change siprec invite to sendrecv just so freeswitch does not try to reinvite (TODO: block outgoing media at rtpengine)

* Config: when enabling recording, block until siprec dialog is established

* missed play verb in commit 031c79d

* linting

* bugfix: get final transcript in siprec call
2022-08-09 15:23:55 +02:00
Dave Horton 91204955c9 Feature/siprec server (#140)
* initial support for siprec/agent assist

* call siprec middleware

* logger fix

* remove verbs that are not valid in a siprec call session
2022-08-05 10:29:13 +01:00
Dave Horton 41d6c74c8e send application defaults for speech in initial webhook 2022-04-09 11:38:31 -04:00
Dave Hortonandjavibookline a950f9f738 Feature/trace propagation (#96)
* add b3 header for trace propagation on initial webhook

* logging

* add tracing context to all webhooks

* Add span parameter to Task.getTracingPropagation. Pass proper span to getTracingPropagation calls in Task methods to propagate the proper spanId (#91)

* some tracing cleanup

* bugfix: azure stt results need to be ordered by confidence level before processing (#92)

* fix assertion

* bugfix: vad was not enabled on config verb, restart STT on empty transcript in gather

* gather: dont send webhook if call is gone

* rest outdial: handle 302 redirect so we can later cancel request if needed (#95)

* gather: restart if we get an empty transcript (looking at you, Azure)

Co-authored-by: javibookline <98887695+javibookline@users.noreply.github.com>
2022-04-01 14:48:27 -04:00