PageSourceSearch

https://app.curis360.com/sw.js

js curis360.com collected 2026-10-04 01:52:39 UTC 5,521 bytes, 148 lines download raw bytes

1const CACHE_NAME = "curis-360-v91";
2
3const CORE_ASSETS = [
4  "/",
5  "/index.html",
6  "/manifest.json",
7  "/patient-manifest.json",
8  "/patient-login.html",
9  "/patient-hub.html",
10  "/tailwind.css",
11  "/main.css",
12  "/firebase-init.js",
13  "/demo-mode.js",
14  "/access-guard.js",
15  "/branch-sync.js",
16  "/logo.webp",
17  "/Curis360 logo.png",
18  "/icons/icon-192.png",
19  "/icons/icon-512.png",
20  "/icons/maskable-192.png",
21  "/icons/maskable-512.png",
22  "/icons/apple-touch-icon.png",
23  "/offline.html", // 👈 Added dedicated offline page
24  "/404.html"
25];
26
27// Helper to check if URL should be ignored by the Service Worker Cache
28function isExternalHost(url) {
29  const hosts = [
30    'firestore.googleapis.com',
31    'identitytoolkit.googleapis.com',
32    'securetoken.googleapis.com',
33    'ui-avatars.com',
34    'firebaseinstallations.googleapis.com',
35    'apis.google.com',
36    'accounts.google.com',
37    'firebasestorage.googleapis.com', // 👈 SECURED: Prevents caching of patient PDFs & images
38    // Signed Storage URLs land here — training videos, attendance selfies, any
39    // getSignedUrl output. They MUST bypass the service worker: the stale-while-
40    // revalidate branch below calls respondWith() on a media element's range
41    // requests and tries to cache the 206, which breaks seeking and can hand a
42    // video element a stale, already-expired response on the next open.
43    'storage.googleapis.com'
44  ];
45  return hosts.some(host => url.hostname.includes(host));
46}
47
48function isFirebaseAuthHelper(url) {
49  return url.origin === self.location.origin && url.pathname.startsWith('/__/auth/');
50}
51
52// Learning Portal media broker. Every hit 302-redirects to a short-lived signed
53// URL minted for ONE viewer, and video playback drives it with byte-range
54// requests — both are things a cache must never touch. Letting the SW through
55// also keeps the browser's native media pipeline in charge of ranges, which is
56// what makes long training videos seek and resume correctly.
57function isTrainingMedia(url) {
58  return url.pathname.startsWith('/training-media')
59    || url.hostname.endsWith('cloudfunctions.net');
60}
61
62self.addEventListener("install", (event) => {
63  // Pre-cache core assets
64  event.waitUntil(
65    caches.open(CACHE_NAME).then((cache) => {
66      const requests = CORE_ASSETS.map(url => {
67        return fetch(new Request(url, { cache: 'no-cache' })).then(response => {
68          if (!response.ok && response.type !== 'opaque') {
69            throw new TypeError(`Request failed with status ${response.status}`);
70          }
71          return cache.put(url, response);
72        }).catch(err => console.warn('Failed to precache', url, err));
73      });
74      return Promise.all(requests);
75    }).then(() => self.skipWaiting())
76  );
77});
78
79self.addEventListener("activate", (event) => {
80  event.waitUntil(
81    caches.keys().then((cacheNames) => {
82      return Promise.all(
83        cacheNames.map((cacheName) => {
84          if (cacheName !== CACHE_NAME) {
85            console.log("Service Worker: Clearing Old Cache", cacheName);
86            return caches.delete(cacheName);
87          }
88        })
89      );
90    }).then(() => self.clients.claim())
91  );
92});
93
94self.addEventListener("fetch", (event) => {
95  const url = new URL(event.request.url);
96
97  // Skip caching for non-GET requests, external API/auth domains, and the
98  // training media broker (per-viewer redirects + byte-range video traffic).
99  if (event.request.method !== 'GET' || isExternalHost(url)
100      || isFirebaseAuthHelper(url) || isTrainingMedia(url)) {
101    return;
102  }
103
104  // HTML Requests Strategy: Network First, fallback to Cache
105  // `accept` can be absent on some requests, and .includes on null throws inside
106  // the handler. Kept from the previous revision because it is a crash guard,
107  // not part of the reverted speed work.
108  const accept = event.request.headers.get('accept') || '';
109  if (event.request.mode === 'navigate' || accept.includes('text/html')) {
110    event.respondWith(
111      fetch(event.request)
112        .then((networkResponse) => {
113          const resClone = networkResponse.clone();
114          caches.open(CACHE_NAME).then((cache) => cache.put(event.request, resClone));
115          return networkResponse;
116        })
117        .catch(async () => {
118          // If offline, serve from cache if available, else fallback to offline.html
119          const cachedResponse = await caches.match(event.request);
120          return cachedResponse || await caches.match('/offline.html');
121        })
122    );
123    return;
124  }
125
126  // Static Assets Strategy: Stale-While-Revalidate (Cache First, refresh in background)
127  event.respondWith(
128    caches.match(event.request).then((cachedResponse) => {
129      const fetchPromise = fetch(event.request)
130        .then((networkResponse) => {
131          // Important: Clone the response. A response is a stream and can only be consumed once.
132          if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {
133            if (networkResponse && networkResponse.type === 'opaque') {
134              const resClone = networkResponse.clone();
135              caches.open(CACHE_NAME).then((cache) => cache.put(event.request, resClone));
136            }
137            return networkResponse;
138          }
139          const resClone = networkResponse.clone();
140          caches.open(CACHE_NAME).then((cache) => cache.put(event.request, resClone));
141          return networkResponse;
142        })
143        .catch(() => null);
144
145      return cachedResponse || fetchPromise;
146    })
147  );
148});

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.