diff --git a/public/sw.js b/public/sw.js index f8b6858..86505f8 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'whisky-vault-v12-offline'; +const CACHE_NAME = 'whisky-vault-v13-offline'; // CONFIG: Assets const STATIC_ASSETS = [ @@ -12,15 +12,21 @@ const CORE_PAGES = [ '/', ]; -// Global state to track progress even when UI is not listening +// Global state to track progress let currentProgress = 0; let isPrecacheFinished = false; -// Helper: Broadcast to all clients +// Helper: Broadcast to ALL clients (including those being installed) async function broadcast(message) { try { const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' }); - clients.forEach(client => client.postMessage(message)); + clients.forEach(client => { + try { + client.postMessage(message); + } catch (err) { + // Ignore message delivery errors + } + }); } catch (e) { } } @@ -43,23 +49,22 @@ async function fetchWithTimeout(url, timeoutMs = 30000) { // ๐๏ธ INSTALL: Build the Offline-Modus self.addEventListener('install', (event) => { + // Take over immediately self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME).then(async (cache) => { - console.log('๐๏ธ PWA: Building Offline-Modus v12...'); - - // ๐ก WAIT A MOMENT: Give the UI time to mount and register listeners - // In dev mode, the app takes a second to boot up. - await new Promise(resolve => setTimeout(resolve, 1500)); + console.log('๐๏ธ PWA: Building Offline-Modus v13...'); const items = [...STATIC_ASSETS, ...CORE_PAGES]; const total = items.length; let loaded = 0; + // Start broadcasting 0% immediately + broadcast({ type: 'OFFLINE_PROGRESS', progress: 0 }); + for (const url of items) { try { - // Start with manifest and icons (fast) then the app shell (slow in dev) const res = await fetchWithTimeout(url); if (res && res.ok) { await cache.put(url, res); diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 6248d56..beb07d0 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -10,8 +10,18 @@ export default function OfflineIndicator() { useEffect(() => { setIsOffline(!navigator.onLine); - const savedReady = localStorage.getItem('whisky_offline_ready') === 'true'; - setIsReady(savedReady); + + // ๐งผ Clean up legacy keys and check for ready state + const checkLocalReady = () => { + const ready = localStorage.getItem('whisky_offline_ready') === 'true' || + localStorage.getItem('whisky_bunker_ready') === 'true'; + if (ready) { + setIsReady(true); + // Standardize on the new key + localStorage.setItem('whisky_offline_ready', 'true'); + } + }; + checkLocalReady(); const handleOnline = () => setIsOffline(false); const handleOffline = () => setIsOffline(true); @@ -37,30 +47,48 @@ export default function OfflineIndicator() { window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); + const queryStatus = async () => { + if (!('serviceWorker' in navigator)) return; + + try { + const registration = await navigator.serviceWorker.getRegistration(); + if (registration) { + // Try to talk to ANY available worker (installing, waiting, or active) + const sw = registration.installing || registration.waiting || registration.active; + if (sw) { + sw.postMessage({ type: 'CHECK_OFFLINE_STATUS' }); + } + } + } catch (e) { + console.error('PWA: Status query failed', e); + } + }; + if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', handleMessage); + queryStatus(); - // Proactive status check - navigator.serviceWorker.ready.then(() => { - // If there's an active or installing worker, ask for status - const sw = navigator.serviceWorker.controller || - (navigator.serviceWorker as any).installing || - (navigator.serviceWorker as any).waiting; - - if (sw) { - sw.postMessage({ type: 'CHECK_OFFLINE_STATUS' }); + // ๐ ROBUST POLLING: If not ready and progress is 0, query periodically + // This handles cases where the SW was suspended or the first broadcast was missed. + const interval = setInterval(() => { + if (!isReady && progress < 100) { + queryStatus(); } - }); + }, 3000); + + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + navigator.serviceWorker.removeEventListener('message', handleMessage); + clearInterval(interval); + }; } return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); - if ('serviceWorker' in navigator) { - navigator.serviceWorker.removeEventListener('message', handleMessage); - } }; - }, []); + }, [isReady, progress]); if (isOffline) { return ( @@ -84,10 +112,10 @@ export default function OfflineIndicator() { } return ( -