Skip to content

feat(realtime): add IVS transport support#97

Open
nagar-decart wants to merge 9 commits intomainfrom
nagar-decart/ivs-sdk-transport
Open

feat(realtime): add IVS transport support#97
nagar-decart wants to merge 9 commits intomainfrom
nagar-decart/ivs-sdk-transport

Conversation

@nagar-decart
Copy link
Contributor

@nagar-decart nagar-decart commented Mar 4, 2026

Summary

Add IVS (Interactive Video Service) as an alternative transport for real-time inference, alongside the existing WebRTC transport. Users select transport via transport: "ivs" option on realtime.connect().

New files

  • ivs-connection.ts — Low-level IVS connection using AWS IVS Web Broadcast SDK (publish + subscribe stages)
  • ivs-manager.ts — Manager with retry/reconnect logic (parallel to webrtc-manager.ts)
  • transport-manager.ts — Shared interface implemented by both WebRTC and IVS managers

Modified files

  • client.tstransport?: "webrtc" | "ivs" option on connect, routes to appropriate manager
  • methods.ts — Accept RealtimeTransportManager interface instead of WebRTCManager directly
  • types.ts — Add IvsStageReadyMessage, IvsJoinedMessage message types
  • diagnostics.ts — Add ivs-stage-setup connection phase
  • webrtc-connection.ts / webrtc-manager.ts — Implement RealtimeTransportManager interface
  • package.json — Add @aws/ivs-web-broadcast as optional peer dependency

Usage

const client = createDecartClient({ apiKey });
const rt = await client.realtime.connect(stream, {
  model: models.realtime("mirage_v2"),
  transport: "ivs", // defaults to "webrtc"
  onRemoteStream: (stream) => { video.srcObject = stream; },
});

IVS SDK can be loaded via npm (@aws/ivs-web-broadcast) or CDN script tag (falls back to globalThis.IVSBroadcastClient).

Test plan

  • Existing WebRTC tests still pass (npm test)
  • Type checking passes (npx tsc --noEmit)
  • Manual test with IVS transport using demo page (index.html)

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new realtime transport path and refactors the connect/subscribe flow to route through transport managers, which can affect connection setup, error handling, and token compatibility. WebRTC remains default, but the new IVS dependency loading and stage setup introduces additional runtime paths to validate.

Overview
Adds IVS (AWS Interactive Video Service) as an alternative to WebRTC for realtime.connect(), controlled via a new transport: "webrtc" | "ivs" option (defaulting to WebRTC).

Introduces an IVS transport implementation (IVSConnection/IVSManager) with dynamic loading of @aws/ivs-web-broadcast, updates realtime methods to use a shared RealtimeTransportManager interface, and extends subscribe token encoding/decoding to carry the selected transport (plus an IVS subscribe path).

Updates the SDK test page and the React Vite example to allow selecting transport (including loading the IVS SDK from CDN in the test page), and marks @aws/ivs-web-broadcast as an optional peer dependency.

Written by Cursor Bugbot for commit 33ffd89. This will update automatically on new commits. Configure here.

nagar-decart and others added 6 commits March 4, 2026 10:20
Add IVS (Interactive Video Service) as an alternative transport to WebRTC.
Users select transport via `transport: "ivs"` in connect options — both paths
return the same RealTimeClient interface.

New files: transport-manager.ts (shared interface), ivs-connection.ts,
ivs-manager.ts. Updated client.ts for transport selection, methods.ts to
accept the shared interface, and WebRTCManager to implement it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add transport dropdown (WebRTC/IVS) to the test page. When IVS is
selected, the IVS Web Broadcast SDK is loaded from CDN. The chosen
transport is passed to client.realtime.connect().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bouncer's IVS handler waits for `ivs_joined` as the first client
message after sending `ivs_stage_ready`. The message pump (which handles
set_image/prompt) only starts after `ivs_joined` is received.

Sending set_image before the stage handshake caused the bouncer to read
it instead of `ivs_joined`, rejecting with "Expected ivs_joined message".

Fix: reorder IVS connection phases so stage setup completes first, then
send initial image/prompt once the bouncer's message pump is running.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The IVS SDK's StageStrategy callbacks take (participant) not (stage, participant).
This caused TypeError: Cannot read properties of undefined (reading 'isLocal').

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
IVS SDK v1.14.0 does not pass participant to shouldPublishParticipant
or shouldSubscribeToParticipant. Use argument-free callbacks instead:
- publish: always true (only called for local publish-eligible participants)
- subscribe: always AUDIO_VIDEO (subscribe to all remote streams)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDK uses pnpm — package-lock.json was generated by mistake.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
};

this.ws?.addEventListener("message", handler);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: ivs_stage_ready lost during SDK loading

High Severity

In setupIVSStages, await getIVSBroadcastClient() is called first (line 241), and only after it resolves is the addEventListener("message", handler) registered to listen for ivs_stage_ready (line 268). During SDK loading, ws.onmessage is already active and routes messages to handleMessage(), which silently drops ivs_stage_ready (it has no handler for it, as noted in the comment on line 388). If the server sends ivs_stage_ready before the SDK finishes loading, the message is consumed and lost, causing the connection to hang until the "IVS stage ready timeout" fires.

Additional Locations (1)

Fix in Cursor Fix in Web

emitOrBuffer("error", classifyWebrtcError(error));
},
customizeOffer: options.customizeOffer as ((offer: RTCSessionDescriptionInit) => Promise<void>) | undefined,
vp8MinBitrate: 300,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebRTC error classifier misclassifies IVS transport errors

Medium Severity

The onError callback passes IVS transport errors through classifyWebrtcError, which maps unrecognized errors to WEBRTC_SIGNALING_ERROR by default. IVS-specific errors (e.g., from stage setup failures) have nothing to do with WebRTC signaling, producing misleading error codes that could confuse consumers relying on the code field for error handling logic.

Fix in Cursor Fix in Web

nagar-decart and others added 2 commits March 4, 2026 19:07
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add transport-aware subscribe flow so viewers can watch IVS sessions
without consuming inference server resources (SFU handles fan-out).

- Add optional transport field to subscribe token encoding
- Add subscribeIVS path: fetches viewer token from bouncer, creates
  subscribe-only IVS stage
- Export getIVSBroadcastClient and IVSBroadcastModule for reuse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

serverPort: number,
transport?: "webrtc" | "ivs",
): string {
return btoa(JSON.stringify({ sid: sessionId, ip: serverIp, port: serverPort, transport }));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Token validation rejects valid IVS subscribe tokens

High Severity

decodeSubscribeToken validates !payload.ip || !payload.port for all tokens, but IVS subscribe tokens don't need ip or port (only sid is used by subscribeIVS). If the IVS server sends server_port: 0 or server_ip: "" in its session_id message (reasonable since these WebRTC-specific fields are irrelevant for IVS), the truthiness check fails (!0 and !"" are both true), throwing "Invalid subscribe token" and completely breaking the IVS subscribe flow.

Additional Locations (1)

Fix in Cursor Fix in Web

reject(new Error("WebSocket is not open"));
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated connection logic across IVS and WebRTC classes

Medium Severity

IVSConnection and WebRTCConnection contain near-identical implementations of setImageBase64, sendInitialPrompt, send, setState, and common handleMessage logic (error, prompt_ack, set_image_ack, generation_started, generation_tick, generation_ended, session_id handling). These are copied line-for-line with only the message type parameter differing. A shared base class or extracted utility functions would reduce the maintenance burden and risk of divergence during future bug fixes.

Additional Locations (1)

Fix in Cursor Fix in Web

The realtime client's baseUrl is a WebSocket URL (wss://), but the
IVS subscribe endpoint is an HTTP GET. Convert the protocol before
calling fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant