From 2db24e28687e4c0b65bf67e799a19ebc4fff02b1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 2 Sep 2026 18:46:12 +0100 Subject: [PATCH 01/20] Fix periodic rageshake log flush never running The throttled flush callback returned this.flush instead of calling it (regressed in #2607), so logs were only persisted to IndexedDB on rageshake submission or beforeunload. When the host removes the widget iframe at hangup, the whole call's logs were lost, so a rageshake filed from a later call carries nothing from the affected one. --- src/settings/rageshake.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/settings/rageshake.ts b/src/settings/rageshake.ts index b9ecbff50..8fe759a8c 100644 --- a/src/settings/rageshake.ts +++ b/src/settings/rageshake.ts @@ -204,10 +204,13 @@ class IndexedDBLogStore { // Throttled function to flush logs. We use throttle rather // than debounce as we want logs to be written regularly, otherwise // if there's a constant stream of logging, we'd never write anything. - private throttledFlush = throttle(() => this.flush, MAX_FLUSH_INTERVAL_MS, { - leading: false, - trailing: true, - }); + private throttledFlush = throttle( + () => { + this.flush().catch((e) => logger.error("Failed to flush logs", e)); + }, + MAX_FLUSH_INTERVAL_MS, + { leading: false, trailing: true }, + ); /** * Flush logs to disk. From 698a98a9c8869ddafc5b2d20f218d2ec58a571c3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 2 Sep 2026 18:55:18 +0100 Subject: [PATCH 02/20] Test that logs are flushed to IndexedDB periodically --- src/settings/rageshake.flush.test.ts | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/settings/rageshake.flush.test.ts diff --git a/src/settings/rageshake.flush.test.ts b/src/settings/rageshake.flush.test.ts new file mode 100644 index 000000000..2bbe120cc --- /dev/null +++ b/src/settings/rageshake.flush.test.ts @@ -0,0 +1,52 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { afterEach, expect, it, vi } from "vitest"; + +import { init as initRageshake } from "./rageshake"; + +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); +}); + +it("flushes logs to IndexedDB periodically without an explicit flush", async () => { + vi.useFakeTimers(); + const add = vi.fn(); + const txn = { + oncomplete: undefined as (() => void) | undefined, + onerror: undefined, + objectStore: (name: string) => + name === "logs" + ? { + add: (entry: unknown): void => { + add(entry); + queueMicrotask(() => txn.oncomplete?.()); + }, + } + : { put: vi.fn() }, + }; + const open = (): unknown => { + const req = { + result: { transaction: () => txn }, + onsuccess: undefined as (() => void) | undefined, + }; + queueMicrotask(() => req.onsuccess?.()); + return req; + }; + vi.stubGlobal("indexedDB", { open }); + + await initRageshake(); + global.mx_rage_logger.log(1, "test", "hello from the buffer"); + expect(add).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(2000); + expect(add).toHaveBeenCalledOnce(); + expect(add.mock.calls[0][0]).toMatchObject({ + lines: expect.stringContaining("hello from the buffer"), + }); +}); From bf5482db8c742a7c6729b167e70b2a5a5d2ca81b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 3 Sep 2026 14:55:31 +0100 Subject: [PATCH 03/20] Surface screen share failures instead of silently doing nothing toggleScreenSharing only had `.catch(logger.error)`, so a getDisplayMedia request that hangs (element-call-rageshakes#17152: Element Desktop on Windows, the user pressed the screen share button 14 times in 25 seconds and the log shows nothing but the toggle lines and livekit-client's "waiting for pending publication promise timed out") left the user with a button that does nothing and us with no evidence of why. Log when a toggle is requested and when it completes or fails, with the elapsed time, so a hang is visible in the logs. Explicit failures other than the user cancelling the picker show as a non-modal "Could not start screen sharing" toast. Nothing is inferred from a toggle taking a long time: the user may simply be choosing what to share. --- locales/en/app.json | 1 + src/room/InCallView.tsx | 17 ++++++ .../__snapshots__/InCallView.test.tsx.snap | 20 +++---- src/state/CallViewModel/CallViewModel.ts | 7 +++ .../localMember/LocalMember.test.ts | 26 ++++++++++ .../CallViewModel/localMember/LocalMember.ts | 52 +++++++++++++++++-- 6 files changed, 109 insertions(+), 14 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 543942e2e..f3d568bb8 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -116,6 +116,7 @@ "peer_connection_timeout_description": "Connection to the media server timed out. Try switching to a different network or disabling your VPN. If the problem persists, see our <0>troubleshooting guide or contact your server administrator.", "room_creation_restricted": "Failed to create call", "room_creation_restricted_description": "Call creation might be restricted to authorized users only. Try again later, or contact your server admin if the problem persists.", + "screen_share_failed": "Could not start screen sharing", "sticky_events_required": "Homeserver does not support Matrix 2.0 calls", "sticky_events_required_description": "This deployment is configured to use Matrix 2.0 call mode, but the homeserver does not advertise support for sticky events (MSC4354). Ask your server admin to upgrade, or switch the deployment to a compatible mode.", "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server admin." diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index a57dcce2b..3ea88f81f 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -270,6 +270,7 @@ export const InCallView: FC = ({ const audioParticipants = useBehavior(vm.livekitRoomItems$); const participantCount = useBehavior(vm.participantCount$); const reconnecting = useBehavior(vm.reconnecting$); + const screenShareError = useBehavior(vm.screenShareError$); const layout = useBehavior(vm.layout$); const edgeToEdge = useBehavior(vm.edgeToEdge$); const overflowing = useBehavior(vm.overflowing$); @@ -399,6 +400,21 @@ export const InCallView: FC = ({ ); } + const onDismissScreenShareToast = useCallback( + () => vm.dismissScreenShareError(), + [vm], + ); + const screenShareToast = ( + + {t("error.screen_share_failed")} + + ); + // The reconnecting toast cannot be dismissed const onDismissReconnectingToast = useCallback(() => {}, []); // We need to use a non-modal toast to avoid trapping focus within the toast. @@ -634,6 +650,7 @@ export const InCallView: FC = ({ {reconnectingToast} + {screenShareToast} {earpieceOverlay} {footer} diff --git a/src/room/__snapshots__/InCallView.test.tsx.snap b/src/room/__snapshots__/InCallView.test.tsx.snap index 984e2b12e..7cd044bfc 100644 --- a/src/room/__snapshots__/InCallView.test.tsx.snap +++ b/src/room/__snapshots__/InCallView.test.tsx.snap @@ -171,7 +171,7 @@ exports[`InCallView > rendering > renders 1`] = ` class="_settingsLogoContainer_20b7b4" >