// ponytail: bump CACHE_NAME on each deploy, or stale HTML breaks assets. // Network-first for navigation (index.html) keeps hashed asset refs fresh. const CACHE_NAME = 'dumpster-v3'; self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((names) => Promise.all(names.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n))) ) ); self.clients.claim(); }); self.addEventListener('fetch', (event) => { if (event.request.method !== 'GET') return; const url = new URL(event.request.url); // Skip API + WS if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/ws')) return; // Skip cross-origin requests (e.g. Giphy, LiveKit) if (url.origin !== self.location.origin) return; // Network-first for navigation requests (index.html) — always fetch fresh HTML // so hashed asset references stay current after deploys. if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request) .then((response) => { const clone = response.clone(); caches.open(CACHE_NAME).then((c) => c.put(event.request, clone)); return response; }) .catch(() => caches.match('/index.html')) ); return; } // Cache-first for static assets (JS, CSS, images, fonts) event.respondWith( caches.match(event.request).then((cached) => { const fetchPromise = fetch(event.request).then((response) => { if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then((c) => c.put(event.request, clone)); } return response; }).catch(() => cached); return cached || fetchPromise; }) ); }); self.addEventListener('push', (event) => { if (!event.data) return; const data = event.data.json(); event.waitUntil( self.registration.showNotification(data.title || 'Dumpster', { body: data.body, icon: '/icons/icon-192.png', badge: '/icons/icon-192.png', vibrate: [100, 50, 100], data: { url: data.url || '/' }, }) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((list) => { for (const client of list) { if (client.url.includes(event.notification.data.url) && 'focus' in client) { return client.focus(); } } return clients.openWindow(event.notification.data.url); }) ); });