const CACHE_NAME = 'mykindlebox-shell-v1' const SHELL_URLS = ['/', '/favicon.svg', '/icon-192.png', '/manifest.json'] // Vendored Tesseract OCR runtime: the worker, the three feature-detect WASM core // variants (relaxedSIMD / SIMD / plain LSTM — only one is loaded per browser, but // we can't know which ahead of time, so all are cached), and the English language // data. These are large (~16 MB total) but static and versioned with the app, so // they're precached on install and served cache-first (see the fetch handler): // once stored they never need re-fetching, and the pedigree scanner must work // fully offline. See FEATURE_OCR_Pedigree_Scan.md Q7. // // UPGRADE CONTRACT: cache-first means installed clients serve these from cache // forever. If tesseract.js is ever upgraded (package.json pins the exact version // for this reason) the files under public/tesseract/ must be re-vendored AND // CACHE_NAME must be bumped, or existing clients keep the old worker/WASM while // the app bundle uses the new library. const TESSERACT_URLS = [ '/tesseract/worker.min.js', '/tesseract/tesseract-core-lstm.wasm.js', '/tesseract/tesseract-core-simd-lstm.wasm.js', '/tesseract/tesseract-core-relaxedsimd-lstm.wasm.js', '/tesseract/eng.traineddata', ] self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => Promise.all([ cache.addAll(SHELL_URLS), // The ~16 MB Tesseract batch is best-effort: if it fails (flaky first // load, missing file in a bad deploy), the shell must still install — // the cache-first fetch branch below backfills these lazily the next // time the scanner runs online. cache.addAll(TESSERACT_URLS).catch(() => {}), ]), ), ) }) self.addEventListener('activate', (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))) .then(() => self.clients.claim()), ) }) // Network-first with a cache fallback for the app shell (HTML/JS/CSS/static // assets), so a previously-visited bundle still loads offline. API requests // are left alone — they should fail fast so the UI can show its own inline // "offline" state instead of serving stale data silently. self.addEventListener('fetch', (event) => { const { request } = event if (request.method !== 'GET') return const url = new URL(request.url) if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/uploads/')) return // Tesseract assets are static and large — cache-first. Serve the stored copy // outright when present (no network round-trip, works offline), and only hit // the network on a cache miss, storing the result for next time. if (url.pathname.startsWith('/tesseract/')) { event.respondWith( caches.match(request).then( (cached) => cached ?? fetch(request).then((response) => { if (response.ok) { const copy = response.clone() caches.open(CACHE_NAME).then((cache) => cache.put(request, copy)) } return response }), ), ) return } event.respondWith( fetch(request) .then((response) => { if (response.ok) { const copy = response.clone() caches.open(CACHE_NAME).then((cache) => cache.put(request, copy)) } return response }) .catch(() => caches.match(request).then((cached) => cached ?? caches.match('/'))), ) })