92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
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('/'))),
|
|
)
|
|
})
|