Spaces:
Sleeping
Sleeping
| /* 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. | |
| }); | |