PageSourceSearch

https://neuburger-hochzeitsmesse.de/lead-scanner-sw.js

js neuburger-hochzeitsmesse.de collected 2026-10-04 20:01:37 UTC 5,670 bytes, 128 lines download raw bytes

1/* Service Worker für den Lead-Scanner (Scope /lead-scanner/):
2 * App-Oberfläche und Skripte werden nach dem ersten Aufruf zwischengespeichert, damit die App
3 * am Messestand auch ohne Netz startet. Scans selbst puffert die App in IndexedDB.
4 *
5 * 22.09.2026: Kaltstart ohne Netz endete auf der Browser-Fehlerseite. Grund war zum einen, dass
6 * der Start der Hauptseite alle Caches löschte (siehe src/main.tsx), zum anderen, dass `install`
7 * nichts vorab holte: Hülle und Chunks landeten erst beim nächsten Durchlauf im Cache. Jetzt legt
8 * `install` die Hülle vorab ab, und die Seite meldet nach dem Laden ihre geladenen Skripte und
9 * Styles per `postMessage({type: "cache-assets", urls: [...]})` nach. Nach EINEM Online-Besuch ist
10 * damit alles für den Kaltstart da. */
11const CACHE = "hk-lead-scanner-v3";
12/** Wie lange ein Seitenaufruf auf das Netz wartet, bevor die Hülle aus dem Cache kommt. */
13const NAV_TIMEOUT_MS = 3000;
14const ZEITLIMIT = Symbol("zeitlimit");
15const SHELL = "/lead-scanner/__shell";
16
17// Wurzel des Scopes – daher kommt die App-Hülle (index.html der SPA)
18function shellRequestUrl() {
19  try {
20    return new URL("./", self.registration.scope).toString();
21  } catch {
22    return "/lead-scanner/";
23  }
24}
25
26self.addEventListener("install", (event) => {
27  event.waitUntil((async () => {
28    // Hülle vorab holen. Ein Fehler hier darf die Installation nicht scheitern lassen.
29    try {
30      const res = await fetch(shellRequestUrl(), { cache: "reload" });
31      if (res && res.ok) {
32        const cache = await caches.open(CACHE);
33        await cache.put(SHELL, res.clone());
34      }
35    } catch { /* offline installiert – die Hülle kommt beim ersten Seitenaufruf */ }
36    await self.skipWaiting();
37  })());
38});
39
40self.addEventListener("activate", (event) => {
41  event.waitUntil((async () => {
42    const keys = await caches.keys();
43    await Promise.all(keys.filter((k) => k.startsWith("hk-lead-scanner-") && k !== CACHE).map((k) => caches.delete(k)));
44    await self.clients.claim();
45  })());
46});
47
48/* Die Seite schickt nach dem Laden die Liste aller geladenen gleichnamigen (same-origin) Skripte
49 * und Styles. Einzeln ablegen statt `cache.addAll`, damit eine einzelne fehlschlagende URL nicht
50 * den ganzen Durchlauf verwirft. */
51self.addEventListener("message", (event) => {
52  const data = event.data;
53  if (!data || data.type !== "cache-assets" || !Array.isArray(data.urls)) return;
54  event.waitUntil((async () => {
55    const cache = await caches.open(CACHE);
56    for (const url of data.urls) {
57      try {
58        if (typeof url !== "string") continue;
59        const abs = new URL(url, self.location.origin);
60        if (abs.origin !== self.location.origin) continue;
61        if (await cache.match(abs.href)) continue;
62        const res = await fetch(abs.href, { credentials: "same-origin" });
63        if (res && res.ok) await cache.put(abs.href, res.clone());
64      } catch { /* diese eine URL überspringen */ }
65    }
66  })());
67});
68
69self.addEventListener("fetch", (event) => {
70  const req = event.request;
71  if (req.method !== "GET") return;
72  const url = new URL(req.url);
73  if (url.origin !== self.location.origin) return; // Supabase & Co. immer live
74
75  // Seitenaufrufe: Netz zuerst, aber höchstens NAV_TIMEOUT_MS lang. Am Messestand ist „Netz ohne
76  // Durchsatz“ häufiger als gar kein Netz: fetch() scheitert dann nicht, sondern hängt, und das
77  // Neuladen der Scanner-App blieb weiß. Nach dem Zeitlimit kommt die zuletzt gesehene Hülle aus
78  // dem Cache; die Netzantwort läuft im Hintergrund weiter und frischt den Cache auf, wenn sie
79  // noch eintrifft. Abgelegt wird nur eine erfolgreiche Antwort (fresh.ok) – eine 404- oder
80  // 500-Seite darf die funktionierende Hülle nicht überschreiben.
81  if (req.mode === "navigate") {
82    let ablage = Promise.resolve();
83    const netz = fetch(req).then((fresh) => {
84      if (fresh && fresh.ok) {
85        // Kopie sofort ziehen, bevor der Browser den Körper liest; das Ablegen wartet nicht
86        // auf die Antwort an die Seite.
87        const kopie = fresh.clone();
88        ablage = caches.open(CACHE).then((cache) => cache.put(SHELL, kopie)).catch(() => undefined);
89      }
90      return fresh;
91    });
92    // Den Hintergrund-Abruf samt Ablage am Leben halten, auch wenn die Antwort schon aus dem
93    // Cache kam.
94    event.waitUntil(netz.then(() => ablage, () => undefined));
95    event.respondWith((async () => {
96      const cache = await caches.open(CACHE);
97      const ausCache = async () => (await cache.match(SHELL)) || (await cache.match(req)) || null;
98      const zeitlimit = new Promise((resolve) => setTimeout(() => resolve(ZEITLIMIT), NAV_TIMEOUT_MS));
99      try {
100        const erstes = await Promise.race([netz, zeitlimit]);
101        if (erstes !== ZEITLIMIT) return erstes;
102        // Netz zu langsam: Hülle zeigen, falls vorhanden – sonst doch auf das Netz warten.
103        const huelle = await ausCache();
104        if (huelle) return huelle;
105        return await netz;
106      } catch {
107        return (await ausCache()) || Response.error();
108      }
109    })());
110    return;
111  }
112
113  // Gebaute Skripte/Styles sind mit Hash versehen: Cache zuerst, sonst Netz und merken
114  const cacheable = url.pathname.startsWith("/assets/") || /\.(js|css|png|svg|woff2?|jpg|webp|mp3|json)$/i.test(url.pathname);
115  if (!cacheable) return;
116  event.respondWith((async () => {
117    const cache = await caches.open(CACHE);
118    const hit = await cache.match(req);
119    if (hit) return hit;
120    try {
121      const fresh = await fetch(req);
122      if (fresh.ok) cache.put(req, fresh.clone());
123      return fresh;
124    } catch {
125      return Response.error();
126    }
127  })());
128});

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.