// What To Cook? - PWA Service Worker (v1.1.0) const CACHE_NAME = 'wtc-cache-v2'; const PRECACHE_URLS = [ '/', '/cookbook', '/shopping-list', '/manifest.json', '/favicon.ico', '/static/favicon.svg', '/static/cookbook.css', '/static/icon-192.png', '/static/icon-512.png', '/static/apple-touch-icon.png' ]; // Install: Pre-cache shell assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(PRECACHE_URLS).catch((err) => { console.warn('[SW] Pre-caching partial failure, continuing:', err); }); }).then(() => self.skipWaiting()) ); }); // Activate: Clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { return caches.delete(cacheName); } }) ); }).then(() => self.clients.claim()) ); }); // Fetch: Strategy self.addEventListener('fetch', (event) => { const req = event.request; const url = new URL(req.url); // Skip non-GET and WebSocket/API mutation requests if (req.method !== 'GET') return; if (url.pathname.startsWith('/ws/') || url.pathname.startsWith('/api/')) return; // Navigation requests (HTML pages): Network first, fallback to cache if (req.mode === 'navigate') { event.respondWith( fetch(req).then((response) => { if (response && response.status === 200) { const copy = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(req, copy)); } return response; }).catch(async () => { const cached = await caches.match(req); if (cached) return cached; // If exact page not cached, return home or shopping list if available if (url.pathname === '/shopping-list') { const shopCached = await caches.match('/shopping-list'); if (shopCached) return shopCached; } const homeCached = await caches.match('/'); if (homeCached) return homeCached; return new Response('Offline – keine Verbindung vorhanden.', { status: 503, headers: { 'Content-Type': 'text/plain; charset=utf-8' } }); }) ); return; } // Static assets: Cache-first, then network update if (url.pathname.startsWith('/static/') || url.hostname.includes('fonts.googleapis.com') || url.hostname.includes('fonts.gstatic.com')) { event.respondWith( caches.match(req).then((cached) => { if (cached) { // In background, refresh cache fetch(req).then((networkRes) => { if (networkRes && networkRes.status === 200) { caches.open(CACHE_NAME).then((cache) => cache.put(req, networkRes)); } }).catch(() => {}); return cached; } return fetch(req).then((networkRes) => { if (networkRes && networkRes.status === 200) { const copy = networkRes.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(req, copy)); } return networkRes; }); }) ); return; } });