// ═══════════════════════════════════════════════════════════════════════════ // Canteen Asset Tracker — Service Worker // Caches app shell + CDN deps. Network-first for API, cache fallback for static. // ═══════════════════════════════════════════════════════════════════════════ const CACHE_NAME = 'canteen-v8'; // App shell — core resources needed to boot the PWA const APP_SHELL = [ '/', '/index.html', ]; // CDN resources we want cached for offline resilience const CDN_URLS = [ 'https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/umd/index.min.js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', 'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js', 'https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js', ]; // ── Install: pre-cache app shell + CDN resources ────────────────────────── self.addEventListener('install', (event) => { console.log('[SW] Install — caching app shell + CDN assets'); event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll([...APP_SHELL, ...CDN_URLS]).catch((err) => { console.warn('[SW] Some resources failed to cache (non-fatal):', err.message); }); }) ); // Activate immediately — don't wait for old SW to release self.skipWaiting(); }); // ── Activate: purge old caches ──────────────────────────────────────────── self.addEventListener('activate', (event) => { console.log('[SW] Activate — purging old caches'); event.waitUntil( caches.keys().then((keys) => { return Promise.all( keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)) ); }) ); // Take control of all clients immediately self.clients.claim(); }); // ── Fetch: network-first for everything, cache fallback ─────────────────── self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); // Navigation requests: network-first, fallback to cached index.html if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request) .then((response) => { // Cache the latest version const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); return response; }) .catch(() => { return caches.match(event.request).then((cached) => cached || caches.match('/')); }) ); return; } // API GET calls: network-first, cache fallback (offline reads) // API POST/PUT/DELETE: pass through — main thread handles offline queuing if (url.pathname.startsWith('/api/')) { if (event.request.method === 'GET') { event.respondWith( fetch(event.request) .then((response) => { if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); } return response; }) .catch(() => caches.match(event.request)) ); } // Non-GET API calls: pass through — main thread queues offline writes return; } // Static assets (local + CDN): network-first, cache fallback event.respondWith( fetch(event.request) .then((response) => { if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); } return response; }) .catch(() => { return caches.match(event.request); }) ); }); // ── Message handler: allow main thread to trigger queue sync ────────────── self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SYNC_QUEUE') { // Forward sync request to all controlled clients self.clients.matchAll().then((clients) => { clients.forEach((client) => { client.postMessage({ type: 'PROCESS_OFFLINE_QUEUE' }); }); }); } });