A customer support case (Zoho #60571) surfaced three related gaps around call.connect() outcome tracking in the RELAY client. Findings verified against the shipped package (@signalwire/sdk 2.0.5) and this repo's source.
1. Docs gap: no recipe for tracking connect() outcomes
connect() is intentionally a plain RPC (the calling.connect wire command carries no control_id, so Action tracking is not possible) — but nothing in relay/docs/ explains how to determine the outcome of a bridge (answered / busy / declined / ring-out / caller cancelled). A well-researched customer had to reverse-engineer the pattern and still missed the key piece.
The working pattern, verified against RelayClient._handleEvent:
- A-leg
calling.call.connect frames route to the tracked Call normally → call.waitFor('calling.call.connect', ...) covers connected / disconnected / failed.
- B-legs created by
connect() never become Call objects — _handleEvent only auto-registers unknown call_ids for pending dials — so their calling.call.state events are only observable through the client-level client.onEvent(...) raw observer, correlated via the tag passed to connect(). The B-leg's ended event carries end_reason (busy / noAnswer / decline / …), which is the reliable terminal signal — including for SIP ring-out.
Suggest adding a "Tracking connect outcomes" section to relay/docs/call-methods.md (or the guide) covering: tag on connect, the onEvent + params.tag / params.call_state / params.end_reason pattern, the fact that onEvent is a single global handler (route by tag in one dispatcher), and the disconnect-before-fallback race (a client-side ring timeout can fire just as the B-leg answers).
2. maxDuration unit contradiction: seconds vs minutes
src/relay/Call.ts (and the shipped Call.d.ts) document options.maxDuration as "Maximum connect duration in seconds".
relay/RELAY_IMPLEMENTATION_GUIDE.md documents the calling.connect max_duration wire param as "Max connected duration in minutes".
One of these is wrong by a factor of 60. Needs confirming against the platform and fixing whichever side is off.
3. Platform question: no terminal calling.call.connect frame on B-leg ring-out
The customer reports that when a SIP destination rings out (~20s, unanswered), the platform stops the ringing leg but the A-leg receives no terminal calling.call.connect frame (failed / disconnected). The B-leg end_reason: noAnswer state event covers the use case (see item 1), but if the A-leg is expected to receive a terminal connect frame here, that's a platform-side gap worth confirming with the RELAY team; if the current behavior is intended, the docs from item 1 should state it explicitly so nobody waits on a frame that never comes.
Context: same customer and same connect-ergonomics territory as the earlier answer_on_bridge request (cloud-product#20389).
A customer support case (Zoho #60571) surfaced three related gaps around
call.connect()outcome tracking in the RELAY client. Findings verified against the shipped package (@signalwire/sdk2.0.5) and this repo's source.1. Docs gap: no recipe for tracking
connect()outcomesconnect()is intentionally a plain RPC (thecalling.connectwire command carries nocontrol_id, so Action tracking is not possible) — but nothing inrelay/docs/explains how to determine the outcome of a bridge (answered / busy / declined / ring-out / caller cancelled). A well-researched customer had to reverse-engineer the pattern and still missed the key piece.The working pattern, verified against
RelayClient._handleEvent:calling.call.connectframes route to the trackedCallnormally →call.waitFor('calling.call.connect', ...)coversconnected/disconnected/failed.connect()never becomeCallobjects —_handleEventonly auto-registers unknown call_ids for pending dials — so theircalling.call.stateevents are only observable through the client-levelclient.onEvent(...)raw observer, correlated via thetagpassed toconnect(). The B-leg'sendedevent carriesend_reason(busy/noAnswer/decline/ …), which is the reliable terminal signal — including for SIP ring-out.Suggest adding a "Tracking connect outcomes" section to
relay/docs/call-methods.md(or the guide) covering:tagon connect, theonEvent+params.tag/params.call_state/params.end_reasonpattern, the fact thatonEventis a single global handler (route by tag in one dispatcher), and the disconnect-before-fallback race (a client-side ring timeout can fire just as the B-leg answers).2.
maxDurationunit contradiction: seconds vs minutessrc/relay/Call.ts(and the shippedCall.d.ts) documentoptions.maxDurationas "Maximum connect duration in seconds".relay/RELAY_IMPLEMENTATION_GUIDE.mddocuments thecalling.connectmax_durationwire param as "Max connected duration in minutes".One of these is wrong by a factor of 60. Needs confirming against the platform and fixing whichever side is off.
3. Platform question: no terminal
calling.call.connectframe on B-leg ring-outThe customer reports that when a SIP destination rings out (~20s, unanswered), the platform stops the ringing leg but the A-leg receives no terminal
calling.call.connectframe (failed/disconnected). The B-legend_reason: noAnswerstate event covers the use case (see item 1), but if the A-leg is expected to receive a terminal connect frame here, that's a platform-side gap worth confirming with the RELAY team; if the current behavior is intended, the docs from item 1 should state it explicitly so nobody waits on a frame that never comes.Context: same customer and same connect-ergonomics territory as the earlier
answer_on_bridgerequest (cloud-product#20389).