1// Minimal service worker for installability + an offline fallback for the app shell. 2// Network-first for navigations (so chat/data stay fresh), cache-first for static icons. 3const CACHE = "sd2-v4"; 4const SHELL = ["/icon-192.png", "/ai-mask.png"]; 5 6self.addEventListener("install", (e) => { 7 e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).then(() => self.skipWaiting())); 8}); 9 10self.addEventListener("activate", (e) => { 11 e.waitUntil( 12 caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim()), 13 ); 14}); 15 16self.addEventListener("fetch", (e) => { 17 const req = e.request; 18 if (req.method !== "GET") return; 19 const url = new URL(req.url); 20 // Never cache API or auth â always go to network. 21 if (url.pathname.includes("/api/")) return; 22 23 if (req.mode === "navigate") { 24 // Network-first; keep a copy of the LAST GOOD shell at runtime so a flaky 25 // network / post-crash reload gets the app back instead of the browser's 26 // "This page couldn't load" (we no longer pre-cache "/" â it masked deploys). 27 e.respondWith( 28 fetch(req) 29 .then((res) => { 30 if (res.ok) { 31 const copy = res.clone(); 32 caches.open(CACHE).then((c) => c.put("/", copy)); 33 } 34 return res; 35 }) 36 .catch(() => caches.match("/").then((r) => r || Response.error())), 37 ); 38 return; 39 } 40 e.respondWith( 41 caches.match(req).then((cached) => cached || fetch(req).then((res) => { 42 if (res.ok && (url.pathname.endsWith(".png") || url.pathname.endsWith(".svg"))) { 43 const copy = res.clone(); 44 caches.open(CACHE).then((c) => c.put(req, copy)); 45 } 46 return res; 47 })), 48 ); 49}); 50 51// ââ Web Push: à¹à¸à¹à¸à¹à¸à¸·à¸à¸à¸ªà¸à¸²à¸à¸°à¸à¸²à¸£à¸à¸à¸ (admin à¹à¸à¸¥à¸µà¹à¸¢à¸à¸ªà¸à¸²à¸à¸° â server ยิภpush มาà¸à¸µà¹à¸à¸µà¹) ââ 52self.addEventListener("push", (e) => { 53 let d = {}; 54 try { d = e.data ? e.data.json() : {}; } catch { d = { body: e.data && e.data.text() }; } 55 e.waitUntil(self.registration.showNotification(d.title || "SiamDive", { 56 body: d.body || "", 57 icon: "/icon-192.png", 58 badge: "/icon-192.png", 59 tag: d.tag || "siamdive-booking", // à¹à¸à¹à¸à¹à¸à¸·à¸à¸à¸ªà¸à¸²à¸à¸°à¹à¸à¸´à¸¡à¸à¹à¸³ â à¹à¸à¸à¸à¸µà¹à¸à¸±à¸à¹à¸à¹à¸² à¹à¸¡à¹à¸à¸à¸à¸à¹à¸à¸ 60 data: { url: d.url || "/" }, 61 })); 62}); 63 64self.addEventListener("notificationclick", (e) => { 65 e.notification.close(); 66 const url = (e.notification.data && e.notification.data.url) || "/"; 67 e.waitUntil(self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((list) => { 68 for (const c of list) { if (c.url.includes(url.split("?")[0]) && "focus" in c) return c.focus(); } 69 return self.clients.openWindow(url); 70 })); 71});
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.