// ========================================== // Chat Switchboard – Service Worker // ========================================== // Caches the app shell for offline / instant load. // API calls always go to network (never cached). // // Cache key includes a build hash computed at container // startup from the JS file contents. Every deploy produces // a new hash, which triggers install → precache → activate // → purge old caches automatically. const CACHE_NAME = 'switchboard-%%APP_VERSION%%-%%BUILD_HASH%%'; // App shell files to pre-cache on install const SHELL_FILES = [ './', './index.html', './css/variables.css', './css/layout.css', './css/primitives.css', './css/modals.css', './css/chat.css', './css/panels.css', './css/surfaces.css', './css/splash.css', './js/debug.js', './js/events.js', './js/extensions.js', './js/api.js', './js/ui-format.js', './js/ui-primitives.js', './js/ui-core.js', './js/ui-settings.js', './js/ui-admin.js', './js/tokens.js', './js/notes.js', './js/files.js', './js/tools-toggle.js', './js/knowledge-ui.js', './js/chat.js', './js/settings-handlers.js', './js/admin-handlers.js', './js/app.js', './vendor/marked.min.js', './vendor/purify.min.js', './favicon.svg', './favicon.ico', './favicon-32.png', './favicon-256.png', './manifest.json', ]; // Install: pre-cache the shell self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(SHELL_FILES)) .then(() => self.skipWaiting()) ); }); // Activate: purge old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(keys => Promise.all( keys.filter(k => k !== CACHE_NAME) .map(k => caches.delete(k)) ) ).then(() => self.clients.claim()) ); }); // Fetch: network-first for API/WS, stale-while-revalidate for shell assets self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); // Only handle http/https — ignore chrome-extension://, moz-extension://, etc. if (url.protocol !== 'https:' && url.protocol !== 'http:') { return; } // Never cache API calls, WebSocket upgrades, branding, extensions, or CM6 bundle if (url.pathname.includes('/api/') || url.pathname.includes('/ws') || url.pathname.includes('/branding/') || url.pathname.includes('/extensions/') || url.pathname.includes('/vendor/codemirror/') || event.request.method !== 'GET') { return; } // Stale-while-revalidate: serve cached, update in background event.respondWith( caches.match(event.request).then(cached => { const fetchPromise = fetch(event.request).then(response => { // Update cache with fresh version if (response.ok) { const clone = response.clone(); caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone)); } return response; }).catch(() => cached); // offline fallback to cache return cached || fetchPromise; }) ); });