const CACHE_NAME = 'check-list-poc-v3'; const DYNAMIC_CACHE_LIMIT = 50; const APP_SHELL = [ '/', '/user.html', '/admin.html', '/styles.css', '/app.js', '/manifest.webmanifest', '/js/constants.js', '/js/state.js', '/js/i18n.js', '/js/utils.js', '/js/db.js', '/js/api.js', '/js/validation.js', '/js/images.js', '/js/image-worker.js', '/js/forms.js', '/js/renderer.js', '/js/export.js' ]; /* * P5 — Bounded SW cache with LRU eviction. Static shell assets use cache-first. * API requests use network-first with automatic pruning of old dynamic entries * so the cache stays within DYNAMIC_CACHE_LIMIT. */ self.addEventListener('install', (event) => { event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) ) ) ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { const { request } = event; if (request.method !== 'GET') { return; } const url = new URL(request.url); if (url.origin !== self.location.origin) { return; } if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(request)); return; } event.respondWith(cacheFirst(request)); }); async function cacheFirst(request) { const cache = await caches.open(CACHE_NAME); const cached = await cache.match(request); if (cached) { return cached; } const response = await fetch(request); cache.put(request, response.clone()); return response; } async function networkFirst(request) { const cache = await caches.open(CACHE_NAME); try { const response = await fetch(request); cache.put(request, response.clone()); await trimCache(cache, DYNAMIC_CACHE_LIMIT); return response; } catch { const cached = await cache.match(request); if (cached) { return cached; } return new Response(JSON.stringify({ message: 'Offline and no cached response available.' }), { status: 503, headers: { 'Content-Type': 'application/json' } }); } } /** * P5 — Remove oldest entries once the cache exceeds the limit. Only dynamic * (non-shell) entries are evicted so the app shell remains always available. */ async function trimCache(cache, maxEntries) { const keys = await cache.keys(); if (keys.length <= maxEntries) { return; } const shellSet = new Set(APP_SHELL.map((path) => new URL(path, self.location.origin).href)); const evictable = keys.filter((request) => !shellSet.has(request.url)); while (evictable.length + APP_SHELL.length > maxEntries && evictable.length > 0) { await cache.delete(evictable.shift()); } }