PageSourceSearch

https://kometri.com/sw.js

js kometri.com collected 2026-10-02 01:35:49 UTC 2,942 bytes, 79 lines download raw bytes

1// Kometri service worker.
2//
3// v1 scope: PWA installability + push notification reception.
4// Deliberately NO caching (no fetch handler) — adding caching naively is
5// the easiest way to ship stale JS/CSS post-deploy. When we want offline
6// support later, plan:
7//   - HTML: network-first (always get latest app shell)
8//   - JS/CSS hashed assets: cache-first (immutable per-deploy)
9//   - API: network-only (do NOT cache user data)
10//   - manifest.json, sw.js itself: network-only
11//
12// Push payload contract (sent from backend/app/services/push_service.py
13// when that ships):
14//   {
15//     "title": "🔥 Day 5's drop is ready",
16//     "body":  "Today's theme: café Italian — 3 min",
17//     "url":   "/discover",       // optional, defaults to /dashboard
18//     "tag":   "daily-drop",      // optional, prevents stacking duplicates
19//     "icon":  "/logo-512.svg",   // optional, defaults below
20//     "badge": "/logo-512.svg"    // optional, defaults below
21//   }
22
23self.addEventListener('install', () => {
24  self.skipWaiting()
25})
26
27self.addEventListener('activate', (event) => {
28  event.waitUntil(self.clients.claim())
29})
30
31self.addEventListener('push', (event) => {
32  // Defensive parse — if backend sends malformed JSON or a plain string
33  // we still want SOMETHING to show, not a silent drop.
34  let payload = {}
35  if (event.data) {
36    try {
37      payload = event.data.json()
38    } catch (e) {
39      payload = { title: 'Kometri', body: event.data.text() || '' }
40    }
41  }
42
43  const title = payload.title || 'Kometri'
44  const options = {
45    body: payload.body || '',
46    icon: payload.icon || '/logo-512.svg',
47    badge: payload.badge || '/logo-512.svg',
48    tag: payload.tag,                          // dedupes — new push with same tag replaces previous
49    renotify: !!payload.tag,                   // ring/buzz even if same tag (intentional re-alert)
50    data: { url: payload.url || '/dashboard' },
51    // requireInteraction: false (default) — fades after a few seconds.
52    // Set to true via payload for "streak ends in 4 hours" urgency notifs.
53    requireInteraction: payload.requireInteraction === true,
54  }
55
56  event.waitUntil(self.registration.showNotification(title, options))
57})
58
59self.addEventListener('notificationclick', (event) => {
60  event.notification.close()
61  const targetUrl = (event.notification.data && event.notification.data.url) || '/dashboard'
62
63  event.waitUntil(
64    self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
65      // If a Kometri tab is already open, focus it and navigate.
66      for (const client of clientList) {
67        if (client.url.includes(self.location.origin)) {
68          client.focus()
69          if ('navigate' in client) {
70            return client.navigate(targetUrl)
71          }
72          return
73        }
74      }
75      // Otherwise open a fresh tab at the target URL.
76      return self.clients.openWindow(targetUrl)
77    })
78  )
79})

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.