const CACHE = 'cortex-v2'; const PRECACHE = [ '/static/style.css', '/static/app.js', '/static/marked.min.js', '/static/icon-192.png', '/static/icon-512.png', '/static/icon.svg', '/static/manifest.json', ]; self.addEventListener('install', evt => { evt.waitUntil( caches.open(CACHE) .then(c => c.addAll(PRECACHE)) .then(() => self.skipWaiting()) ); }); self.addEventListener('activate', evt => { evt.waitUntil( caches.keys() .then(keys => Promise.all( keys.filter(k => k !== CACHE).map(k => caches.delete(k)) )) .then(() => self.clients.claim()) ); }); self.addEventListener('push', evt => { let data = { title: 'Cortex', body: '', url: '/' }; if (evt.data) { try { data = { ...data, ...evt.data.json() }; } catch (_) {} } evt.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: '/static/icon-192.png', badge: '/static/icon-192.png', data: { url: data.url }, }) ); }); self.addEventListener('notificationclick', evt => { evt.notification.close(); const url = evt.notification.data?.url || '/'; evt.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then(list => { for (const c of list) { if (c.url.includes(self.location.origin) && 'focus' in c) { c.navigate(url); return c.focus(); } } if (clients.openWindow) return clients.openWindow(url); }) ); }); self.addEventListener('fetch', evt => { const url = new URL(evt.request.url); // Only handle same-origin GETs if (evt.request.method !== 'GET' || url.origin !== self.location.origin) return; // Never intercept streaming or API calls if ( url.pathname.startsWith('/chat') || url.pathname.startsWith('/orchestrate') || url.pathname.startsWith('/api/') || url.pathname.startsWith('/distill') || url.pathname.startsWith('/webhook') || url.pathname.startsWith('/auth/') ) return; // Static assets — cache first, refresh in background (stale-while-revalidate) if (url.pathname.startsWith('/static/')) { evt.respondWith( caches.open(CACHE).then(cache => cache.match(evt.request).then(cached => { const network = fetch(evt.request).then(resp => { if (resp.ok) cache.put(evt.request, resp.clone()); return resp; }); return cached || network; }) ) ); return; } // HTML pages — network first, cached shell fallback evt.respondWith( fetch(evt.request) .then(resp => { if (resp.ok) { const clone = resp.clone(); caches.open(CACHE).then(c => c.put(evt.request, clone)); } return resp; }) .catch(() => caches.match(evt.request)) ); });