From 074c5552943206ff47df0aa0f85b50b4d79af656 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 14 Feb 2026 11:41:36 +0530 Subject: [PATCH] Post session info to service worker instead of asking from sw (#2605) post session info to service worker instead of asking from sw on each request --- src/app/pages/Router.tsx | 5 ++- src/client/initMatrix.ts | 2 + src/index.tsx | 15 +++---- src/sw-session.ts | 10 +++++ src/sw.ts | 94 +++++++++++++++++++++++++++------------- 5 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 src/sw-session.ts diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index 18591f29..04d14a07 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -68,6 +68,7 @@ import { Create } from './client/create'; import { CreateSpaceModalRenderer } from '../features/create-space'; import { SearchModalRenderer } from '../features/search'; import { getFallbackSession } from '../state/sessions'; +import { pushSessionToSW } from '../../sw-session'; export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => { const { hashRouter } = clientConfig; @@ -106,7 +107,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) { - if (!getFallbackSession()) { + const session = getFallbackSession(); + if (!session) { const afterLoginPath = getAppPathFromHref( getOriginBaseUrl(hashRouter), window.location.href @@ -114,6 +116,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath); return redirect(getLoginPath()); } + pushSessionToSW(session.baseUrl, session.accessToken); return null; }} element={ diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index 487c3f13..498d4f75 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -2,6 +2,7 @@ import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from import { cryptoCallbacks } from './secretStorageKeys'; import { clearNavToActivePathStore } from '../app/state/navToActivePath'; +import { pushSessionToSW } from '../sw-session'; type Session = { baseUrl: string; @@ -53,6 +54,7 @@ export const clearCacheAndReload = async (mx: MatrixClient) => { }; export const logoutClient = async (mx: MatrixClient) => { + pushSessionToSW(); mx.stopClient(); try { await mx.logout(); diff --git a/src/index.tsx b/src/index.tsx index ddfd30b4..0019a224 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,6 +15,8 @@ import App from './app/pages/App'; // import i18n (needs to be bundled ;)) import './app/i18n'; +import { pushSessionToSW } from './sw-session'; +import { getFallbackSession } from './app/state/sessions'; document.body.classList.add(configClass, varsClass); @@ -25,16 +27,9 @@ if ('serviceWorker' in navigator) { ? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js` : `/dev-sw.js?dev-sw`; - navigator.serviceWorker.register(swUrl); - navigator.serviceWorker.addEventListener('message', (event) => { - if (event.data?.type === 'token' && event.data?.responseKey) { - // Get the token for SW. - const token = localStorage.getItem('cinny_access_token') ?? undefined; - event.source!.postMessage({ - responseKey: event.data.responseKey, - token, - }); - } + navigator.serviceWorker.register(swUrl).then(() => { + const session = getFallbackSession(); + pushSessionToSW(session?.baseUrl, session?.accessToken); }); } diff --git a/src/sw-session.ts b/src/sw-session.ts new file mode 100644 index 00000000..4b2ec055 --- /dev/null +++ b/src/sw-session.ts @@ -0,0 +1,10 @@ +export function pushSessionToSW(baseUrl?: string, accessToken?: string) { + if (!('serviceWorker' in navigator)) return; + if (!navigator.serviceWorker.controller) return; + + navigator.serviceWorker.controller.postMessage({ + type: 'setSession', + accessToken, + baseUrl, + }); +} diff --git a/src/sw.ts b/src/sw.ts index 2179dfcb..d4eebc02 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -3,22 +3,64 @@ export type {}; declare const self: ServiceWorkerGlobalScope; -async function askForAccessToken(client: Client): Promise { - return new Promise((resolve) => { - const responseKey = Math.random().toString(36); - const listener = (event: ExtendableMessageEvent) => { - if (event.data.responseKey !== responseKey) return; - resolve(event.data.token); - self.removeEventListener('message', listener); - }; - self.addEventListener('message', listener); - client.postMessage({ responseKey, type: 'token' }); +self.addEventListener('install', () => { + self.skipWaiting(); +}); + +self.addEventListener('activate', (event: ExtendableEvent) => { + event.waitUntil(self.clients.claim()); +}); + +type SessionInfo = { + accessToken: string; + baseUrl: string; +}; + +/** + * Store session per client (tab) + */ +const sessions = 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); + } }); } -function fetchConfig(token?: string): RequestInit | undefined { - if (!token) return undefined; +/** + * 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') return; + + cleanupDeadClients(); + + if (typeof accessToken === 'string' && typeof baseUrl === 'string') { + sessions.set(client.id, { accessToken, baseUrl }); + } else { + // Logout or invalid session + sessions.delete(client.id); + } +}); + +function validMediaRequest(url: string, baseUrl: string): boolean { + const downloadUrl = new URL('/_matrix/client/v1/media/download', baseUrl); + const thumbnailUrl = new URL('/_matrix/client/v1/media/thumbnail', baseUrl); + + return url.startsWith(downloadUrl.href) || url.startsWith(thumbnailUrl.href); +} + +function fetchConfig(token: string): RequestInit { return { headers: { Authorization: `Bearer ${token}`, @@ -27,26 +69,16 @@ function fetchConfig(token?: string): RequestInit | undefined { }; } -self.addEventListener('activate', (event: ExtendableEvent) => { - event.waitUntil(clients.claim()); -}); - self.addEventListener('fetch', (event: FetchEvent) => { const { url, method } = event.request; - if (method !== 'GET') return; - if ( - !url.includes('/_matrix/client/v1/media/download') && - !url.includes('/_matrix/client/v1/media/thumbnail') - ) { - return; - } - event.respondWith( - (async (): Promise => { - const client = await self.clients.get(event.clientId); - let token: string | undefined; - if (client) token = await askForAccessToken(client); - return fetch(url, fetchConfig(token)); - })() - ); + if (method !== 'GET') return; + if (!event.clientId) return; + + const session = sessions.get(event.clientId); + if (!session) return; + + if (!validMediaRequest(url, session.baseUrl)) return; + + event.respondWith(fetch(url, fetchConfig(session.accessToken))); });