1/* 2 * Cutout service worker - intentionally minimal and conservative. 3 * 4 * WHY THIS EXISTS: Chrome's installability criteria for a PWA (which is 5 * what a TWA/Android app wraps) require a registered service worker with 6 * a fetch handler. Without one, "Add to Home Screen" / the eventual 7 * Android app wrapper has a weaker installability signal. 8 * 9 * WHY IT DOES SO LITTLE: Cutout is not a content site that benefits from 10 * aggressive caching - every real interaction (removing a background, 11 * batch processing) is a live POST to /api/remove-bg that must always 12 * hit the real network, never a cache. Caching anything related to that 13 * endpoint, even by accident, would risk serving a stale/wrong image 14 * result. This service worker deliberately: 15 * - NEVER intercepts or caches anything under /api/ - those requests 16 * are passed straight through, untouched, every time 17 * - NEVER caches POST requests at all (the Cache API can only store 18 * GET responses anyway, but this is explicit about the intent) 19 * - Only caches a small, fixed "app shell" list (the page itself, the 20 * manifest, and the icons) using a network-first strategy, so a 21 * visitor with a live connection ALWAYS gets the current, live 22 * version - the cached copy is purely a fallback for the rare case 23 * of opening the app with genuinely no network connection at all 24 * - Uses a versioned cache name (CACHE_VERSION below) - bump this any 25 * time app/manifest.webmanifest or the icon set changes, so old 26 * clients don't keep serving a stale shell indefinitely 27 */ 28 29const CACHE_VERSION = 'cutout-shell-v1'; 30const SHELL_URLS = [ 31 '/', 32 '/app/manifest.webmanifest', 33 '/app/icons/icon-192.png', 34 '/app/icons/icon-512.png', 35]; 36 37self.addEventListener('install', (event) => { 38 event.waitUntil( 39 caches.open(CACHE_VERSION).then((cache) => cache.addAll(SHELL_URLS)) 40 ); 41 self.skipWaiting(); 42}); 43 44self.addEventListener('activate', (event) => { 45 event.waitUntil( 46 caches.keys().then((keys) => 47 Promise.all( 48 keys 49 .filter((key) => key.startsWith('cutout-shell-') && key !== CACHE_VERSION) 50 .map((key) => caches.delete(key)) 51 ) 52 ) 53 ); 54 self.clients.claim(); 55}); 56 57self.addEventListener('fetch', (event) => { 58 const { request } = event; 59 60 // Never touch anything that isn't a plain GET, and never touch the 61 // real backend API route under any circumstances - always go straight 62 // to the network for both, no caching logic applied at all. 63 if (request.method !== 'GET' || new URL(request.url).pathname.startsWith('/api/')) { 64 return; 65 } 66 67 // Network-first for everything else: always try the live network 68 // first so a connected visitor never sees stale content, and only 69 // fall back to the cached shell if the network request genuinely 70 // fails (offline, or the server is unreachable). 71 event.respondWith( 72 fetch(request) 73 .then((networkResponse) => { 74 // Opportunistically refresh the cached copy of shell URLs with 75 // whatever the network just returned, so the offline fallback 76 // stays reasonably current for next time - but only for the 77 // fixed shell list, never for arbitrary requests. 78 if (SHELL_URLS.includes(new URL(request.url).pathname)) { 79 const responseClone = networkResponse.clone(); 80 caches.open(CACHE_VERSION).then((cache) => cache.put(request, responseClone)); 81 } 82 return networkResponse; 83 }) 84 .catch(() => caches.match(request)) 85 ); 86});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.