const CACHE_NAME = '1driver-v1'; const STATIC_CACHE = '1driver-static-v1'; const API_CACHE = '1driver-api-v1'; const IMAGE_CACHE = '1driver-images-v1'; const POCKETBASE_URL = 'https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io'; const PRECACHE_URLS = [ '/', '/login/', '/map/', '/settings/', '/manifest.json', '/icons/icon-192.png', '/icons/icon-512.png', '/icons/apple-touch-icon.png', ]; const STATIC_EXTENSIONS = [ '.js', '.css', '.woff', '.woff2', '.ttf', '.eot', '.ico', ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { return cache.addAll(PRECACHE_URLS).catch(() => { // Precache silently fails for offline-first approach }); }) ); self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((key) => key !== CACHE_NAME && key !== STATIC_CACHE && key !== API_CACHE && key !== IMAGE_CACHE) .map((key) => caches.delete(key)) ) ) ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); // Skip non-GET requests if (event.request.method !== 'GET') return; // Skip cross-origin except PocketBase API if (url.origin !== self.location.origin && !url.href.startsWith(POCKETBASE_URL)) return; // Which build is live: always the network, never cached (UpdateWatcher) if (url.pathname === '/version.json') return; // PocketBase API - Network First if (url.href.startsWith(POCKETBASE_URL)) { event.respondWith(networkFirst(event.request, API_CACHE)); return; } // Map tiles - Stale While Revalidate if (url.hostname.includes('tile.openstreetmap.org') || url.hostname.includes('openstreetmap.org')) { event.respondWith(staleWhileRevalidate(event.request, IMAGE_CACHE)); return; } // Leaflet CDN assets - Cache First if (url.hostname.includes('cdnjs.cloudflare.com')) { event.respondWith(cacheFirst(event.request, STATIC_CACHE)); return; } // Static assets - Cache First const isStatic = STATIC_EXTENSIONS.some((ext) => url.pathname.endsWith(ext)); if (isStatic) { event.respondWith(cacheFirst(event.request, STATIC_CACHE)); return; } // Navigation requests (HTML pages) - Stale While Revalidate so // repeat visits render the cached HTML instantly while we fetch // a fresh copy in the background. Previously this used // networkFirst, which forced a full network round-trip on every // tab switch — the biggest contributor to "feels sluggish on PC" // alongside the per-page loading skeleton. if (event.request.mode === 'navigate') { event.respondWith(staleWhileRevalidate(event.request, STATIC_CACHE)); return; } // Everything else - Stale While Revalidate event.respondWith(staleWhileRevalidate(event.request, STATIC_CACHE)); }); async function cacheFirst(request, cacheName) { const cached = await caches.match(request); if (cached) return cached; try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(cacheName); cache.put(request, response.clone()); } return response; } catch { return new Response('Offline', { status: 503, statusText: 'Offline' }); } } async function networkFirst(request, cacheName, fallbackPath) { try { const response = await fetch(request); if (response.ok) { const cache = await caches.open(cacheName); cache.put(request, response.clone()); } return response; } catch { const cached = await caches.match(request); if (cached) return cached; if (fallbackPath) { const fallback = await caches.match(fallbackPath); if (fallback) return fallback; } return new Response('Offline', { status: 503, statusText: 'Offline' }); } } async function staleWhileRevalidate(request, cacheName) { const cache = await caches.open(cacheName); const cached = await cache.match(request); const fetchPromise = fetch(request) .then((response) => { if (response.ok) { cache.put(request, response.clone()); } return response; }) .catch(() => cached); return cached || fetchPromise; } // Background sync for offline queue (future enhancement) self.addEventListener('sync', (event) => { if (event.tag === 'sync-collections') { event.waitUntil(syncPendingData()); } }); async function syncPendingData() { // Placeholder for future offline queue sync console.log('[SW] Sync event triggered'); }