From ec20a636d3af5d14e955e3f209213044e129b0b1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 10 May 2026 16:53:47 +0000 Subject: [PATCH] fix a race where EC would disconnect from LK in pre-multi-SFU mode if it gets told to connect to the SFU it's already connected to --- .../remoteMembers/ConnectionManager.ts | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts index 727f68bcc..34add73bd 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionManager.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionManager.ts @@ -131,10 +131,38 @@ export function createConnectionManager$({ // Combine local and remote transports into one transport array // and set the forceOldJwtEndpoint property on the local transport map(([remoteTransports, localTransport]) => { - let localTransportAsArray: LocalTransportWithSFUConfig[] = []; - if (localTransport) { - localTransportAsArray = [localTransport]; + // Defer all transport processing until we know our own local + // transport. Without this, the snapshot taken between + // remoteTransports$ first emitting and localTransport$ + // emitting causes us to create a subscribe-only Connection + // for any remote member's URL — and then, when localTransport$ + // does emit, that Connection is keyed differently + // (`[URL, sfuConfig]` vs. `[URL, undefined]`) and gets + // destroyed and recreated. The destroy races with the + // freshly-started LK connection on the new Connection (and, + // worse, the two Connections fetch JWTs via different + // endpoints — the legacy `/sfu/get` for the local one and + // the Matrix-2.0 delegation endpoint for the + // subscribe-only — so they end up with *different* + // LK identities for the same Matrix user, which the SFU then + // gets confused about). Holding back until localTransport is + // resolved means the dedup against the local URL has the + // chance to filter out matching remotes before any + // subscribe-only Connection is started, so that race never + // happens. Cascade calls (where some remote URLs genuinely + // don't match the local one) just see those subscribe-only + // Connections come up the moment the local transport + // resolves, with no functional difference vs. coming up at + // remoteTransports$' first emission. + if (!localTransport) { + logger.debug( + "localTransport not yet resolved; deferring remote transport processing", + ); + return new Epoch([], remoteTransports.epoch); } + const localTransportAsArray: LocalTransportWithSFUConfig[] = [ + localTransport, + ]; const dedupedRemote = removeDuplicateTransports(remoteTransports.value); const remoteWithoutLocal = dedupedRemote.filter( (transport) =>