From e83bbdee186898b081d954be70a96e54249adaf1 Mon Sep 17 00:00:00 2001 From: subham Date: Thu, 3 Sep 2026 14:29:56 -0700 Subject: [PATCH 1/2] feat: add disableIceLite connect option --- .changeset/disable-ice-lite-connect-option.md | 5 ++ src/api/SignalClient.test.ts | 68 +++++++++++++++++++ src/api/SignalClient.ts | 6 ++ src/options.ts | 6 ++ src/room/Room.ts | 1 + 5 files changed, 86 insertions(+) create mode 100644 .changeset/disable-ice-lite-connect-option.md diff --git a/.changeset/disable-ice-lite-connect-option.md b/.changeset/disable-ice-lite-connect-option.md new file mode 100644 index 0000000000..7a85f95915 --- /dev/null +++ b/.changeset/disable-ice-lite-connect-option.md @@ -0,0 +1,5 @@ +--- +'livekit-client': patch +--- + +Add `disableIceLite` connect option to request full ICE from a server running ICE lite. diff --git a/src/api/SignalClient.test.ts b/src/api/SignalClient.test.ts index 8a4c71ee09..662898f18c 100644 --- a/src/api/SignalClient.test.ts +++ b/src/api/SignalClient.test.ts @@ -206,6 +206,74 @@ describe('SignalClient.connect', () => { ClientInfo_Capability.CAP_PACKET_TRAILER, ]); }); + + it('does not request full ICE by default', async () => { + const joinResponse = createJoinResponse(); + const signalResponse = createSignalResponse('join', joinResponse); + const mockReadable = createMockReadableStream([signalResponse]); + const mockConnection = createMockConnection(mockReadable); + let capturedUrl = ''; + + mockWebSocketStream({ + connection: mockConnection, + onUrl: (url) => { + capturedUrl = url; + }, + }); + + await signalClient.join('wss://test.livekit.io', 'test-token', defaultOptions); + + const joinRequest = await decodeJoinRequestFromUrl(capturedUrl); + expect(joinRequest.connectionSettings?.disableIceLite).toBe(false); + }); + + it('requests full ICE in the join request when disableIceLite is set', async () => { + const joinResponse = createJoinResponse(); + const signalResponse = createSignalResponse('join', joinResponse); + const mockReadable = createMockReadableStream([signalResponse]); + const mockConnection = createMockConnection(mockReadable); + let capturedUrl = ''; + + mockWebSocketStream({ + connection: mockConnection, + onUrl: (url) => { + capturedUrl = url; + }, + }); + + await signalClient.join('wss://test.livekit.io', 'test-token', { + ...defaultOptions, + disableIceLite: true, + }); + + const joinRequest = await decodeJoinRequestFromUrl(capturedUrl); + expect(joinRequest.connectionSettings?.disableIceLite).toBe(true); + }); + + it('requests full ICE on the v0 path when disableIceLite is set', async () => { + const joinResponse = createJoinResponse(); + const signalResponse = createSignalResponse('join', joinResponse); + const mockReadable = createMockReadableStream([signalResponse]); + const mockConnection = createMockConnection(mockReadable); + let capturedUrl = ''; + + mockWebSocketStream({ + connection: mockConnection, + onUrl: (url) => { + capturedUrl = url; + }, + }); + + await signalClient.join( + 'wss://test.livekit.io', + 'test-token', + { ...defaultOptions, disableIceLite: true }, + undefined, + true, + ); + + expect(new URL(capturedUrl).searchParams.get('disable_ice_lite')).toBe('1'); + }); }); describe('Happy Path - Reconnect', () => { diff --git a/src/api/SignalClient.ts b/src/api/SignalClient.ts index 72f58d684c..b143e07545 100644 --- a/src/api/SignalClient.ts +++ b/src/api/SignalClient.ts @@ -92,6 +92,7 @@ interface ConnectOpts extends SignalOptions { export interface SignalOptions { autoSubscribe: boolean; adaptiveStream?: boolean; + disableIceLite?: boolean; clientInfoCapabilities?: ClientInfo_Capability[]; maxRetries: number; e2eeEnabled: boolean; @@ -1394,6 +1395,10 @@ function createConnectionParams( params.set('adaptive_stream', '1'); } + if (opts.disableIceLite) { + params.set('disable_ice_lite', '1'); + } + if (opts.reconnectReason) { params.set('reconnect_reason', opts.reconnectReason.toString()); } @@ -1421,6 +1426,7 @@ async function createJoinRequestConnectionParams( connectionSettings: new ConnectionSettings({ autoSubscribe: !!opts.autoSubscribe, adaptiveStream: !!opts.adaptiveStream, + disableIceLite: !!opts.disableIceLite, }), reconnect: !!opts.reconnect, participantSid: opts.sid ? opts.sid : undefined, diff --git a/src/options.ts b/src/options.ts index 582a224156..08553d783e 100644 --- a/src/options.ts +++ b/src/options.ts @@ -166,6 +166,12 @@ export interface InternalRoomConnectOptions { */ rtcConfig?: RTCConfiguration; + /** + * when the server runs ICE lite, asks it to use a full ICE agent for this participant. + * this is a server-side setting that the server may ignore, defaults to false + */ + disableIceLite?: boolean; + /** specifies how often an initial join connection is allowed to retry (only applicable if server is not reachable) */ maxRetries: number; diff --git a/src/room/Room.ts b/src/room/Room.ts index 75488f4726..7b72c7c39a 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -992,6 +992,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) autoSubscribe: connectOptions.autoSubscribe, adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream, + disableIceLite: connectOptions.disableIceLite, clientInfoCapabilities: this.getClientInfoCapabilities(roomOptions), maxRetries: connectOptions.maxRetries, e2eeEnabled: !!this.e2eeManager, From 25df5ad289f387ab8227cde93e71b2650292090b Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 8 Sep 2026 15:40:16 -0400 Subject: [PATCH 2/2] refactor: default disableIceLite in defaults.ts Follow the SDK convention for connect options: make the field non-optional on InternalRoomConnectOptions and declare its default in roomConnectOptionDefaults instead of relying on undefined. Co-Authored-By: Claude Opus 5 (1M context) --- src/options.ts | 2 +- src/room/defaults.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/options.ts b/src/options.ts index 08553d783e..d2472c58dd 100644 --- a/src/options.ts +++ b/src/options.ts @@ -170,7 +170,7 @@ export interface InternalRoomConnectOptions { * when the server runs ICE lite, asks it to use a full ICE agent for this participant. * this is a server-side setting that the server may ignore, defaults to false */ - disableIceLite?: boolean; + disableIceLite: boolean; /** specifies how often an initial join connection is allowed to retry (only applicable if server is not reachable) */ maxRetries: number; diff --git a/src/room/defaults.ts b/src/room/defaults.ts index 68e95ffb42..d0e2daff9a 100644 --- a/src/room/defaults.ts +++ b/src/room/defaults.ts @@ -47,6 +47,7 @@ export const roomOptionDefaults: InternalRoomOptions = { export const roomConnectOptionDefaults: InternalRoomConnectOptions = { autoSubscribe: true, + disableIceLite: false, maxRetries: 1, peerConnectionTimeout: 15_000, websocketTimeout: 15_000,