From 12752443d1cd02fddef9e8eecab75496e5e42b41 Mon Sep 17 00:00:00 2001 From: gregory1996 Date: Wed, 19 Jun 2024 15:28:12 +0300 Subject: [PATCH] add JWT parsing on sw, separate IndexedDB logic, and implement named responses cache by did --- src/indexedDB.js | 23 ++++++++ src/service-worker.js | 132 ++++++++++++++---------------------------- 2 files changed, 65 insertions(+), 90 deletions(-) create mode 100644 src/indexedDB.js diff --git a/src/indexedDB.js b/src/indexedDB.js new file mode 100644 index 000000000..b2de9b27b --- /dev/null +++ b/src/indexedDB.js @@ -0,0 +1,23 @@ +import { openDB } from 'idb'; + +const DB_NAME = "wwwallet-db"; +const DB_VERSION = 1; +const DB_STORAGE_VC_NAME = "storage"; + +const dbPromise = openDB(DB_NAME, DB_VERSION, { + upgrade(db) { + db.createObjectStore(DB_STORAGE_VC_NAME); + }, +}); + +export async function saveResponseToIndexedDB(did, url, responseText) { + const db = await dbPromise; + const key = `${did}-${url}`; + await db.put(DB_STORAGE_VC_NAME, responseText, key); +} + +export async function getResponseFromIndexedDB(did, url) { + const db = await dbPromise; + const key = `${did}-${url}`; + return db.get(DB_STORAGE_VC_NAME, key); +} diff --git a/src/service-worker.js b/src/service-worker.js index 068fd1e0c..9b4ec0b10 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -5,154 +5,94 @@ import { ExpirationPlugin } from "workbox-expiration"; import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching"; import { registerRoute } from "workbox-routing"; import { StaleWhileRevalidate } from "workbox-strategies"; -import { openDB } from 'idb'; - -const DB_NAME = "wwwallet-db"; -const DB_VERSION = 1; -const DB_STORAGE_VC_NAME = "storage"; - -const dbPromise = openDB(DB_NAME, DB_VERSION, { - upgrade(db) { - db.createObjectStore(DB_STORAGE_VC_NAME); - }, -}); +import { + saveResponseToIndexedDB, + getResponseFromIndexedDB, +} from './indexedDB'; clientsClaim(); -// Precache all of the assets generated by your build process. -// Their URLs are injected into the manifest variable below. -// This variable must be present somewhere in your service worker file, -// even if you decide not to use precaching. See https://cra.link/PWA precacheAndRoute([ ...self.__WB_MANIFEST, { url: '/manifest.json', revision: '1' }, { url: '/favicon.ico', revision: '1' }, ]); -// Set up App Shell-style routing, so that all navigation requests -// are fulfilled with your index.html shell. Learn more at -// https://developers.google.com/web/fundamentals/architecture/app-shell const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$"); registerRoute( - // Return false to exempt requests from being fulfilled by index.html. ({ request, url }) => { - // If this isn't a navigation, skip. if (request.mode !== "navigate") { return false; } - // If this is a URL that starts with /_, skip. if (url.pathname.startsWith("/_")) { return false; } - // If this looks like a URL for a resource, because it contains // a file extension, skip. if (url.pathname.match(fileExtensionRegexp)) { return false; } - // Return true to signal that we want to use the handler. return true; }, - createHandlerBoundToURL('/index.html') // Assumes your index.html is in the root of the public directory + createHandlerBoundToURL('/index.html') ); -// An example runtime caching route for requests that aren't handled by the -// precache, in this case same-origin .png requests like those from in public/ -// and cross-origin .png requests for credential logo images registerRoute( - // Add in any other file extensions or routing criteria as needed. - ({ url }) => - url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst. + ({ url }) => url.pathname.endsWith(".png"), new StaleWhileRevalidate({ cacheName: "images", plugins: [ - // Ensure that once this runtime cache reaches a maximum size the - // least-recently used images are removed. new ExpirationPlugin({ maxEntries: 50 }), ], }) ); -// This allows the web app to trigger skipWaiting via -// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +let currentDid = null; + self.addEventListener("message", (event) => { if (event.data && event.data.type === "SKIP_WAITING") { self.skipWaiting(); } if (event.data && event.data.type === 'ADD_APP_TOKEN') { - handleSessionStorageChange(event.data.value); + const did = parseJwt(event.data.value).did; + currentDid = did; + handleSessionStorageChange(event.data.value, did); } }); -// In-memory store for sensitive data -const inMemoryStore = {}; - -async function fetchAndSaveResponse(request) { +async function fetchAndSaveResponse(request, did) { try { const response = await fetch(request); if (response.ok) { const url = request.url; - if (isSensitiveResponse(url)) { - const responseText = await response.clone().text(); - inMemoryStore[url] = responseText; - console.log(`Cached sensitive response in memory for URL: ${url}`); - } else { - const responseText = await response.clone().text(); - await saveResponseToIndexedDB(url, responseText); - console.log(`Cached response in IndexedDB for URL: ${url}`); - } + const responseText = await response.clone().text(); + await saveResponseToIndexedDB(did, url, responseText); + console.log(`Cached response in IndexedDB for URL: ${url}`); return response; } } catch (error) { const url = request.url; - if (isSensitiveResponse(url)) { - const storedResponse = inMemoryStore[url]; - if (storedResponse) { - console.log(`Retrieved sensitive response from memory for URL: ${url}`); - return new Response(storedResponse); - } - } else { - const responseFromIndexedDB = await getResponseFromIndexedDB(url); - if (responseFromIndexedDB) { - console.log(`Retrieved response from IndexedDB for URL: ${url}`); - return new Response(responseFromIndexedDB); - } + const responseFromIndexedDB = await getResponseFromIndexedDB(did, url); + if (responseFromIndexedDB) { + console.log(`Retrieved response from IndexedDB for URL: ${url}`); + return new Response(responseFromIndexedDB); } throw error; } } -async function saveResponseToIndexedDB(url, responseText) { - const db = await dbPromise; - await db.put(DB_STORAGE_VC_NAME, responseText, url); -} - -async function getResponseFromIndexedDB(url) { - const db = await dbPromise; - return db.get(DB_STORAGE_VC_NAME, url); -} - -function isSensitiveResponse(url) { - const sensitiveEndpoints = [ - '/storage/vc', - '/storage/vp', - '/user/session/account-info', - ]; - return sensitiveEndpoints.some(endpoint => url.includes(endpoint)); -} - const matchVCStorageCb = ({ url }) => url.pathname.endsWith("/storage/vc"); -const handlerVCStorageCb = async ({ request }) => await fetchAndSaveResponse(request); +const handlerVCStorageCb = async ({ request }) => await fetchAndSaveResponse(request, currentDid); const matchVCStorageVp = ({ url }) => url.pathname.endsWith("/storage/vp"); -const handlerVCStorageVp = async ({ request }) => await fetchAndSaveResponse(request); +const handlerVCStorageVp = async ({ request }) => await fetchAndSaveResponse(request, currentDid); const matchIssuersCb = ({ url }) => url.pathname.endsWith("/legal_person/issuers/all"); -const handlerIssuersCb = async ({ request }) => await fetchAndSaveResponse(request); +const handlerIssuersCb = async ({ request }) => await fetchAndSaveResponse(request, currentDid); const matchVerifiersCb = ({ url }) => url.pathname.endsWith("/verifiers/all"); -const handlerVerifiersCb = async ({ request }) => await fetchAndSaveResponse(request); +const handlerVerifiersCb = async ({ request }) => await fetchAndSaveResponse(request, currentDid); const matchAccountInfoCb = ({ url }) => url.pathname.endsWith("/user/session/account-info"); -const handlerAccountInfoCb = async ({ request }) => await fetchAndSaveResponse(request); +const handlerAccountInfoCb = async ({ request }) => await fetchAndSaveResponse(request, currentDid); registerRoute(matchVCStorageCb, handlerVCStorageCb); registerRoute(matchVCStorageVp, handlerVCStorageVp); @@ -164,20 +104,19 @@ function getBackendUrl() { return process.env.REACT_APP_WALLET_BACKEND_URL; } -async function fetchAndCache(appToken, url) { +async function fetchAndCache(appToken, url, did) { const walletBackendUrl = getBackendUrl(); const request = new Request(`${walletBackendUrl}${url}`, { headers: { 'Authorization': `Bearer ${appToken}` } }); - return fetchAndSaveResponse(request); + return fetchAndSaveResponse(request, did); } -async function handleSessionStorageChange(appToken) { - console.log(`Add app token:= ${appToken}`); +async function handleSessionStorageChange(appToken, did) { + console.log(`Add app token: ${appToken}, DID: ${did}`); - // List of URLs to fetch and cache const urlsToFetch = [ '/storage/vc', '/storage/vp', @@ -188,9 +127,22 @@ async function handleSessionStorageChange(appToken) { for (const url of urlsToFetch) { try { - await fetchAndCache(appToken, url); + await fetchAndCache(appToken, url, did); } catch (error) { console.error(`Error fetching and caching data for ${url}`, error); } } } + +// Helper function to parse JWT and extract DID +function parseJwt(token) { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + Array.prototype.map.call(atob(base64), (c) => + '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ).join('') + ); + + return JSON.parse(jsonPayload); +}