/// export type {}; declare const self: ServiceWorkerGlobalScope; type SessionInfo = { accessToken: string; baseUrl: string; }; /** * Store session per client (tab) */ const sessions = new Map(); const clientToResolve = new Map void>(); const clientToSessionPromise = new Map>(); async function cleanupDeadClients() { const activeClients = await self.clients.matchAll(); const activeIds = new Set(activeClients.map((c) => c.id)); Array.from(sessions.keys()).forEach((id) => { if (!activeIds.has(id)) { sessions.delete(id); clientToResolve.delete(id); clientToSessionPromise.delete(id); } }); } function setSession(clientId: string, accessToken: any, baseUrl: any) { if (typeof accessToken === 'string' && typeof baseUrl === 'string') { sessions.set(clientId, { accessToken, baseUrl }); } else { // Logout or invalid session sessions.delete(clientId); } const resolveSession = clientToResolve.get(clientId); if (resolveSession) { resolveSession(sessions.get(clientId)); clientToResolve.delete(clientId); clientToSessionPromise.delete(clientId); } } function requestSession(client: Client): Promise { const promise = clientToSessionPromise.get(client.id) ?? new Promise((resolve) => { clientToResolve.set(client.id, resolve); client.postMessage({ type: 'requestSession' }); }); if (!clientToSessionPromise.has(client.id)) { clientToSessionPromise.set(client.id, promise); } return promise; } async function requestSessionWithTimeout( clientId: string, timeoutMs = 3000 ): Promise { const client = await self.clients.get(clientId); if (!client) return undefined; const sessionPromise = requestSession(client); const timeout = new Promise((resolve) => { setTimeout(() => resolve(undefined), timeoutMs); }); return Promise.race([sessionPromise, timeout]); } self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', (event: ExtendableEvent) => { event.waitUntil( (async () => { await self.clients.claim(); await cleanupDeadClients(); })() ); }); /** * Receive session updates from clients */ self.addEventListener('message', (event: ExtendableMessageEvent) => { const client = event.source as Client | null; if (!client) return; const { type, accessToken, baseUrl } = event.data || {}; if (type === 'setSession') { setSession(client.id, accessToken, baseUrl); cleanupDeadClients(); } }); const MEDIA_PATHS = ['/_matrix/client/v1/media/download', '/_matrix/client/v1/media/thumbnail']; function mediaPath(url: string): boolean { try { const { pathname } = new URL(url); return MEDIA_PATHS.some((p) => pathname.startsWith(p)); } catch { return false; } } function validMediaRequest(url: string, baseUrl: string): boolean { return MEDIA_PATHS.some((p) => { const validUrl = new URL(p, baseUrl); return url.startsWith(validUrl.href); }); } function fetchConfig(token: string): RequestInit { return { headers: { Authorization: `Bearer ${token}`, }, cache: 'default', }; } self.addEventListener('fetch', (event: FetchEvent) => { const { url, method } = event.request; if (method !== 'GET' || !mediaPath(url)) return; const { clientId } = event; if (!clientId) return; const session = sessions.get(clientId); if (session) { if (validMediaRequest(url, session.baseUrl)) { event.respondWith(fetch(url, fetchConfig(session.accessToken))); } return; } event.respondWith( requestSessionWithTimeout(clientId).then((s) => { if (s && validMediaRequest(url, s.baseUrl)) { return fetch(url, fetchConfig(s.accessToken)); } return fetch(event.request); }) ); });