1// public/sw.js â AUTO-UPDATE + SAFE CACHE SERVICE WORKER 2 3const CACHE_NAME = "fastfeast-v2.1"; // ð¹ Increment version whenever you deploy 4const APP_VERSION = "2.1"; // ð¹ Version log 5 6/** 7 * â Utility: Cache only valid HTTP(S) GET requests 8 */ 9function canCache(request, response) { 10 try { 11 const url = new URL(request.url); 12 return ( 13 request.method === "GET" && 14 (url.protocol === "http:" || url.protocol === "https:") && 15 response && 16 response.status === 200 && 17 response.type !== "opaqueredirect" 18 ); 19 } catch { 20 return false; 21 } 22} 23 24/** 25 * â INSTALL: Precache app shell & skip waiting immediately 26 */ 27self.addEventListener("install", (event) => { 28 event.waitUntil( 29 caches 30 .open(CACHE_NAME) 31 .then((cache) => 32 cache.addAll([ 33 "/", 34 "/manifest.json", 35 "/icons/icon-192x192.png", 36 "/icons/icon-512x512.png", 37 ]) 38 ) 39 .then(() => self.skipWaiting()) 40 .catch((err) => console.error("â Cache preload failed:", err)) 41 ); 42}); 43 44/** 45 * â ACTIVATE: Clear all old caches + claim clients 46 */ 47self.addEventListener("activate", (event) => { 48 event.waitUntil( 49 caches.keys().then((keys) => 50 Promise.all( 51 keys.map((key) => { 52 if (key !== CACHE_NAME && key.startsWith("fastfeast-v")) { 53 return caches.delete(key); 54 } 55 }) 56 ) 57 ) 58 ); 59 self.clients.claim(); 60}); 61 62/** 63 * â FETCH: Intelligent caching strategies 64 */ 65self.addEventListener("fetch", (event) => { 66 const req = event.request; 67 68 // Ignore non-http(s) URLs (like extensions) 69 try { 70 const url = new URL(req.url); 71 if (!["http:", "https:"].includes(url.protocol)) return; 72 } catch { 73 return; 74 } 75 76 // HTML navigation â Network-first, fallback to cache 77 if (req.mode === "navigate" || req.destination === "document") { 78 event.respondWith( 79 fetch(req) 80 .then((res) => { 81 const clone = res.clone(); 82 if (canCache(req, clone)) 83 caches.open(CACHE_NAME).then((c) => c.put(req, clone)); 84 return res; 85 }) 86 .catch(() => caches.match(req).then((r) => r || caches.match("/"))) 87 ); 88 return; 89 } 90 91 // Static files & images â Cache-first, update in background 92 if ( 93 req.url.includes("/_next/static/") || 94 req.url.includes("/icons/") || 95 req.destination === "image" || 96 req.destination === "audio" 97 ) { 98 event.respondWith( 99 caches.match(req).then((cached) => { 100 if (cached) { 101 fetch(req) 102 .then((netRes) => { 103 const clone = netRes.clone(); 104 if (canCache(req, clone)) 105 caches.open(CACHE_NAME).then((c) => c.put(req, clone)); 106 }) 107 .catch(() => {}); // Background update 108 return cached; 109 } 110 return fetch(req) 111 .then((res) => { 112 const clone = res.clone(); 113 if (canCache(req, clone)) 114 caches.open(CACHE_NAME).then((c) => c.put(req, clone)); 115 return res; 116 }) 117 .catch(() => 118 req.destination === "image" 119 ? new Response( 120 `<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#f3f4f6"/><text x="50%" y="50%" font-family="Arial" font-size="12" fill="#9ca3af" text-anchor="middle" dy=".3em">Image</text></svg>`, 121 { headers: { "Content-Type": "image/svg+xml" } } 122 ) 123 : new Response("Network error", { status: 408 }) 124 ); 125 }) 126 ); 127 return; 128 } 129 130 // API calls â Network-only (no caching) 131 if (req.url.includes("/api/")) { 132 event.respondWith(fetch(req)); 133 return; 134 } 135 136 // Default: Cache-first fallback 137 event.respondWith( 138 caches.match(req).then((cached) => { 139 if (cached) return cached; 140 return fetch(req).then((res) => { 141 const clone = res.clone(); 142 if (canCache(req, clone)) 143 caches.open(CACHE_NAME).then((c) => c.put(req, clone)); 144 return res; 145 }); 146 }) 147 ); 148}); 149 150/** 151 * â MESSAGE HANDLER: Manual update commands 152 */ 153self.addEventListener("message", (event) => { 154 const data = event.data; 155 if (!data) return; 156 157 if (data.type === "SKIP_WAITING") {
158 self.skipWaiting(); 159 } 160 161 if (data.type === "GET_VERSION") { 162 event.ports?.[0]?.postMessage({ version: APP_VERSION }); 163 } 164}); 165 166/** 167 * â BACKGROUND SYNC (future offline features) 168 */ 169self.addEventListener("sync", (event) => { 170 if (event.tag === "background-sync") { 171 event.waitUntil(doBackgroundSync()); 172 } 173}); 174 175async function doBackgroundSync() { 176 // future offline upload logic here 177}
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.