File size: 3,215 Bytes
284e818
 
 
 
 
 
cd2d39f
284e818
 
 
 
 
 
71c08ae
284e818
 
 
 
ed70eb7
 
 
 
 
 
 
284e818
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11e4991
284e818
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/* 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.
});