const CACHE_NAME = 'gridtv-v2'; // Static assets cached during installation. const STATIC_ASSETS = [ '/', '/index.php', '/manifest.json', '/assets/fonts/fonts.css', '/assets/icon-192.png', '/assets/icon-512.png', '/assets/vendor/hls.min.js' ]; // Install: cache the static assets. self.addEventListener('install', e => { e.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(STATIC_ASSETS)) .then(() => self.skipWaiting()) ); }); // Activate: remove outdated caches. self.addEventListener('activate', e => { e.waitUntil( caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) ).then(() => self.clients.claim()) ); }); // Fetch: choose a caching strategy based on resource type. self.addEventListener('fetch', e => { const url = new URL(e.request.url); // EPG/M3U feeds, the GitHub API, and version.json stay network-only. if ( url.pathname.includes('xmltv') || url.pathname.includes('.m3u') || url.hostname === 'api.github.com' || url.pathname.includes('proxy.php') || url.pathname.endsWith('version.json') ) { return; } // Static assets use a cache-first strategy. if ( e.request.destination === 'style' || e.request.destination === 'script' || e.request.destination === 'font' || e.request.destination === 'image' || url.pathname.endsWith('.json') || url.pathname.endsWith('.css') || url.pathname.endsWith('.js') ) { e.respondWith( caches.match(e.request).then(cached => cached || fetch(e.request).then(res => { const clone = res.clone(); caches.open(CACHE_NAME).then(cache => cache.put(e.request, clone)); return res; })) ); return; } // PHP pages use network-first with cache fallback. e.respondWith( fetch(e.request) .then(res => { const clone = res.clone(); caches.open(CACHE_NAME).then(cache => cache.put(e.request, clone)); return res; }) .catch(() => caches.match(e.request)) ); }); self.addEventListener('notificationclick', e => { const url = e.notification?.data?.url || '/index.php'; e.notification.close(); e.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then(windowClients => { for (const client of windowClients) { if ('focus' in client) { client.navigate(url); return client.focus(); } } if (clients.openWindow) return clients.openWindow(url); return undefined; }) ); });