PageSourceSearch

https://webcade.fun/sw.js

js webcade.fun collected 2026-10-08 08:27:25 UTC 3,797 bytes, 100 lines download raw bytes

1// Webcade service worker — the minimum that makes the app installable (a PWA,
2// and therefore wrappable as the Saga/Seeker dApp-Store TWA) without ever
3// letting a stale response reach a money-relevant screen.
4//
5// The disciplines that matter here:
6//   /api/* is NEVER cached — the board, prizes, duels and wallet state must
7//   always be live. Versioned assets (?v=<deploy>) are immutable, so they are
8//   cache-first. Everything else is network-first, falling back to cache only
9//   when the device is actually offline. That keeps the SW an offline nicety,
10//   not a second source of truth that could serve yesterday's board.
11const CACHE = 'webcade-v1';
12const OFFLINE = '/offline.html';
13
14self.addEventListener('install', (e) => {
15  e.waitUntil(
16    caches.open(CACHE)
17      .then((c) => c.addAll([OFFLINE, '/icon-192.png']))
18      .then(() => self.skipWaiting())
19  );
20});
21
22self.addEventListener('activate', (e) => {
23  e.waitUntil(
24    caches.keys()
25      .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
26      .then(() => self.clients.claim())
27  );
28});
29
30self.addEventListener('fetch', (e) => {
31  const req = e.request;
32  if (req.method !== 'GET') return;
33  const url = new URL(req.url);
34  if (url.origin !== location.origin) return;      // CDN scripts pass straight through
35  if (url.pathname.startsWith('/api/')) return;    // never cache the live endpoints
36
37  // Immutable, deploy-stamped assets: serve from cache, fill it on first miss.
38  if (url.searchParams.has('v')) {
39    e.respondWith(caches.open(CACHE).then(async (c) => {
40      const hit = await c.match(req);
41      if (hit) return hit;
42      const res = await fetch(req);
43      if (res && res.ok) c.put(req, res.clone());
44      return res;
45    }));
46    return;
47  }
48
49  // Page loads: live first, offline shell as the fallback.
50  if (req.mode === 'navigate') {
51    e.respondWith(fetch(req).catch(() => caches.match(OFFLINE)));
52    return;
53  }
54
55  // Anything else: live, with whatever we cached as a last resort offline.
56  e.respondWith(fetch(req).catch(() => caches.match(req)));
57});
58
59// ------------------------------------------------------------- web push
60//
61// A nudge arrives here even when the app is closed. In the dApp Store TWA
62// (built with enableNotifications) these render as the APP's own notifications
63// — our icon, our name — rather than a browser's.
64//
65// Everything is defensive: a malformed payload must still produce a
66// notification rather than a swallowed error, because a silent push is
67// indistinguishable from a broken one.
68self.addEventListener('push', (e) => {
69  let d = {};
70  try { d = e.data ? e.data.json() : {}; } catch (_) { /* not JSON: fall through */ }
71  const title = d.title || 'WEBCADE';
72  e.waitUntil(self.registration.showNotification(title, {
73    body: d.body || 'Your arcade is calling.',
74    icon: '/icon-192.png',
75    badge: '/icon-192.png',
76    // A tag collapses repeats: two board nudges replace each other rather
77    // than stacking into a wall the player has to swipe away.
78    tag: d.tag || 'webcade',
79    renotify: true,
80    data: { url: d.url || '/arcade' },
81  }));
82});
83
84// Tapping a nudge should land on the thing it was about — and reuse an open
85// window rather than piling up tabs.
86self.addEventListener('notificationclick', (e) => {
87  e.notification.close();
88  const target = (e.notification.data && e.notification.data.url) || '/arcade';
89  e.waitUntil((async () => {
90    const all = await clients.matchAll({ type: 'window', includeUncontrolled: true });
91    for (const c of all) {
92      if (c.url.includes(location.origin)) {
93        await c.focus();
94        if ('navigate' in c) { try { await c.navigate(target); } catch (_) {} }
95        return;
96      }
97    }
98    await clients.openWindow(target);
99  })());
100});

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.