// Bridge the live Matrix session (baseUrl + accessToken) to the service // worker, which attaches `Authorization: Bearer ` to authenticated // media requests (/_matrix/client/v1/media/{download,thumbnail}) — see // src/sw.ts fetch handler. Without this, media GETs hit the homeserver // unauthenticated and 401. // // First-load race: on the very first visit after SW install, // `navigator.serviceWorker.controller` is null — the worker has registered // but not yet taken over (it calls `self.clients.claim()` in its activate // handler). A direct postMessage at that moment silently drops, and there is // no controller to receive it. So we keep the latest session in // `pendingSession` and re-attempt when (a) the registration becomes ready and // (b) on every `controllerchange`. This mirrors the language bridge in // src/app/utils/pushLanguageBridge.ts, which already covers this exact window. // Both retry paths no-op once the controller is set and the post has landed, // so duplicates are cheap. type PendingSession = { baseUrl?: string; accessToken?: string }; let pendingSession: PendingSession | undefined; let swControllerWatchInstalled = false; const trySendSession = (): void => { if (pendingSession === undefined) return; if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return; const sw = navigator.serviceWorker; if (!sw.controller) return; try { sw.controller.postMessage({ type: 'setSession', accessToken: pendingSession.accessToken, baseUrl: pendingSession.baseUrl, }); } catch { /* ignore — next controllerchange re-posts */ } }; const installSwControllerWatch = (): void => { if (swControllerWatchInstalled) return; if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return; swControllerWatchInstalled = true; const sw = navigator.serviceWorker; // `ready` resolves once there is an active registration; our SW then calls // `self.clients.claim()` in its activate handler, so shortly after `ready` // the page gains a controller and `controllerchange` fires. We listen to // both so the first-install window is covered regardless of which fires // first. sw.ready.then(trySendSession).catch(() => undefined); sw.addEventListener('controllerchange', trySendSession); }; export function pushSessionToSW(baseUrl?: string, accessToken?: string) { if (!('serviceWorker' in navigator)) return; // Remember the latest session even when there is no controller yet, then // attempt delivery now and again whenever the controller appears. Logout // passes both args undefined, which the SW reads as "clear session". pendingSession = { baseUrl, accessToken }; installSwControllerWatch(); trySendSession(); }