/* every.channel PWA service worker * * Goal: cache the app shell so it can be installed and load offline. * Do not interfere with media fetching/streaming: always network-pass-through * for non-GET requests and for large binary media responses. */ const CACHE_NAME = "every.channel-shell-v1"; const SHELL = [ "./", "./index.html", "./style.css", "./manifest.webmanifest", "./icons/icon-192.png", "./icons/icon-512.png", "./icons/apple-touch-icon.png", ]; self.addEventListener("install", (event) => { event.waitUntil( caches .open(CACHE_NAME) .then((cache) => cache.addAll(SHELL)) .then(() => self.skipWaiting()) ); }); self.addEventListener("activate", (event) => { event.waitUntil( caches .keys() .then((keys) => Promise.all( keys.map((key) => { if (key !== CACHE_NAME) return caches.delete(key); return Promise.resolve(); }) ) ) .then(() => self.clients.claim()) ); }); function isNavigationRequest(request) { return request.mode === "navigate"; } function isMediaRequest(request) { const url = new URL(request.url); const path = url.pathname.toLowerCase(); return ( path.endsWith(".m3u8") || path.endsWith(".m4s") || path.endsWith(".mp4") || path.endsWith(".ts") ); } self.addEventListener("fetch", (event) => { const { request } = event; if (request.method !== "GET") return; // Don't cache/modify streaming media requests. if (isMediaRequest(request)) { event.respondWith(fetch(request)); return; } // For navigations, prefer network but fall back to cached shell. if (isNavigationRequest(request)) { event.respondWith( fetch(request).catch(() => caches.match("./index.html").then((r) => r || Response.error())) ); return; } // Cache-first for same-origin static assets; network fallback. const url = new URL(request.url); if (url.origin === self.location.origin) { event.respondWith( caches.match(request).then((cached) => { if (cached) return cached; return fetch(request) .then((resp) => { // Avoid caching huge binary responses. const len = resp.headers.get("content-length"); const tooBig = len && Number(len) > 5_000_000; if (resp.ok && !tooBig) { const clone = resp.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)).catch(() => {}); } return resp; }) .catch(() => cached || Response.error()); }) ); return; } // Default: network. event.respondWith(fetch(request)); });