// STEM Copilot — Service Worker (PWA installability + safe caching) // // Caching strategy: // • API calls (/chat, /auth, …) → never touched, always network. // • Navigations + app code (HTML/JS/ → NETWORK-FIRST. This is critical: a // CSS) cache-first shell used to serve a stale // index.html / app.js after every deploy, // which forced users to hard-refresh // (Ctrl+Shift+R) and could surface an old, // un-gated build of the app. Network-first // means everyone always gets the live code. // • Other static assets (images, → cache-first (fine to be stale). // fonts) const CACHE_NAME = 'stemcopilot-v3'; const ASSET_URLS = [ '/assets/bot.png', '/assets/stem_black.png', '/assets/stembotix.png', ]; self.addEventListener('install', (e) => { e.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSET_URLS))); self.skipWaiting(); }); self.addEventListener('activate', (e) => { e.waitUntil( caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))) ) ); self.clients.claim(); }); function _isApi(url) { return ['/chat', '/auth', '/threads', '/history', '/user', '/export', '/health', '/feedback', '/notes', '/upload-doc', '/rename', '/thread'] .some((p) => url.includes(p)); } // HTML navigations and the core app code must always come fresh from the network. function _isAppCode(req, url) { return req.mode === 'navigate' || url.endsWith('/') || url.includes('/static/app.js') || url.includes('/static/style.css') || url.includes('/static/index.html'); } self.addEventListener('fetch', (e) => { const url = e.request.url; if (_isApi(url)) return; // straight to network, no SW involvement if (_isAppCode(e.request, url)) { // Network-first: fetch live, fall back to cache only when offline. e.respondWith( fetch(e.request) .then((resp) => { const copy = resp.clone(); caches.open(CACHE_NAME).then((c) => c.put(e.request, copy)).catch(() => {}); return resp; }) .catch(() => caches.match(e.request)) ); return; } // Everything else (images, fonts): cache-first. e.respondWith(caches.match(e.request).then((cached) => cached || fetch(e.request))); });