From 13a967ae8fdb74612af0255e51dff3ea5d63ff31 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 17 Oct 2024 13:41:18 -0400 Subject: [PATCH] Prepare delayed call leave events more reliably (#4447) * Prepare delayed call leave events more reliably - Try sending call join after preparing delayed leave - On leave, send delayed leave instead of a new event * Don't rely on errcodes for retry logic because they are unavailable in widget mode * Make arrow method readonly SonarCloud rule typescript:S2933 * Test coverage for restarting delayed call leave * Remove unneeded unstable_features mock It's unneeded because all affected methods are mocked --- spec/unit/matrixrtc/MatrixRTCSession.spec.ts | 12 ++- src/matrixrtc/MatrixRTCSession.ts | 93 ++++++++++++++++---- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts index d19038ff250..fab61029bb0 100644 --- a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts +++ b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts @@ -46,9 +46,6 @@ describe("MatrixRTCSession", () => { client = new MatrixClient({ baseUrl: "base_url" }); client.getUserId = jest.fn().mockReturnValue("@alice:example.org"); client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA"); - client.doesServerSupportUnstableFeature = jest.fn((feature) => - Promise.resolve(feature === "org.matrix.msc4140"), - ); }); afterEach(() => { @@ -414,6 +411,8 @@ describe("MatrixRTCSession", () => { client._unstable_sendDelayedStateEvent = sendDelayedStateMock; client.sendEvent = sendEventMock; + client._unstable_updateDelayedEvent = jest.fn(); + mockRoom = makeMockRoom([]); sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom); }); @@ -490,6 +489,13 @@ describe("MatrixRTCSession", () => { ); await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]); expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1); + + // should have tried updating the delayed leave to test that it wasn't replaced by own state + expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1); + // should update delayed disconnect + jest.advanceTimersByTime(5000); + expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2); + jest.useRealTimers(); } diff --git a/src/matrixrtc/MatrixRTCSession.ts b/src/matrixrtc/MatrixRTCSession.ts index a76df9afd44..bb8c0334035 100644 --- a/src/matrixrtc/MatrixRTCSession.ts +++ b/src/matrixrtc/MatrixRTCSession.ts @@ -140,6 +140,8 @@ export class MatrixRTCSession extends TypedEventEmitter>(); private lastEncryptionKeyUpdateRequest?: number; + private disconnectDelayId: string | undefined; + // We use this to store the last membership fingerprints we saw, so we can proactively re-send encryption keys // if it looks like a membership has been updated. private lastMembershipFingerprints: Set | undefined; @@ -1011,19 +1013,24 @@ export class MatrixRTCSession extends TypedEventEmitter => { try { // TODO: If delayed event times out, re-join! const res = await this.client._unstable_sendDelayedStateEvent( @@ -1035,12 +1042,63 @@ export class MatrixRTCSession extends TypedEventEmitter this.delayDisconnection(delayId), 5000); + private scheduleDelayDisconnection(): void { + this.memberEventTimeout = setTimeout(this.delayDisconnection, 5000); } - private async delayDisconnection(delayId: string): Promise { + private readonly delayDisconnection = async (): Promise => { try { - await this.client._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart); - this.scheduleDelayDisconnection(delayId); + await this.client._unstable_updateDelayedEvent(this.disconnectDelayId!, UpdateDelayedEventAction.Restart); + this.scheduleDelayDisconnection(); } catch (e) { - logger.error("Failed to delay our disconnection event", e); + // TODO: Retry if rate-limited + logger.error("Failed to delay our disconnection event:", e); } - } + }; private stateEventsContainOngoingLegacySession(callMemberEvents: Map | undefined): boolean { if (!callMemberEvents?.size) {