Repository Public
1/*2 * Claude Remote service worker — caches the STATIC APP SHELL only.3 * cached: /_next/static/*, fonts, icons, the manifest and the HTML shells of the routes4 * never: /api/* (not even looked at), WebSockets (not interceptable anyway), anything5 * non-GET or cross-origin. No terminal data, API response or credential is stored.6 * Offline: navigations fall back to the cached shell, which renders the "Offline" state.7 */8const VERSION = 'cr-shell-v2';9const SHELLS = ['/', '/login/', '/setup/', '/home/', '/terminal/', '/sessions/', '/projects/', '/files/', '/tasks/', '/cluster/', '/skills/', '/mcp/', '/system/', '/settings/'];10const PRECACHE = [...SHELLS, '/theme-init.js', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png'];1112self.addEventListener('install', (event) => {13 event.waitUntil(14 caches.open(VERSION).then((cache) => Promise.all(PRECACHE.map((u) => cache.add(new Request(u, { cache: 'reload' })).catch(() => undefined)))).then(() => self.skipWaiting()),15 );16});17self.addEventListener('activate', (event) => {18 event.waitUntil(19 caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== VERSION).map((k) => caches.delete(k)))).then(() => self.clients.claim()),20 );21});2223function isStaticAsset(url) {24 return url.pathname.startsWith('/_next/static/') || url.pathname.startsWith('/icons/') || url.pathname === '/manifest.webmanifest' || url.pathname === '/theme-init.js' || /\.(woff2?|ttf)$/.test(url.pathname);25}2627self.addEventListener('fetch', (event) => {28 const req = event.request;29 if (req.method !== 'GET') return;30 const url = new URL(req.url);31 if (url.origin !== self.location.origin) return;32 if (url.pathname.startsWith('/api/')) return; // never handled, never cached3334 if (isStaticAsset(url)) {35 // content-hashed: cache first36 event.respondWith(37 caches.match(req).then((hit) => hit || fetch(req).then((res) => {38 if (res.ok) { const copy = res.clone(); caches.open(VERSION).then((c) => c.put(req, copy)); }39 return res;40 })),41 );42 return;43 }44 if (req.mode === 'navigate' || url.pathname.endsWith('.txt')) {45 // HTML shells and RSC payloads: network first (always fresh when online), cache as fallback46 const key = url.origin + url.pathname; // ignore ?s=… so one shell serves every query47 event.respondWith(48 fetch(req).then((res) => {49 if (res.ok && res.status === 200) { const copy = res.clone(); caches.open(VERSION).then((c) => c.put(key, copy)); }50 return res;51 }).catch(() => caches.match(key).then((hit) => hit || (req.mode === 'navigate' ? caches.match(url.origin + '/terminal/') : undefined)).then((hit) => hit || Response.error())),52 );53 }54});55