const CACHE_NAME = 'whisky-vault-v7-bunker'; // Neue Version für stabilen Bunker + SWR // CONFIG: Kern-Seiten und Assets für den Bunker (sofortiges Pre-Caching) const CORE_PAGES = [ '/', // Dashboard / App Shell (Enthält Scan & Tasting) ]; const STATIC_ASSETS = [ '/manifest.webmanifest', // Korrigierter Pfad für Next.js '/icon-192.png', '/icon-512.png', '/favicon.ico', ]; // Helper: Fetch mit Timeout und AbortController (sauberer Abbruch) async function fetchWithTimeout(request, timeoutMs = 3000) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(request, { signal: controller.signal }); clearTimeout(id); return response; } catch (e) { clearTimeout(id); throw e; } } // Install: Lade alles Wichtige einzeln in den Bunker self.addEventListener('install', (event) => { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME).then(async (cache) => { console.log('🏗️ PWA: Building bunker v7...'); const promises = [...CORE_PAGES, ...STATIC_ASSETS].map(async (url) => { try { const res = await fetch(url); if (!res.ok) throw new Error(`Status ${res.status}`); return cache.put(url, res); } catch (error) { console.error(`⚠️ PWA: Pre-cache failed for ${url}:`, error); } }); await Promise.all(promises); console.log('✅ PWA: Bunker build finished'); // Signal to clients that pre-caching is complete broadcast({ type: 'PRECACHE_COMPLETE', version: CACHE_NAME }); }) ); }); // Helper: Broadcast to all clients async function broadcast(message) { const clients = await self.clients.matchAll(); clients.forEach(client => client.postMessage(message)); } // Activate: Alte Bunker räumen 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 bunker', cacheName); return caches.delete(cacheName); } }) ); }) ); self.clients.claim(); }); // Communication: Listen for status checks self.addEventListener('message', (event) => { if (event.data?.type === 'CHECK_BUNKER_STATUS') { event.source.postMessage({ type: 'BUNKER_STATUS', isReady: true, version: CACHE_NAME }); } }); self.addEventListener('fetch', (event) => { // Nur GET-Requests cachen if (event.request.method !== 'GET') return; const url = new URL(event.request.url); // 0. BYPASS: Auth & API (wird von App/Dexie direkt gehandelt) if (url.pathname.includes('/auth/') || url.pathname.includes('/api/') || url.hostname.includes('supabase.co')) { return; } // 1. NEXT.JS DATA (RSC): Bunker & Fallback if (url.pathname.startsWith('/_next/data/')) { event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached; return fetchWithTimeout(event.request, 2000) .catch(() => { return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }); }) ); return; } // 2. ASSETS & NAVIGATION: Stale-While-Revalidate (Der "Echte" Bunker Mode) // Wir liefern SOFORT aus dem Cache, fragen aber im Hintergrund das Netzwerk. const isNavigation = event.request.mode === 'navigate'; 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 (isNavigation || isAsset) { event.respondWith( caches.match(event.request).then(async (cachedResponse) => { // Hintergrund-Update vorbereiten const fetchPromise = fetchWithTimeout(event.request, 5000) .then(async (networkResponse) => { if (networkResponse && networkResponse.status === 200) { const cache = await caches.open(CACHE_NAME); cache.put(event.request, networkResponse.clone()); } return networkResponse; }) .catch(() => { /* Fail silently in background */ }); // Navigation Fallback Logik if (isNavigation) { if (cachedResponse) return cachedResponse; // Root Fallback für Deep Links (App Shell) const shell = await caches.match('/'); if (shell) { console.log('[SW] Route not cached, using Root App Shell fallback'); return shell; } } // Assets: Cache oder Netzwerk return cachedResponse || fetchPromise || fetch(event.request); }) ); } });