PageSourceSearch

https://boostmebitch.vercel.app/sw.js

js boostmebitch.vercel.app collected 2026-10-03 06:18:36 UTC 8,289 bytes, 173 lines download raw bytes

1// Generated by app/sw.js/route.ts — do not edit a copy of this by hand.
2const VERSION = "83f6c25d1354";
3const STATIC = 'bmb-sw-static-' + VERSION;
4const PAGES = 'bmb-sw-pages-' + VERSION;
5const MINE = [STATIC, PAGES];
6
7// The last resort, and it should be unreachable: install precaches '/'. Kept
8// because the alternative at this point is rejecting, and a navigation that
9// rejects is the error screen this whole branch exists to avoid.
10const OFFLINE_HTML = '<!doctype html><meta charset=utf-8>'
11  + '<meta name=viewport content="width=device-width,initial-scale=1">'
12  + '<title>Offline</title>'
13  + '<body style="margin:0;display:grid;place-items:center;height:100vh;'
14  + 'font:16px system-ui;background:#0b0b0d;color:#f4efe6">'
15  + '<p>No connection, and this page was not saved.</p>';
16
17self.addEventListener('install', (event) => {
18  // TWO documents are precached, and no more.
19  //
20  // Everything else still fills from real traffic. This exists because the
21  // FIRST load of a page is fetched BEFORE this worker controls it, so it never
22  // passes through the fetch handler and never enters PAGES. A visitor who
23  // installs the app and then loses signal without loading a second time had no
24  // shell at all — measured on an iPhone, and it did not degrade, it produced
25  // "FetchEvent.respondWith received an error: TypeError: Load failed".
26  //
27  // It does not weaken network-first. '/' is still fetched fresh on every load
28  // that has a connection; this copy is only ever reached when the fetch
29  // rejects. And it cannot go stale across a deploy: PAGES carries the build id,
30  // so a new worker precaches its own '/' and activate() deletes the old one.
31  event.waitUntil((async () => {
32    // '/downloads' is here for its own reason, not for symmetry: it is the one
33    // route whose entire PURPOSE is to work without a network, and a dock tab
34    // that does nothing is the worst way to find that out. A client-side route
35    // change fetches an RSC payload rather than a document, so when that fetch
36    // fails the router falls back to a real navigation — and that navigation
37    // has to land on the route's OWN document. Without this it landed on the
38    // shell, which is the home page's HTML under a /downloads URL.
39    //
40    // Reported from an iPhone: the tab did not open in airplane mode and was
41    // fine on wifi. It does not reproduce in headless Chrome, which prefetches
42    // every dock <Link> on the first load and so always has what the tap needs.
43    const staticCache = await caches.open(STATIC);
44    for (const path of ['/', '/downloads']) {
45      try {
46        const res = await fetch(path);
47        if (!res.ok) continue;
48        const html = await res.clone().text();
49        const cache = await caches.open(PAGES);
50        await cache.put(path, res);
51        // A precached page whose script deps are not cached breaks on the
52        // next deploy: activate deletes the old STATIC cache while the new
53        // page references new hashes. Collect same-origin /_next/static/
54        // URLs and cache them now.
55        const re = /(?:src|href)=["']([^"']*[/]_next[/]static[/][^"']+)["']/g;
56        const deps = [...html.matchAll(re)].map(m => m[1]);
57        await Promise.all(deps.map(u => staticCache.add(u).catch(() => {})));
58      } catch (e) {
59        // An install must never fail on this. Without it the app simply has no
60        // offline shell until the next load, which is where this started.
61      }
62    }
63    await self.skipWaiting();
64  })());
65});
66
67self.addEventListener('activate', (event) => {
68  event.waitUntil((async () => {
69    // Delete every cache this worker owns from an older build. The prefix test
70    // is deliberately narrow: the DOWNLOADS buckets (bmb-downloads-*) are the
71    // user's own files and must never be touched here.
72    const names = await caches.keys();
73    await Promise.all(
74      names
75        .filter((n) => n.startsWith('bmb-sw-') && !MINE.includes(n))
76        .map((n) => caches.delete(n)),
77    );
78    await self.clients.claim();
79  })());
80});
81
82/** Four things never enter a cache, and each would be a real bug. */
83function cacheable(request, url) {
84  // A cached POST is not a thing.
85  if (request.method !== 'GET') return false;
86  // Enclosure audio, relay traffic, third-party artwork. Not ours to hold, and
87  // downloads have their own buckets with their own lifetime.
88  if (url.origin !== self.location.origin) return false;
89  // A cached /api/feed serves a stale episode list, and /api/live-status would
90  // report a finished show as live.
91  if (url.pathname.startsWith('/api/')) return false;
92  return true;
93}
94
95async function putIfOk(cacheName, request, response) {
96  // A cached 404 or 502 outlives the outage that produced it. An opaque or
97  // cross-origin response cannot be read back usefully either.
98  if (!response || !response.ok || response.type !== 'basic') return;
99  if ((response.headers.get('cache-control') || '').includes('no-store')) return;
100  // A 206 is ok and Cache.put REJECTS it — and every same-origin media element
101  // asks with a Range header, the boost sound included. A partial body is not
102  // the resource anyway.
103  if (response.status === 206 || request.headers.has('range')) return;
104  // THE CACHE WRITE MUST NEVER FAIL THE RESPONSE. The caller awaits this before
105  // returning a response the network already delivered, so a throw here (a full
106  // quota once downloads have used it, a private-mode refusal) turned a good
107  // answer into a rejection: a chunk failed to load while online.
108  try {
109    const cache = await caches.open(cacheName);
110    await cache.put(request, response.clone());
111  } catch (e) {
112    // Not caching is always a safe answer; failing the fetch is not.
113  }
114}
115
116self.addEventListener('fetch', (event) => {
117  const request = event.request;
118  const url = new URL(request.url);
119  if (!cacheable(request, url)) return; // straight to the network, as before
120
121  // /_next/static/* is CONTENT-HASHED, so a given URL's bytes never change and
122  // a cached copy cannot go stale. Cache-first here is what makes the rule
123  // below work offline: the last document's chunks are still present.
124  if (url.pathname.startsWith('/_next/static/')) {
125    event.respondWith((async () => {
126      const hit = await caches.match(request);
127      if (hit) return hit;
128      const res = await fetch(request);
129      await putIfOk(STATIC, request, res);
130      return res;
131    })());
132    return;
133  }
134
135  // EVERYTHING ELSE IS NETWORK-FIRST, and the cache is used ONLY when the fetch
136  // rejects. A reader with a connection is therefore never served a stale
137  // document, so a stale document can never reference dead chunk hashes while
138  // online — which is the failure the old public/sw.js comment warned about.
139  event.respondWith((async () => {
140    try {
141      const res = await fetch(request);
142      await putIfOk(PAGES, request, res);
143      return res;
144    } catch (err) {
145      const hit = await caches.match(request);
146      if (hit) return hit;
147
148      // A NAVIGATION MUST NEVER REJECT HERE. Rejecting inside respondWith does
149      // not fall back to the browser's own offline page — it replaces it with
150      // "FetchEvent.respondWith received an error: TypeError: Load failed",
151      // which is a worse screen than having no service worker at all. So a
152      // navigation that missed its exact URL falls back to the shell: the app
153      // reads its own deep-link parameters off window.location on mount, so
154      // '/?podcast=…' served the shell still lands where the reader meant.
155      if (request.mode === 'navigate') {
156        const shell = await caches.match('/');
157        if (shell) return shell;
158        return new Response(OFFLINE_HTML, {
159          status: 503,
160          headers: { 'Content-Type': 'text/html; charset=utf-8' },
161        });
162      }
163
164      // A SUBRESOURCE is the opposite: rejecting is exactly what would happen
165      // with no worker installed, and it is what the app's own catch blocks are
166      // written against. Synthesising a Response here would turn a network
167      // failure into a non-ok ANSWER, and lib/downloads/downloads-cache.ts's
168      // fetchDoc reads the difference — it falls back to a downloaded copy only
169      // on a rejection.
170      throw err;
171    }
172  })());
173});

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.