PageSourceSearch

https://onconex.ai/sw.js

js onconex.ai collected 2026-10-05 12:21:13 UTC 4,153 bytes, 124 lines download raw bytes

1// CANONIC service worker — Tier A offline cache.
2// Per SERVICES/APP/CANON.md § OFFLINE_CACHE.
3//
4// Strategy:
5//   - App shell (/, /offline, /icon.svg, /manifest.webmanifest): cache-first.
6//   - Governed JSON routes (/api/canon/*, /api/galaxy/*): network-first with
7//     cached fallback so fresh-on-connect / safe-on-plane.
8//   - Everything else: pass-through to network.
9//
10// Version string invalidates cache on deploy — bump when shell changes.
11
12const CACHE_VERSION = 'canonic-v1-2026-04-24'
13const SHELL_CACHE = `${CACHE_VERSION}-shell`
14const API_CACHE = `${CACHE_VERSION}-api`
15
16const SHELL_URLS = [
17  '/',
18  '/icon.svg',
19  '/manifest.webmanifest',
20]
21
22const API_PREFIXES = ['/api/canon/', '/api/galaxy/']
23
24self.addEventListener('install', (event) => {
25  event.waitUntil(
26    caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL_URLS))
27      .then(() => self.skipWaiting())
28  )
29})
30
31self.addEventListener('activate', (event) => {
32  event.waitUntil(
33    caches.keys().then((keys) =>
34      Promise.all(
35        keys
36          .filter((k) => !k.startsWith(CACHE_VERSION))
37          .map((k) => caches.delete(k))
38      )
39    ).then(() => self.clients.claim())
40  )
41})
42
43function isGovernedApi(url) {
44  return API_PREFIXES.some((p) => url.pathname.startsWith(p))
45}
46
47function offlineShellResponse() {
48  return new Response(
49    '<!doctype html><html><head><meta charset="utf-8">' +
50    '<title>CANONIC — offline</title>' +
51    '<meta name="viewport" content="width=device-width,initial-scale=1">' +
52    // Self-contained offline page — :root holds the governed palette SOT; every
53    // rule below references var(--…). No globals.css here (sw scope). color-allow below.
54    '<style>:root{--bg:#fff;--fg:#111;--muted:#555;--code-bg:#f4f4f4}' + // color-allow:self-contained-offline-page-palette-sot
55    'body{font-family:Georgia,serif;background:var(--bg);color:var(--fg);' +
56    'display:flex;align-items:center;justify-content:center;min-height:100vh;' +
57    'margin:0;text-align:center;padding:2rem}h1{font-size:2rem;margin:0 0 .5rem}' +
58    'p{color:var(--muted);max-width:32rem}code{background:var(--code-bg);padding:.15rem .35rem}</style>' +
59    '</head><body><div><h1>∩ CANONIC</h1>' +
60    '<p>You\'re offline. Cached governance is still available — navigate back, ' +
61    'or reconnect to refresh. Service worker: <code>' + CACHE_VERSION + '</code></p>' +
62    '</div></body></html>',
63    { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } }
64  )
65}
66
67self.addEventListener('fetch', (event) => {
68  const req = event.request
69  if (req.method !== 'GET') return
70  const url = new URL(req.url)
71  if (url.origin !== self.location.origin) return
72
73  // Governed JSON routes — network-first, fall back to cache, then empty 503.
74  if (isGovernedApi(url)) {
75    event.respondWith(
76      fetch(req)
77        .then((res) => {
78          if (res.ok) {
79            const copy = res.clone()
80            caches.open(API_CACHE).then((cache) => cache.put(req, copy))
81          }
82          return res
83        })
84        .catch(() =>
85          caches.match(req).then((cached) =>
86            cached ||
87            new Response(
88              JSON.stringify({ offline: true, cached: false }),
89              { status: 503, headers: { 'content-type': 'application/json' } }
90            )
91          )
92        )
93    )
94    return
95  }
96
97  // Navigations — cache-first shell, network as refresh, offline fallback.
98  if (req.mode === 'navigate') {
99    event.respondWith(
100      caches.match(req).then((cached) => {
101        const fetchPromise = fetch(req)
102          .then((res) => {
103            if (res.ok) {
104              const copy = res.clone()
105              caches.open(SHELL_CACHE).then((cache) => cache.put(req, copy))
106            }
107            return res
108          })
109          .catch(() =>
110            caches.match('/').then((root) => root || offlineShellResponse())
111          )
112        return cached || fetchPromise
113      })
114    )
115    return
116  }
117
118  // Static shell assets — cache-first.
119  if (SHELL_URLS.includes(url.pathname)) {
120    event.respondWith(
121      caches.match(req).then((cached) => cached || fetch(req))
122    )
123  }
124})

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.