diff --git a/public/sw.js b/public/sw.js index 58fd5a8..ad8a6bd 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'whisky-vault-v7-bunker'; // Neue Version für stabilen Bunker + SWR +const CACHE_NAME = 'whisky-vault-v8-bunker'; // Optimierter Bunker v8 // CONFIG: Kern-Seiten und Assets für den Bunker (sofortiges Pre-Caching) const CORE_PAGES = [ @@ -6,13 +6,13 @@ const CORE_PAGES = [ ]; const STATIC_ASSETS = [ - '/manifest.webmanifest', // Korrigierter Pfad für Next.js + '/manifest.webmanifest', '/icon-192.png', '/icon-512.png', '/favicon.ico', ]; -// Helper: Fetch mit Timeout und AbortController (sauberer Abbruch) +// Helper: Fetch mit Timeout und Sauberen Abort async function fetchWithTimeout(request, timeoutMs = 3000) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeoutMs); @@ -26,36 +26,47 @@ async function fetchWithTimeout(request, timeoutMs = 3000) { } } +// Helper: Broadcast an alle Clients +async function broadcast(message) { + const clients = await self.clients.matchAll(); + clients.forEach(client => client.postMessage(message)); +} + // 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...'); + console.log('🏗️ PWA: Building bunker v8...'); + const total = CORE_PAGES.length + STATIC_ASSETS.length; + let loaded = 0; + const promises = [...CORE_PAGES, ...STATIC_ASSETS].map(async (url) => { try { - const res = await fetch(url); + // Im Dev-Mode kann / lange dauern (Kompilierung). 10s Timeout. + const res = await fetchWithTimeout(url, 10000); if (!res.ok) throw new Error(`Status ${res.status}`); - return cache.put(url, res); + await cache.put(url, res); } catch (error) { console.error(`⚠️ PWA: Pre-cache failed for ${url}:`, error); + } finally { + loaded++; + broadcast({ + type: 'PRECACHE_PROGRESS', + progress: Math.round((loaded / total) * 100) + }); } }); + await Promise.all(promises); console.log('✅ PWA: Bunker build finished'); - // Signal to clients that pre-caching is complete + // Signal an Clients: Bunker ist bereit 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( @@ -114,7 +125,6 @@ self.addEventListener('fetch', (event) => { } // 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' || @@ -126,7 +136,7 @@ self.addEventListener('fetch', (event) => { if (isNavigation || isAsset) { event.respondWith( caches.match(event.request).then(async (cachedResponse) => { - // Hintergrund-Update vorbereiten + // Hintergrund-Update const fetchPromise = fetchWithTimeout(event.request, 5000) .then(async (networkResponse) => { if (networkResponse && networkResponse.status === 200) { @@ -135,21 +145,14 @@ self.addEventListener('fetch', (event) => { } return networkResponse; }) - .catch(() => { /* Fail silently in background */ }); + .catch(() => { /* Silent 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; - } + if (shell) return shell; } - // Assets: Cache oder Netzwerk return cachedResponse || fetchPromise || fetch(event.request); }) ); diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 5951fc3..1101587 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -6,6 +6,7 @@ import { WifiOff, ShieldCheck } from 'lucide-react'; export default function OfflineIndicator() { const [isOffline, setIsOffline] = useState(false); const [isBunkerReady, setIsBunkerReady] = useState(false); + const [progress, setProgress] = useState(0); useEffect(() => { setIsOffline(!navigator.onLine); @@ -17,6 +18,9 @@ export default function OfflineIndicator() { const handleOffline = () => setIsOffline(true); const handleMessage = (event: MessageEvent) => { + if (event.data?.type === 'PRECACHE_PROGRESS') { + setProgress(event.data.progress); + } if (event.data?.type === 'PRECACHE_COMPLETE' || event.data?.type === 'BUNKER_STATUS') { console.log('🛡️ PWA: Bunker is ready for offline use!'); setIsBunkerReady(true); @@ -30,10 +34,27 @@ export default function OfflineIndicator() { if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', handleMessage); - // Proactively check status if SW is already active - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage({ type: 'CHECK_BUNKER_STATUS' }); - } + // Proactive check + navigator.serviceWorker.ready.then((registration) => { + if (registration.active) { + registration.active.postMessage({ type: 'CHECK_BUNKER_STATUS' }); + } + }); + + // Fallback: If after 20s we still think we are loading but SW is active, assume ready + const timer = setTimeout(() => { + const isSwActive = !!navigator.serviceWorker.controller; + if (!isBunkerReady && isSwActive) { + setIsBunkerReady(true); + localStorage.setItem('whisky_bunker_ready', 'true'); + } + }, 20000); + return () => { + clearTimeout(timer); + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + navigator.serviceWorker.removeEventListener('message', handleMessage); + }; } return () => { @@ -43,7 +64,7 @@ export default function OfflineIndicator() { navigator.serviceWorker.removeEventListener('message', handleMessage); } }; - }, []); + }, [isBunkerReady]); if (isOffline) { return ( @@ -76,7 +97,7 @@ export default function OfflineIndicator() {
- Bunker wird geladen... + Bunker wird geladen... {progress > 0 ? `${progress}%` : ''}