From c35ada949eac3aeaeeaeb3cf75fe61a45d9541ea Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 20 Apr 2026 11:16:27 +0200 Subject: [PATCH 1/4] fix: ensure udpated tokens get set on the regionUrlProvider --- src/room/RTCEngine.ts | 36 +++++++++++++++++------------------- src/room/Room.ts | 26 +++++++++++++++++++++++--- src/room/events.ts | 2 ++ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/room/RTCEngine.ts b/src/room/RTCEngine.ts index 4bf7cb1cd4..1762029cc9 100644 --- a/src/room/RTCEngine.ts +++ b/src/room/RTCEngine.ts @@ -21,6 +21,7 @@ import { PublishDataTrackResponse, ReconnectReason, type ReconnectResponse, + type RegionSettings, RequestResponse, Room as RoomModel, RoomMovedResponse, @@ -62,7 +63,6 @@ import { TTLMap } from '../utils/ttlmap'; import PCTransport, { PCEvents } from './PCTransport'; import { PCTransportManager, PCTransportState } from './PCTransportManager'; import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy'; -import { DEFAULT_MAX_AGE_MS, type RegionUrlProvider } from './RegionUrlProvider'; import { DataTrackInfo } from './data-track/types'; import { roomConnectOptionDefaults } from './defaults'; import { @@ -222,7 +222,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit private shouldFailOnV1Path: boolean = false; - private regionUrlProvider?: RegionUrlProvider; + private regionStrategy?: RegionStrategy; private log = log; @@ -532,8 +532,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit } /* @internal */ - setRegionUrlProvider(provider: RegionUrlProvider) { - this.regionUrlProvider = provider; + setRegionStrategy(strategy: RegionStrategy | undefined) { + this.regionStrategy = strategy; } private async configure(joinResponse?: JoinResponse, useSinglePeerConnection?: boolean) { @@ -684,7 +684,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit this.client.onTokenRefresh = (token: string) => { this.token = token; - this.regionUrlProvider?.updateToken(token); + this.emit(EngineEvent.TokenRefreshed, token); }; this.client.onRemoteMuteChanged = (trackSid: string, muted: boolean) => { @@ -726,13 +726,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit this.client.onLeave = (leave: LeaveRequest) => { this.log.debug('client leave request', { ...this.logContext, reason: leave?.reason }); - if (leave.regions && this.regionUrlProvider) { + if (leave.regions) { this.log.debug('updating regions', this.logContext); - this.regionUrlProvider.setServerReportedRegions({ - updatedAtInMs: Date.now(), - maxAgeInMs: DEFAULT_MAX_AGE_MS, - regionSettings: leave.regions, - }); + this.emit(EngineEvent.ServerRegionsReported, leave.regions); } switch (leave.action) { case LeaveRequest_Action.DISCONNECT: @@ -1137,11 +1133,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit this.log.debug(`reconnecting in ${delay}ms`, this.logContext); this.clearReconnectTimeout(); - if (this.token && this.regionUrlProvider) { - // token may have been refreshed, we do not want to recreate the regionUrlProvider - // since the current engine may have inherited a regional url - this.regionUrlProvider.updateToken(this.token); - } this.reconnectTimeout = CriticalTimers.setTimeout( () => this.attemptReconnect(disconnectReason).finally(() => (this.reconnectTimeout = undefined)), @@ -1273,17 +1264,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit throw new SignalReconnectError('Signal connection got severed during reconnect'); } - this.regionUrlProvider?.resetAttempts(); + this.regionStrategy?.resetAttempts(); // reconnect success this.emit(EngineEvent.Restarted); } catch (error) { - const nextRegionUrl = await this.regionUrlProvider?.getNextBestRegionUrl(); + const nextRegionUrl = await this.regionStrategy?.getNextUrl(); if (nextRegionUrl) { await this.restartConnection(nextRegionUrl); return; } else { // no more regions to try (or we're not on cloud) - this.regionUrlProvider?.resetAttempts(); + this.regionStrategy?.resetAttempts(); throw error; } } @@ -2023,8 +2014,15 @@ export type EngineEventCallbacks = { dataTrackSubscriberHandles: (event: DataTrackSubscriberHandles) => void; dataTrackPacketReceived: (packet: Uint8Array) => void; joined: (joinResponse: JoinResponse) => void; + tokenRefreshed: (token: string) => void; + serverRegionsReported: (regions: RegionSettings) => void; }; +export interface RegionStrategy { + getNextUrl(abortSignal?: AbortSignal): Promise; + resetAttempts(): void; +} + function applyUserDataCompat(newObj: DataPacket, oldObj: UserPacket) { const participantIdentity = newObj.participantIdentity ? newObj.participantIdentity diff --git a/src/room/Room.ts b/src/room/Room.ts index a28f9959b3..43ec0fa8ec 100644 --- a/src/room/Room.ts +++ b/src/room/Room.ts @@ -47,8 +47,8 @@ import TypedPromise from '../utils/TypedPromise'; import { getBrowser } from '../utils/browserParser'; import { BackOffStrategy } from './BackOffStrategy'; import DeviceManager from './DeviceManager'; -import RTCEngine, { DataChannelKind } from './RTCEngine'; -import { RegionUrlProvider } from './RegionUrlProvider'; +import RTCEngine, { DataChannelKind, type RegionStrategy } from './RTCEngine'; +import { DEFAULT_MAX_AGE_MS, RegionUrlProvider } from './RegionUrlProvider'; import IncomingDataStreamManager from './data-stream/incoming/IncomingDataStreamManager'; import { type ByteStreamHandler, @@ -668,6 +668,16 @@ class Room extends (EventEmitter as new () => TypedEmitter) }), ); this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped); + }) + .on(EngineEvent.TokenRefreshed, (token) => { + this.regionUrlProvider?.updateToken(token); + }) + .on(EngineEvent.ServerRegionsReported, (regions) => { + this.regionUrlProvider?.setServerReportedRegions({ + regionSettings: regions, + updatedAtInMs: Date.now(), + maxAgeInMs: DEFAULT_MAX_AGE_MS, + }); }); if (this.localParticipant) { @@ -681,6 +691,14 @@ class Room extends (EventEmitter as new () => TypedEmitter) } } + private createRegionStrategy(): RegionStrategy { + return { + getNextUrl: async (signal?: AbortSignal) => + this.regionUrlProvider ? this.regionUrlProvider.getNextBestRegionUrl(signal) : null, + resetAttempts: () => this.regionUrlProvider?.resetAttempts(), + }; + } + /** * getLocalDevices abstracts navigator.mediaDevices.enumerateDevices. * In particular, it requests device permissions by default if needed @@ -764,10 +782,12 @@ class Room extends (EventEmitter as new () => TypedEmitter) if (this.regionUrlProvider?.getServerUrl().toString() !== ensureTrailingSlash(url)) { this.regionUrl = undefined; this.regionUrlProvider = undefined; + this.engine.setRegionStrategy(undefined); } if (isCloud(new URL(url))) { if (this.regionUrlProvider === undefined) { this.regionUrlProvider = new RegionUrlProvider(url, token); + this.engine.setRegionStrategy(this.createRegionStrategy()); } else { this.regionUrlProvider.updateToken(token); } @@ -965,7 +985,7 @@ class Room extends (EventEmitter as new () => TypedEmitter) this.maybeCreateEngine(); } if (this.regionUrlProvider?.isCloud()) { - this.engine.setRegionUrlProvider(this.regionUrlProvider); + this.engine.setRegionStrategy(this.createRegionStrategy()); } this.acquireAudioContext(); diff --git a/src/room/events.ts b/src/room/events.ts index 078faed076..9762ce9d04 100644 --- a/src/room/events.ts +++ b/src/room/events.ts @@ -631,6 +631,8 @@ export enum EngineEvent { DataTrackSubscriberHandles = 'dataTrackSubscriberHandles', DataTrackPacketReceived = 'dataTrackPacketReceived', Joined = 'joined', + TokenRefreshed = 'tokenRefreshed', + ServerRegionsReported = 'serverRegionsReported', } export enum TrackEvent { From 1fe9fdffe6b9acfca97c5db8885a6e19084e3a29 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 20 Apr 2026 11:19:11 +0200 Subject: [PATCH 2/4] Create popular-pumas-drop.md --- .changeset/popular-pumas-drop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-pumas-drop.md diff --git a/.changeset/popular-pumas-drop.md b/.changeset/popular-pumas-drop.md new file mode 100644 index 0000000000..e08b456711 --- /dev/null +++ b/.changeset/popular-pumas-drop.md @@ -0,0 +1,5 @@ +--- +"livekit-client": patch +--- + +fix: ensure udpated tokens get set on the regionUrlProvider From 961cf79d1ad49d04efcf8aeac2f1fae33d467d5a Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 21 Apr 2026 09:48:00 +0200 Subject: [PATCH 3/4] emit on disconnect --- src/room/RTCEngine.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/room/RTCEngine.ts b/src/room/RTCEngine.ts index 1762029cc9..051ec7f529 100644 --- a/src/room/RTCEngine.ts +++ b/src/room/RTCEngine.ts @@ -1133,6 +1133,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit this.log.debug(`reconnecting in ${delay}ms`, this.logContext); this.clearReconnectTimeout(); + if (this.token) { + // token may have been refreshed, we do not want to recreate the regionUrlProvider + // since the current engine may have inherited a regional url + this.emit(EngineEvent.TokenRefreshed, this.token); + } this.reconnectTimeout = CriticalTimers.setTimeout( () => this.attemptReconnect(disconnectReason).finally(() => (this.reconnectTimeout = undefined)), From 1ae31ce4ef165eeeea350d978d6a7e34d43bded4 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 21 Apr 2026 10:14:41 +0200 Subject: [PATCH 4/4] re-schedule region fetch after token update --- src/room/RTCEngine.ts | 1 - src/room/RegionUrlProvider.ts | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/room/RTCEngine.ts b/src/room/RTCEngine.ts index 051ec7f529..10775e3295 100644 --- a/src/room/RTCEngine.ts +++ b/src/room/RTCEngine.ts @@ -332,7 +332,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit this.shouldFailOnV1Path = false; throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc'); } - log.warn('joining signal with ', url); const joinResponse = await this.client.join( url, token, diff --git a/src/room/RegionUrlProvider.ts b/src/room/RegionUrlProvider.ts index 3bf72f97d3..4e424f7f59 100644 --- a/src/room/RegionUrlProvider.ts +++ b/src/room/RegionUrlProvider.ts @@ -189,6 +189,13 @@ export class RegionUrlProvider { updateToken(token: string) { this.token = token; + const url = this.getServerUrl(); + const settings = RegionUrlProvider.cache.get(url.hostname); + RegionUrlProvider.scheduleRefetch( + this.serverUrl, + this.token, + settings?.maxAgeInMs ?? DEFAULT_MAX_AGE_MS, + ); } isCloud() {