/* Service worker: makes the map installable and usable offline. * - App navigations are network-first (fresh prices when online), falling back * to the last cached page when offline. * - The app shell, icons and CDN libraries are cached so the app boots offline. * - Map tiles are cached with a bounded LRU-ish cap for offline revisits. */ const VERSION = "v15"; const SHELL = "shell-" + VERSION; const RUNTIME = "runtime-" + VERSION; const TILES = "tiles-" + VERSION; const TILE_LIMIT = 300; const APP_SHELL = [ "/", "/index.html", "/styles.css", "/app.js", "/manifest.webmanifest", "/icon-192.png", "/icon-512.png", ]; self.addEventListener("install", (event) => { // Pre-cache the shell but don't auto-activate: the page shows an update prompt // and tells us to skip waiting, so prices/assets never change under the user. event.waitUntil(caches.open(SHELL).then((c) => c.addAll(APP_SHELL))); }); self.addEventListener("message", (event) => { if (event.data && event.data.type === "SKIP_WAITING") self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((k) => ![SHELL, RUNTIME, TILES].includes(k)).map((k) => caches.delete(k)) ) ).then(() => self.clients.claim()) ); }); async function trimCache(name, max) { const cache = await caches.open(name); const keys = await cache.keys(); for (let i = 0; i < keys.length - max; i++) await cache.delete(keys[i]); } function cacheable(res) { return res && (res.ok || res.type === "opaque"); } self.addEventListener("fetch", (event) => { const req = event.request; if (req.method !== "GET") return; const url = new URL(req.url); // App navigation: fresh page online, cached shell offline. if (req.mode === "navigate") { event.respondWith( fetch(req) .then((res) => { const copy = res.clone(); caches.open(SHELL).then((c) => c.put("/index.html", copy)); return res; }) .catch(() => caches.match("/index.html").then((r) => r || caches.match("/"))) ); return; } // Map tiles: network-first, bounded cache for offline revisits. if (url.hostname === "data.geopf.fr") { event.respondWith( fetch(req) .then((res) => { const copy = res.clone(); caches.open(TILES).then((c) => { c.put(req, copy); trimCache(TILES, TILE_LIMIT); }); return res; }) .catch(() => caches.match(req)) ); return; } // Same-origin assets + CDN libraries: cache-first, refresh in background. if (url.origin === self.location.origin || url.hostname === "unpkg.com") { event.respondWith( caches.match(req).then((cached) => { const network = fetch(req) .then((res) => { if (cacheable(res)) { const copy = res.clone(); caches.open(RUNTIME).then((c) => c.put(req, copy)); } return res; }) .catch(() => cached); return cached || network; }) ); } // Anything else (e.g. Nominatim geocoding) goes straight to the network. });