const CACHE_NAME = 'whisky-vault-v6-bunker'; // Neue Version erzwingen! // CONFIG: Core pages and assets to pre-cache immediately on install // WICHTIG: Hier muss die URL für "Neues Tasting" rein, damit sie offline da ist! const CORE_PAGES = [ '/', // Dashboard / App Shell '/sessions', // Tasting Sessions '/scan', // Scan Page (falls URL existiert) // '/tasting/new' // <--- FÜGE HIER DEINE TASTING URL HINZU, falls sie existiert! ]; const STATIC_ASSETS = [ '/manifest.json', '/icon-192.png', '/icon-512.png', '/favicon.ico', ]; // Install: Lade alles Wichtige sofort in den Keller self.addEventListener('install', (event) => { self.skipWaiting(); // Sofortiger Wechsel zum neuen SW event.waitUntil( caches.open(CACHE_NAME).then((cache) => { console.log('⚡ PWA: Pre-caching core pages and assets...'); return cache.addAll([...CORE_PAGES, ...STATIC_ASSETS]); }) ); }); // Activate: Alten Müll rauswerfen self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { console.log('🧹 PWA: Clearing old cache', cacheName); return caches.delete(cacheName); } }) ); }) ); self.clients.claim(); // Sofort Kontrolle übernehmen }); // Helper: Network mit Timeout (für den Notfall) function fetchWithTimeout(request, timeoutMs = 3000) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => reject(new Error('Network timeout')), timeoutMs); fetch(request).then( (res) => { clearTimeout(timeoutId); resolve(res); }, (err) => { clearTimeout(timeoutId); reject(err); } ); }); } self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); // 0. BYPASS: Auth & API immer durchlassen (werden von Dexie/App handled) if (url.pathname.includes('/auth/') || url.pathname.includes('/api/') || url.hostname.includes('supabase.co')) { return; } // 1. NEXT.JS DATA (RSC): Swallow Errors // Verhindert "Application Error" wenn JSON fehlt if (url.pathname.startsWith('/_next/data/')) { event.respondWith( caches.match(event.request).then((cached) => { // Wenn im Cache (selten bei RSC), nimm es if (cached) return cached; // Sonst Netzwerk mit Timeout return fetchWithTimeout(event.request, 2000) .then(res => { // Optional: RSC cachen für später? // Vorsicht: RSC Daten veralten schnell. Hier cachen wir NICHT. return res; }) .catch(() => { // Fallback: Leeres JSON, damit React nicht crasht return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }); }) ); return; } // 2. ASSETS (JS/CSS/Images): CACHE FIRST (Der "Bunker" Fix) // Wenn wir die Datei haben, nutzen wir sie. Keine Diskussion mit dem Netzwerk. const isAsset = event.request.destination === 'style' || event.request.destination === 'script' || event.request.destination === 'worker' || event.request.destination === 'font' || event.request.destination === 'image' || url.pathname.startsWith('/_next/static'); if (isAsset) { event.respondWith( caches.match(event.request).then((cachedResponse) => { if (cachedResponse) { // TREFFER: Sofort zurückgeben. Kein Fetch! // Das verhindert den "Network Changed" Crash. return cachedResponse; } // MISS: Nur dann zum Netzwerk gehen return fetch(event.request).then((networkResponse) => { if (networkResponse && networkResponse.status === 200) { const responseClone = networkResponse.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, responseClone); }); } return networkResponse; }); }) ); return; } // 3. NAVIGATION (HTML): App Shell / Cache First // Wenn offline, gib die Startseite zurück und lass Next.js routen. if (event.request.mode === 'navigate') { event.respondWith( (async () => { try { // A. Suche exakte Seite im Cache const cachedResponse = await caches.match(event.request); if (cachedResponse) return cachedResponse; // B. Suche Startseite (App Shell) im Cache // Das ist der wichtigste Fallback für SPAs! const shellResponse = await caches.match('/'); if (shellResponse) return shellResponse; // C. Wenn nichts im Cache ist (ganz neuer User): Netzwerk return await fetchWithTimeout(event.request, 3000); } catch (error) { console.log('[SW] Offline fallback failed:', error); // D. Letzter Ausweg (Text anzeigen) return new Response('
Bitte App neu laden.
', { headers: { 'Content-Type': 'text/html' } }); } })() ); } });