// ========================================== // Switchboard Core – 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 (cleaned up in v0.37.12) const SHELL_FILES = [ './', './index.html', // CSS — shared './css/variables.css', './css/layout.css', './css/primitives.css', './css/modals.css', './css/surfaces.css', './css/user-menu.css', './css/workflow.css', './css/admin-surfaces.css', './css/tool-grants.css', './css/extension-surface.css', // CSS — Preact (sw-*) './css/sw-primitives.css', './css/sw-shell.css', './css/sw-debug.css', './css/sw-login.css', './css/sw-chat-pane.css', './css/sw-chat-surface.css', './css/sw-notes-pane.css', './css/sw-notes-surface.css', // JS — debug tooling (v0.37.18: repl.js absorbed into Preact debug components) './js/debug.js', // Vendor './vendor/marked.min.js', './vendor/purify.min.js', // Static assets './favicon.svg', './favicon-light.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; }) ); });