PageSourceSearch

https://www.repetio.ai/sw.js

js repetio.ai collected 2026-10-05 14:54:20 UTC 3,876 bytes, 114 lines download raw bytes

1/**
2 * Repetio's service worker. It exists for web push and nothing else.
3 *
4 * It used to also make the site an installable offline app (network-first
5 * navigations with an `/offline` fallback, cache-first `/_next/static/*`). That
6 * was removed along with the web app manifest, so this worker now caches
7 * nothing and never answers a `fetch`: every request goes straight to the
8 * network as if no worker were installed.
9 *
10 * `activate` still deletes every `repetio-*` cache, which clears what the old
11 * caching worker left behind on devices that had it.
12 *
13 * `next.config.ts` serves this file with `Cache-Control: no-store`, so a fix to
14 * it reaches every browser on its next update check. `/reset` (public, works
15 * signed out) unregisters every worker and deletes every cache, and needs
16 * nothing from this file to work.
17 */
18
19self.addEventListener("install", () => {
20  // Take over immediately. Waiting for every tab to close means a fix can sit
21  // unapplied for days on the machine of the person who never closes tabs.
22  self.skipWaiting();
23});
24
25self.addEventListener("activate", (event) => {
26  event.waitUntil(
27    (async () => {
28      const names = await caches.keys();
29      await Promise.all(
30        names.filter((name) => name.startsWith("repetio-")).map((name) => caches.delete(name))
31      );
32      await self.clients.claim();
33    })()
34  );
35});
36
37/**
38 * The manual kill switch, reachable from `/reset`. Clearing from inside the
39 * worker as well as from the page covers the case where the page's `caches`
40 * access is blocked.
41 */
42self.addEventListener("message", (event) => {
43  const type = event.data && event.data.type;
44
45  if (type === "repetio-sw-reset") {
46    event.waitUntil(
47      (async () => {
48        const names = await caches.keys();
49        await Promise.all(names.map((name) => caches.delete(name)));
50        await self.registration.unregister();
51      })()
52    );
53  }
54});
55
56/**
57 * ## Push
58 *
59 * The payload is JSON written by `apps/api/src/modules/notifications/push/push-sender.ts`.
60 * A push event that shows no notification is a spec violation in every browser
61 * that implements `userVisibleOnly` (which is all of them) and will eventually
62 * cost the site its push permission, so the catch below still shows something.
63 *
64 * `tag` collapses repeats: a reminder redelivered by the push service replaces
65 * the one already on the lock screen instead of stacking a second copy.
66 */
67self.addEventListener("push", (event) => {
68  let payload = {};
69  try {
70    payload = event.data ? event.data.json() : {};
71  } catch {
72    payload = {};
73  }
74
75  const title = payload.title || "Repetio";
76  const options = {
77    body: payload.body || "You have cards waiting.",
78    icon: "/icons/icon-192.png",
79    badge: "/icons/icon-192.png",
80    tag: payload.tag || "repetio-reminder",
81    // Replace rather than re-alert: a second buzz for the same reminder is how
82    // a notification channel gets turned off.
83    renotify: false,
84    data: { url: payload.url || "/dashboard" },
85  };
86
87  event.waitUntil(self.registration.showNotification(title, options));
88});
89
90self.addEventListener("notificationclick", (event) => {
91  event.notification.close();
92  const target = (event.notification.data && event.notification.data.url) || "/dashboard";
93
94  event.waitUntil(
95    (async () => {
96      const clientList = await self.clients.matchAll({
97        type: "window",
98        includeUncontrolled: true,
99      });
100
101      // Reuse an open window if there is one. Opening a second tab for a
102      // reminder is disorienting, and on a phone it looks like a bug.
103      for (const client of clientList) {
104        if (new URL(client.url).origin === self.location.origin) {
105          await client.focus();
106          if ("navigate" in client) await client.navigate(target);
107          return;
108        }
109      }
110
111      await self.clients.openWindow(target);
112    })()
113  );
114});

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.