Skip to content

VAPI-3808 VAPI-3804 fix(signaling): classify 409 handshake rejection distinctly from 403 - #19

Merged
smoghe-bw merged 1 commit into
mainfrom
vapi-3804-classify-409-endpoint-occupied
Aug 20, 2026
Merged

VAPI-3808 VAPI-3804 fix(signaling): classify 409 handshake rejection distinctly from 403#19
smoghe-bw merged 1 commit into
mainfrom
vapi-3804-classify-409-endpoint-occupied

Conversation

@smoghe-bw

Copy link
Copy Markdown
Contributor

Problem

javascript-brtc-sdk#17 fixed a reconnect storm in the JS SDK: its Signaling client is built with unlimited auto-reconnect, and only treated HTTP 403 as a fatal (non-retryable) handshake failure. A 409 — the gateway rejecting a connection because another device already holds that endpoint — was treated as retryable, causing rapid repeated reconnect attempts against a gateway correctly refusing the connection.

The Swift SignalingClient has no built-in auto-reconnect at all (it's a raw URLSessionWebSocketTask, and connect() already throws .alreadyConnected if called while connected), so that specific storm can't happen here. But the SDK also gave callers no way to distinguish why the signaling WebSocket closed — a bad token (403), an endpoint already in use by another device (409), and any other drop all looked identical from the app's perspective.

Change

  • WebSocketProtocol now exposes the underlying response: URLResponse? (available via URLSessionWebSocketTask after a rejected handshake).
  • SignalingClient.handleReceiveError reads the HTTP status of a rejected handshake and threads it through the "close" event as a small WebSocketCloseInfo payload, logging distinctly for the known fatal cases (403/409).
  • Added BandwidthRTCError.endpointOccupied for 409.
  • BandwidthRTCClient decodes the close reason and exposes a new onDisconnected: (BandwidthRTCError) -> Void callback, mapping 403 → .invalidToken, 409 → .endpointOccupied, otherwise .webSocketDisconnected — so an app that retries connect() on disconnect can tell these apart and avoid hammering the gateway on a non-retryable failure.

Bumped VERSION to 1.0.7 per repo convention.

Test plan

  • New tests: testCloseEventCarriesStatusCodeOnRejectedHandshake (SignalingClientTests), testCloseEventWith409ReportsEndpointOccupied, testCloseEventWith403ReportsInvalidToken, testCloseEventWithNoStatusCodeReportsWebSocketDisconnected (ResourceLifecycleTests)
  • Full suite green: xcodebuild test -scheme BandwidthRTC -destination 'platform=iOS Simulator,name=iPhone 17' — 218/218 passed

🤖 Generated with Claude Code

… from 403

The JS SDK (Bandwidth/javascript-brtc-sdk#17) fixed a reconnect storm caused
by unlimited auto-reconnect treating a 409 (another device holds the
endpoint) as retryable. The Swift SignalingClient has no auto-reconnect at
all, so that storm can't happen here, but it also gave the app no way to
tell a 403 (bad token) or 409 (endpoint occupied) apart from any other
WebSocket drop — both non-retryable conditions worth surfacing distinctly.

Capture the rejected handshake's HTTP status via WebSocketProtocol.response,
add BandwidthRTCError.endpointOccupied, and thread the classification through
the "close" event to a new BandwidthRTCClient.onDisconnected callback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@smoghe-bw
smoghe-bw requested review from a team as code owners August 20, 2026 18:04
@bwappsec

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@smoghe-bw smoghe-bw changed the title VAPI-3804 fix(signaling): classify 409 handshake rejection distinctly from 403 VAPI-3808 VAPI-3804 fix(signaling): classify 409 handshake rejection distinctly from 403 Aug 20, 2026
@smoghe-bw
smoghe-bw merged commit 9a1578d into main Aug 20, 2026
3 checks passed
@smoghe-bw
smoghe-bw deleted the vapi-3804-classify-409-endpoint-occupied branch August 20, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants