PageSourceSearch

https://offthepress.com/sw.js

js offthepress.com collected 2026-09-24 09:39:14 UTC 4,536 bytes, 98 lines download raw bytes

1// Off The Press service worker (2026-08-31).
2//
3// PURPOSE IS NARROW ON PURPOSE. This worker exists for two reasons only:
4//   1. Chromium's install-prompt algorithm still wants a fetch handler present
5//      (the *menu* install requirement was dropped in Chrome 108 mobile / 112
6//      desktop, but the prompt heuristic was not), so `beforeinstallprompt`
7//      fires reliably for the home-screen banner in index.html.
8//   2. Give an installed reader a branded page instead of the browser's dinosaur
9//      when they open the icon with no signal.
10//
11// IT CACHES NO NEWS CONTENT, EVER. This is a news site with two known staleness
12// windows already (the ~20min post-deploy origin lag, and unversioned main.js on
13// max-age=300). A caching service worker would be a third and by far the worst,
14// because it outlives a deploy and a hard refresh. So:
15//
16//   * The fetch handler returns early for anything that is not a top-level
17//     navigation. It never calls respondWith() for subresources, which means
18//     feed.json, main.js, style.css, px.js, Ezoic, Connatix and Hotjar are not
19//     intercepted at all -- not cached, not proxied, not delayed.
20//   * Navigations are network-FIRST with no cache write. A reader who is online
21//     always gets the live page. The cached copy is only ever reached when
22//     fetch() itself rejects, i.e. the network is genuinely gone.
23//
24// The only two things in the cache are /offline and the 192px icon it
25// draws, both of which are static and change only on an explicit deploy.
26//
27// KILL SWITCH. Service workers outlive the page that registered them, so
28// removing the <script> that registers this does NOT uninstall it from readers
29// who already have it. To retire it, REPLACE this file's body with:
30//
31//     self.addEventListener('install', () => self.skipWaiting());
32//     self.addEventListener('activate', (e) => e.waitUntil(
33//       self.registration.unregister().then(() => self.clients.claim())));
34//
35// and deploy that. Every existing reader unregisters on their next visit.
36// Do not simply delete the file -- /sw.js would then 404 and the old worker
37// stays installed and in charge. _worker.js serves this path `no-cache` so the
38// replacement is picked up on the next navigation rather than up to a day later.
39
40const CACHE = 'otp-offline-v1';
41// '/offline', NOT '/offline.html'. Cloudflare Pages serves offline.html at the
42// extensionless path and 308-redirects the .html form; precaching the redirect
43// would leave a redirected response in the cache that caches.match() cannot
44// reliably return, so the fallback would silently never appear.
45const OFFLINE_URL = '/offline';
46const PRECACHE = [OFFLINE_URL, '/icons/otp-192.png'];
47
48self.addEventListener('install', (event) => {
49  event.waitUntil(
50    caches
51      .open(CACHE)
52      .then((cache) => cache.addAll(PRECACHE))
53      // Never let a precache failure wedge the install: a worker with an empty
54      // cache still satisfies the install criteria, and the fetch handler below
55      // degrades to a plain network error exactly as if it were not here.
56      .catch(() => {})
57      .then(() => self.skipWaiting())
58  );
59});
60
61self.addEventListener('activate', (event) => {
62  event.waitUntil(
63    caches
64      .keys()
65      .then((keys) =>
66        Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
67      )
68      .catch(() => {})
69      .then(() => self.clients.claim())
70  );
71});
72
73self.addEventListener('fetch', (event) => {
74  const req = event.request;
75
76  // Everything that is not a top-level navigation is left completely alone.
77  // Returning without calling respondWith() hands the request back to the
78  // browser untouched -- this is the line that keeps ads, analytics and
79  // feed.json out of the worker's path entirely.
80  if (req.mode !== 'navigate') return;
81
82  // Range requests are navigations only in exotic cases, but a partial response
83  // must never be answered from the offline page.
84  if (req.headers.has('range')) return;
85
86  event.respondWith(
87    // Network first, always. No cache write on success: nothing about a news
88    // page is worth persisting past this request.
89    fetch(req).catch(async () => {
90      const cached = await caches.match(OFFLINE_URL);
91      if (cached) return cached;
92      // Cache evicted (iOS clears PWA storage after ~7 days unused) and still
93      // offline: fall through to the browser's own error rather than an
94      // undefined response, which would surface as a confusing failure.
95      return Response.error();
96    })
97  );
98});

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.