const CACHE_NAME = "dada-public-assets-v1"; const DATABASE_NAME = "dada-public-asset-cache-v1"; const LRU_STORE = "public_asset_lru"; const POLICY_STORE = "client_cache_policy"; const PUBLIC_ROUTE = /^\/api\/v1\/assets\/public\/([a-z0-9][a-z0-9._-]{0,79})\/([0-9a-f-]{36})$/i; function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open(DATABASE_NAME, 1); request.onupgradeneeded = () => { const database = request.result; if (!database.objectStoreNames.contains(LRU_STORE)) database.createObjectStore(LRU_STORE, { keyPath: "resource_id" }); if (!database.objectStoreNames.contains(POLICY_STORE)) database.createObjectStore(POLICY_STORE, { keyPath: "policy_id" }); }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error ?? new Error("public_cache_database_open_failed")); }); } async function readMetadata(resourceId) { const database = await openDatabase(); try { return await new Promise((resolve, reject) => { const request = database.transaction(LRU_STORE, "readonly").objectStore(LRU_STORE).get(resourceId); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error ?? new Error("public_cache_metadata_read_failed")); }); } finally { database.close(); } } async function touchMetadata(resourceId, releaseVersion) { const database = await openDatabase(); try { await new Promise((resolve, reject) => { const transaction = database.transaction(LRU_STORE, "readwrite"); const store = transaction.objectStore(LRU_STORE); const request = store.get(resourceId); request.onsuccess = () => { const entry = request.result; if (entry?.release_version === releaseVersion) store.put({ ...entry, last_accessed_at: Date.now() }); }; transaction.oncomplete = () => resolve(); transaction.onabort = () => reject(transaction.error ?? new Error("public_cache_touch_aborted")); transaction.onerror = () => reject(transaction.error ?? new Error("public_cache_touch_failed")); }); } finally { database.close(); } } self.addEventListener("install", () => self.skipWaiting()); self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); self.addEventListener("fetch", (event) => { if (event.request.method !== "GET") return; const url = new URL(event.request.url); if (url.origin !== self.location.origin || url.search || url.hash) return; const route = PUBLIC_ROUTE.exec(url.pathname); if (!route) return; event.respondWith((async () => { const releaseVersion = route[1]; const resourceId = route[2]; const metadata = await readMetadata(resourceId); if (!metadata || metadata.release_version !== releaseVersion) return fetch(event.request); const cached = await (await caches.open(CACHE_NAME)).match(event.request); if (!cached) return fetch(event.request); event.waitUntil(touchMetadata(resourceId, releaseVersion)); return cached; })()); });