Skip to content

JS SDK: handle iceConnectionState=disconnected/failed + attempt client-side ICE restart #197

Description

@linear

Sub-task of ENG-2180.

What to change

javascript-sdk/src/modules/StreamingClient.ts, the onIceConnectionStateChange() and onConnectionStateChange() handlers (currently lines ~590-610).

Today the SDK only reacts to:

  • iceConnectionState === 'connected' | 'completed' → emit CONNECTION_ESTABLISHED
  • connectionState === 'closed' → emit CONNECTION_CLOSED { WEBRTC_FAILURE }

It has no handler for disconnected or failed. So when a real user's network blips:

  1. T+5s: Chrome flips iceConnectionState=disconnected — SDK does nothing, customer's app doesn't know.
  2. T+15s: Chrome flips connectionState=failed — SDK does nothing, customer's app still doesn't know.
  3. T+20s: engine's 15s grace fires, engine closes the PC, Chrome flips connectionState=closed, SDK finally fires CONNECTION_CLOSED.

For ~15-20s the customer's user sees a frozen avatar with no feedback.

Proposed handlers

private onIceConnectionStateChange() {
  switch (this.peerConnection?.iceConnectionState) {
    case 'connected':
    case 'completed':
      this.publicEventEmitter.emit(AnamEvent.CONNECTION_ESTABLISHED);
      this.startStatsCollection();
      break;
    case 'disconnected':
      // path went bad; customer can show "Reconnecting…" UI
      this.publicEventEmitter.emit(AnamEvent.CONNECTION_UNSTABLE);
      // attempt ICE restart proactively (don't wait for the server)
      this.attemptIceRestart();
      break;
    case 'failed':
      // path is dead from Chrome's perspective; consent checks stopped
      this.publicEventEmitter.emit(AnamEvent.CONNECTION_LOST);
      break;
  }
}

And a corresponding recovered event when the state returns to connected after a disconnected.

ICE restart on the client side

pc.restartIce() (or setLocalDescription({iceRestart: true}) on the offer) triggers a fresh ICE gathering on the client. Combined with the engine-side restart from ENG-2180's engine subticket, this gives us belt-and-suspenders recovery — whoever notices Disconnected first can drive it.

Note: this also needs the engine to handle a client-initiated ICE restart offer cleanly via the existing websocket offer channel.

New public events

Add to AnamEvent:

  • CONNECTION_UNSTABLE — path is degraded, attempting recovery
  • CONNECTION_LOST — path failed; customer should consider this terminal unless CONNECTION_ESTABLISHED fires again
  • (existing CONNECTION_CLOSED stays for final teardown)

Document these in the SDK reference + cookbook — SN and other customers can replace their black-screen experience with a "Reconnecting…" spinner.

Backwards compatibility

New events are additive. Existing CONNECTION_CLOSED behaviour unchanged. SDK consumers that don't subscribe to the new events keep working as today.

Test plan

  • Unit tests for each new state transition emitting the right event.
  • Manual repro using client-issue-test-bench: kill the user's network for 3s, verify CONNECTION_UNSTABLE fires then CONNECTION_ESTABLISHED again on recovery (or CONNECTION_LOST if not).
  • Verify pc.restartIce() correctly produces a renegotiation offer that the engine handles.
  • Test on a TURN-only session and an srflx-only session.
  • Ship behind the customer-facing changelog so SN, DataQueue, etc. know they can wire up CONNECTION_UNSTABLE to their UI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions