PageSourceSearch

https://www.kich.app/sw.js

js kich.app collected 2026-10-02 01:14:23 UTC 4,816 bytes, 146 lines download raw bytes

1const CACHE_NAME = 'kich-v5';
2const IMAGE_CACHE_NAME = 'kich-images-v1';
3const IMAGE_CACHE_MAX = 500; // Max cached images — ~250MB at ~500KB each
4
5self.addEventListener('install', event => {
6  // Pre-cache the app shell immediately
7  event.waitUntil(
8    caches.open(CACHE_NAME).then(cache => {
9      return cache.addAll([
10        '/',
11        '/index.html',
12        '/manifest.json',
13        '/icon.svg',
14        '/icon-192.png',
15        '/icon-512.png'
16      ]);
17    })
18  );
19  self.skipWaiting();
20});
21
22self.addEventListener('activate', event => {
23  // Clean old caches (keep current app + image cache)
24  event.waitUntil(
25    caches.keys().then(keys =>
26      Promise.all(keys.filter(k => k !== CACHE_NAME && k !== IMAGE_CACHE_NAME).map(k => caches.delete(k)))
27    )
28  );
29  self.clients.claim();
30});
31
32self.addEventListener('fetch', event => {
33  const url = new URL(event.request.url);
34
35  // Skip non-GET
36  if (event.request.method !== 'GET') return;
37
38  // Skip non-http(s) schemes (chrome-extension://, etc.)
39  if (!url.protocol.startsWith('http')) return;
40
41  // Skip API endpoints — always network
42  if (url.pathname.startsWith('/api/')) return;
43
44  // Skip admin page — always load fresh from network
45  if (url.pathname.startsWith('/admin')) return;
46
47  // Firebase Storage images — cache-first (images don't change once uploaded)
48  if (url.hostname.includes('firebasestorage.googleapis.com') || url.hostname.includes('firebasestorage.app')) {
49    event.respondWith(
50      caches.open(IMAGE_CACHE_NAME).then(cache =>
51        cache.match(event.request).then(cached => {
52          if (cached) return cached;
53          return fetch(event.request).then(response => {
54            if (response.status === 200) {
55              const clone = response.clone();
56              // Cache the image, then trim if over limit
57              cache.put(event.request, clone).then(() => {
58                cache.keys().then(keys => {
59                  if (keys.length > IMAGE_CACHE_MAX) {
60                    const toDelete = keys.length - IMAGE_CACHE_MAX;
61                    for (let i = 0; i < toDelete; i++) {
62                      cache.delete(keys[i]);
63                    }
64                  }
65                });
66              });
67            }
68            return response;
69          }).catch(() => {
70            // Offline and not cached — return transparent SVG placeholder
71            return new Response(
72              '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>',
73              { headers: { 'Content-Type': 'image/svg+xml' } }
74            );
75          });
76        })
77      )
78    );
79    return;
80  }
81
82  // Skip other Firebase and external APIs — always network
83  if (url.hostname.includes('firebaseio.com') ||
84      url.hostname.includes('firebaseapp.com') ||
85      url.hostname.includes('googleapis.com') ||
86      url.hostname.includes('google.com') ||
87      url.hostname.includes('fcm.googleapis.com') ||
88      url.hostname.includes('anthropic.com') ||
89      url.hostname.includes('qrserver.com')) {
90    return;
91  }
92
93  // Navigation requests (page loads) — network first, fall back to cached index.html
94  if (event.request.mode === 'navigate') {
95    event.respondWith(
96      fetch(event.request).then(response => {
97        const clone = response.clone();
98        caches.open(CACHE_NAME).then(cache => {
99          try { cache.put(event.request, clone); } catch (e) {}
100        });
101        return response;
102      }).catch(() => {
103        return caches.match('/index.html').then(cached => {
104          return cached || new Response('Offline — please connect to the internet and reload.', {
105            status: 503,
106            headers: { 'Content-Type': 'text/plain' }
107          });
108        });
109      })
110    );
111    return;
112  }
113
114  // All other requests — stale-while-revalidate
115  // Serve from cache immediately, update cache in background
116  event.respondWith(
117    caches.match(event.request).then(cached => {
118      const fetchPromise = fetch(event.request).then(response => {
119        if (response.status === 200) {
120          const clone = response.clone();
121          caches.open(CACHE_NAME).then(cache => {
122            try { cache.put(event.request, clone); } catch (e) {}
123          });
124        }
125        return response;
126      }).catch(() => cached || new Response('', { status: 408 }));
127
128      return cached || fetchPromise;
129    })
130  );
131});
132
133// Handle notification clicks
134self.addEventListener('notificationclick', event => {
135  event.notification.close();
136  event.waitUntil(
137    clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => {
138      for (const client of clientList) {
139        if (client.url.includes(self.location.origin) && 'focus' in client) {
140          return client.focus();
141        }
142      }
143      return clients.openWindow('/');
144    })
145  );
146});

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.