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,