Request session info from sw if missing (#2664)
* request session info from sw if missing * fix async session request in fetch * respond fetch synchronously and add early check for non media requests (#2670) * make sure we call respondWith synchronously * simplify isMediaRequest in sw * improve naming in sw * get back baseUrl check into validMediaRequest * pass original request into fetch in sw * extract mediaPath util and performs checks properly --------- Co-authored-by: mmmykhailo <35040944+mmmykhailo@users.noreply.github.com>
This commit is contained in:
parent
29ec172c8b
commit
170f5cd473
3 changed files with 105 additions and 36 deletions
|
|
@ -68,7 +68,6 @@ 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;
|
||||
|
|
@ -116,7 +115,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath);
|
||||
return redirect(getLoginPath());
|
||||
}
|
||||
pushSessionToSW(session.baseUrl, session.accessToken);
|
||||
return null;
|
||||
}}
|
||||
element={
|
||||
|
|
|
|||
|
|
@ -34,17 +34,14 @@ if ('serviceWorker' in navigator) {
|
|||
|
||||
navigator.serviceWorker.register(swUrl).then(sendSessionToSW);
|
||||
navigator.serviceWorker.ready.then(sendSessionToSW);
|
||||
window.addEventListener('load', sendSessionToSW);
|
||||
|
||||
// When returning from background
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
navigator.serviceWorker.addEventListener('message', (ev) => {
|
||||
const { type } = ev.data ?? {};
|
||||
|
||||
if (type === 'requestSession') {
|
||||
sendSessionToSW();
|
||||
}
|
||||
});
|
||||
|
||||
// When restored from bfcache (important on iOS)
|
||||
window.addEventListener('pageshow', sendSessionToSW);
|
||||
}
|
||||
|
||||
const mountApp = () => {
|
||||
|
|
|
|||
128
src/sw.ts
128
src/sw.ts
|
|
@ -3,14 +3,6 @@
|
|||
export type {};
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
self.addEventListener('install', () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
type SessionInfo = {
|
||||
accessToken: string;
|
||||
baseUrl: string;
|
||||
|
|
@ -21,6 +13,9 @@ type SessionInfo = {
|
|||
*/
|
||||
const sessions = new Map<string, SessionInfo>();
|
||||
|
||||
const clientToResolve = new Map<string, (value: SessionInfo | undefined) => void>();
|
||||
const clientToSessionPromise = new Map<string, Promise<SessionInfo | undefined>>();
|
||||
|
||||
async function cleanupDeadClients() {
|
||||
const activeClients = await self.clients.matchAll();
|
||||
const activeIds = new Set(activeClients.map((c) => c.id));
|
||||
|
|
@ -28,10 +23,72 @@ async function cleanupDeadClients() {
|
|||
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<SessionInfo | undefined> {
|
||||
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<SessionInfo | undefined> {
|
||||
const client = await self.clients.get(clientId);
|
||||
if (!client) return undefined;
|
||||
|
||||
const sessionPromise = requestSession(client);
|
||||
|
||||
const timeout = new Promise<undefined>((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
|
||||
*/
|
||||
|
|
@ -41,23 +98,28 @@ self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
|||
|
||||
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);
|
||||
if (type === 'setSession') {
|
||||
setSession(client.id, accessToken, baseUrl);
|
||||
cleanupDeadClients();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
const MEDIA_PATHS = ['/_matrix/client/v1/media/download', '/_matrix/client/v1/media/thumbnail'];
|
||||
|
||||
return url.startsWith(downloadUrl.href) || url.startsWith(thumbnailUrl.href);
|
||||
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 {
|
||||
|
|
@ -72,13 +134,25 @@ function fetchConfig(token: string): RequestInit {
|
|||
self.addEventListener('fetch', (event: FetchEvent) => {
|
||||
const { url, method } = event.request;
|
||||
|
||||
if (method !== 'GET') return;
|
||||
if (!event.clientId) return;
|
||||
if (method !== 'GET' || !mediaPath(url)) return;
|
||||
|
||||
const session = sessions.get(event.clientId);
|
||||
if (!session) return;
|
||||
const { clientId } = event;
|
||||
if (!clientId) return;
|
||||
|
||||
if (!validMediaRequest(url, session.baseUrl)) return;
|
||||
const session = sessions.get(clientId);
|
||||
if (session) {
|
||||
if (validMediaRequest(url, session.baseUrl)) {
|
||||
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(fetch(url, fetchConfig(session.accessToken)));
|
||||
event.respondWith(
|
||||
requestSessionWithTimeout(clientId).then((s) => {
|
||||
if (s && validMediaRequest(url, s.baseUrl)) {
|
||||
return fetch(url, fetchConfig(s.accessToken));
|
||||
}
|
||||
return fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue