From 53b8a2f4d09c3f3b44ed9c3133f0fe86c385c4dd Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Mon, 19 Aug 2024 18:12:47 +0530 Subject: [PATCH] Add access-token to request --- src/platform/web/Platform.js | 8 +- src/platform/web/dom/ServiceWorkerHandler.js | 81 ++++++++++++++------ src/platform/web/sw.js | 31 +++++++- 3 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e4986cc7..a59d4fc3 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -146,8 +146,13 @@ export class Platform { this.onlineStatus = new OnlineStatus(); this.timeFormatter = new TimeFormatter(); this._serviceWorkerHandler = null; + this.sessionInfoStorage = new SessionInfoStorage( + "hydrogen_sessions_v1" + ); if (assetPaths.serviceWorker && "serviceWorker" in navigator) { - this._serviceWorkerHandler = new ServiceWorkerHandler(); + this._serviceWorkerHandler = new ServiceWorkerHandler( + this.sessionInfoStorage + ); this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } this.notificationService = undefined; @@ -156,7 +161,6 @@ export class Platform { this.crypto = new Crypto(cryptoExtras); } this.storageFactory = new StorageFactory(this._serviceWorkerHandler); - this.sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1"); this.estimateStorageUsage = estimateStorageUsage; if (typeof fetch === "function") { this.request = createFetchRequest(this.clock.createTimeout, this._serviceWorkerHandler); diff --git a/src/platform/web/dom/ServiceWorkerHandler.js b/src/platform/web/dom/ServiceWorkerHandler.js index 4b92d413..84f499a8 100644 --- a/src/platform/web/dom/ServiceWorkerHandler.js +++ b/src/platform/web/dom/ServiceWorkerHandler.js @@ -19,13 +19,14 @@ limitations under the License. // - UpdateService (see checkForUpdate method, and should also emit events rather than showing confirm dialog here) // - ConcurrentAccessBlocker (see preventConcurrentSessionAccess method) export class ServiceWorkerHandler { - constructor() { + constructor(sessionInfoStorage) { this._waitingForReply = new Map(); this._messageIdCounter = 0; this._navigation = null; this._registration = null; this._registrationPromise = null; this._currentController = null; + this._sessionInfoStorage = sessionInfoStorage; this.haltRequests = false; } @@ -50,8 +51,8 @@ export class ServiceWorkerHandler { })(); } - _onMessage(event) { - const {data} = event; + async _onMessage(event) { + const { data } = event; const replyTo = data.replyTo; if (replyTo) { const resolve = this._waitingForReply.get(replyTo); @@ -61,37 +62,63 @@ export class ServiceWorkerHandler { } } if (data.type === "hasSessionOpen") { - const hasOpen = this._navigation.observe("session").get() === data.payload.sessionId; - event.source.postMessage({replyTo: data.id, payload: hasOpen}); + const hasOpen = + this._navigation.observe("session").get() === + data.payload.sessionId; + event.source.postMessage({ replyTo: data.id, payload: hasOpen }); } else if (data.type === "hasRoomOpen") { - const hasSessionOpen = this._navigation.observe("session").get() === data.payload.sessionId; - const hasRoomOpen = this._navigation.observe("room").get() === data.payload.roomId; - event.source.postMessage({replyTo: data.id, payload: hasSessionOpen && hasRoomOpen}); + const hasSessionOpen = + this._navigation.observe("session").get() === + data.payload.sessionId; + const hasRoomOpen = + this._navigation.observe("room").get() === data.payload.roomId; + event.source.postMessage({ + replyTo: data.id, + payload: hasSessionOpen && hasRoomOpen, + }); } else if (data.type === "closeSession") { - const {sessionId} = data.payload; + const { sessionId } = data.payload; this._closeSessionIfNeeded(sessionId).finally(() => { - event.source.postMessage({replyTo: data.id}); + event.source.postMessage({ replyTo: data.id }); }); } else if (data.type === "haltRequests") { // this flag is read in fetch.js this.haltRequests = true; - event.source.postMessage({replyTo: data.id}); + event.source.postMessage({ replyTo: data.id }); } else if (data.type === "openRoom") { this._navigation.push("room", data.payload.roomId); + } else if (data.type === "getAccessToken") { + const token = await this._getLatestAccessToken(); + event.source.postMessage({ replyTo: data.id, payload: token }); } } + /** + * Fetch access-token from the storage + * @returns access token as string + */ + async _getLatestAccessToken() { + const currentSessionId = this._navigation?.path.get("session")?.value; + if (!currentSessionId) return null; + const { accessToken } = await this._sessionInfoStorage.get( + currentSessionId + ); + return accessToken; + } + _closeSessionIfNeeded(sessionId) { const currentSession = this._navigation?.path.get("session"); if (sessionId && currentSession?.value === sessionId) { - return new Promise(resolve => { - const unsubscribe = this._navigation.pathObservable.subscribe(path => { - const session = path.get("session"); - if (!session || session.value !== sessionId) { - unsubscribe(); - resolve(); + return new Promise((resolve) => { + const unsubscribe = this._navigation.pathObservable.subscribe( + (path) => { + const session = path.get("session"); + if (!session || session.value !== sessionId) { + unsubscribe(); + resolve(); + } } - }); + ); this._navigation.push("session"); }); } else { @@ -135,7 +162,10 @@ export class ServiceWorkerHandler { this._onMessage(event); break; case "updatefound": - this._registration.installing.addEventListener("statechange", this); + this._registration.installing.addEventListener( + "statechange", + this + ); break; case "statechange": { if (event.target.state === "installed") { @@ -149,10 +179,11 @@ export class ServiceWorkerHandler { // Clients.claim() in the SW can trigger a controllerchange event // if we had no SW before. This is fine, // and now our requests will be served from the SW. - this._currentController = navigator.serviceWorker.controller; + this._currentController = + navigator.serviceWorker.controller; } else { // active service worker changed, - // refresh, so we can get all assets + // refresh, so we can get all assets // (and not only some if we would not refresh) // up to date from it document.location.reload(); @@ -168,7 +199,7 @@ export class ServiceWorkerHandler { if (!worker) { worker = this._registration.active; } - worker.postMessage({type, payload}); + worker.postMessage({ type, payload }); } async _sendAndWaitForReply(type, payload, worker = undefined) { @@ -180,10 +211,10 @@ export class ServiceWorkerHandler { } this._messageIdCounter += 1; const id = this._messageIdCounter; - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { this._waitingForReply.set(id, resolve); }); - worker.postMessage({type, id, payload}); + worker.postMessage({ type, id, payload }); return await promise; } @@ -203,7 +234,7 @@ export class ServiceWorkerHandler { } async preventConcurrentSessionAccess(sessionId) { - return this._sendAndWaitForReply("closeSession", {sessionId}); + return this._sendAndWaitForReply("closeSession", { sessionId }); } async getRegistration() { diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 2c0aca5f..96b2f452 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -83,12 +83,15 @@ self.addEventListener("fetch", (event) => { This has to do with xhr not being supported in service workers. */ if (event.request.method === "GET") { - event.respondWith(handleRequest(event.request)); + event.respondWith(handleRequest(event)); } }); function isCacheableThumbnail(url) { - if (url.pathname.startsWith("/_matrix/media/r0/thumbnail/")) { + if ( + url.pathname.startsWith("/_matrix/media/r0/thumbnail/") || + url.pathname.startsWith("/_matrix/client/v1/media/thumbnail/") + ) { const width = parseInt(url.searchParams.get("width"), 10); const height = parseInt(url.searchParams.get("height"), 10); if (width <= 50 && height <= 50) { @@ -101,22 +104,42 @@ function isCacheableThumbnail(url) { const baseURL = new URL(self.registration.scope); let pendingFetchAbortController = new AbortController(); -async function handleRequest(request) { +async function handleRequest({ request, clientId }) { try { + // Special caching strategy for config.json and theme json files if ( request.url.includes("config.json") || /theme-.+\.json/.test(request.url) ) { return handleStaleWhileRevalidateRequest(request); } - const url = new URL(request.url); + // rewrite / to /index.html so it hits the cache + const url = new URL(request.url); if ( url.origin === baseURL.origin && url.pathname === baseURL.pathname ) { request = new Request(new URL("index.html", baseURL.href)); } + + // Add access token for authenticated media endpoints + if (request.url.includes("_matrix/client/v1/media")) { + const headers = new Headers(request.headers); + const client = await self.clients.get(clientId); + const accessToken = await sendAndWaitForReply( + client, + "getAccessToken", + {} + ); + headers.set("authorization", `Bearer ${accessToken}`); + request = new Request(request, { + mode: "cors", + credentials: "omit", + headers, + }); + } + let response = await readCache(request); if (!response) { // use cors so the resource in the cache isn't opaque and uses up to 7mb