From 02950499eac0307d25edcc7b53029779f9c499c9 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Fri, 16 Oct 2026 21:31:55 -0400 Subject: [PATCH] fix(startup): Firefox no longer hangs on "Heating up" after login (#303) initClient awaited navigator.storage.persist() before creating the client. Chromium and WebKit answer at once, but Firefox shows a permission prompt and leaves the promise pending until it's answered, so the app sat on "Heating up" (no sync, no crypto, no IndexedDB) for every Firefox user who didn't notice the prompt. The request is still made (KE-1 only needs that), but startup waits at most 1.5 s for the answer. The comment claiming persist() never prompts is corrected. Verified in Playwright Firefox with the prompt left unanswered (persist() still pending): before, still "Heating up" after 20 s; now the room list after 12 s. Fixes #303 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- src/client/initMatrix.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index ca33ebd39..605d0f300 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -20,9 +20,12 @@ export const IDB_VERSION_CONFLICT = 'IDB_VERSION_CONFLICT'; * session. When that happens the device "resurrects" with a blank key store and the * client re-uploads a one-time key at an id Synapse already holds → a permanent * `400 M_UNKNOWN: … already exists` upload-conflict storm (and, downstream, - * undecryptable to-device/media keys). `persist()` grants based on engagement / PWA - * install and shows no prompt; denial/absence is non-fatal. + * undecryptable to-device/media keys). Chromium/WebKit answer `persist()` at once + * (engagement / PWA install); Firefox shows a permission prompt and the promise + * stays pending until it's answered. Denial/absence is non-fatal. */ +const PERSIST_WAIT_MS = 1500; + export const requestPersistentStorage = async (): Promise => { try { if (!navigator.storage?.persist) return false; @@ -39,7 +42,15 @@ export const requestPersistentStorage = async (): Promise => { export const initClient = async (session: Session): Promise => { // Protect the crypto store from eviction before we create and write to it. - await requestPersistentStorage(); + // [Gitea #303] Never block startup on the answer: Firefox prompts and leaves + // the promise pending until the user responds, which froze the app on + // "Heating up". KE-1 only needs the request made; give it 1.5 s at most. + await Promise.race([ + requestPersistentStorage(), + new Promise((resolve) => { + setTimeout(() => resolve(false), PERSIST_WAIT_MS); + }), + ]); const indexedDBStore = new IndexedDBStore({ indexedDB: globalThis.indexedDB,