1/** 2 * Service Worker for Jastrow Dictionary 3 * Provides offline functionality and caching for the 42MB dictionary data 4 * Uses vanilla Service Worker API (no build process, no Workbox) 5 */ 6 7// Update this timestamp whenever you deploy changes 8// This forces the service worker to update and clear old caches 9const CACHE_VERSION = '2026-04-28T00:00:00.000Z'; 10 11// Assets to cache immediately on install 12const STATIC_ASSETS = [ 13 '/', 14 '/index.html', 15 '/assets/styles/styles.css', 16 '/assets/scripts/constants.js', 17 '/assets/scripts/sanitizer.js', 18 '/assets/scripts/keyboard.js', 19 '/assets/scripts/data-loader.js', 20 '/assets/scripts/app.js', 21 '/assets/images/logo.svg', 22 '/assets/images/sefarialogo.svg', 23 '/assets/images/favicon/favicon.svg', 24 '/assets/images/favicon/favicon-96x96.png', 25 '/assets/images/favicon/favicon.ico', 26 '/assets/images/favicon/apple-touch-icon.png', 27 '/assets/images/favicon/site.webmanifest', 28 '/assets/styles/sages.css', 29 '/assets/scripts/sages-data.js', 30 '/assets/scripts/sages-graph.js', 31 '/assets/scripts/sages-sidebar.js', 32 '/assets/scripts/sages.js', 33 '/assets/scripts/scroll-manager.js', 34 '/assets/styles/rabbinic-time.css', 35 '/assets/scripts/rabbinic-time.js', 36 '/data/sages.json', 37 '/data/jastrow-abbr.json', 38 '/data/jastrow-hebrew-abbr.json', 39]; 40 41/** 42 * Install event - cache static assets 43 */ 44self.addEventListener('install', (event) => { 45 console.log('[Service Worker] Installing...'); 46 47 event.waitUntil( 48 caches 49 .open(CACHE_VERSION) 50 .then((cache) => { 51 console.log('[Service Worker] Caching static assets'); 52 return cache.addAll(STATIC_ASSETS); 53 }) 54 .then(() => { 55 console.log('[Service Worker] Static assets cached successfully'); 56 }) 57 .catch((error) => { 58 console.error('[Service Worker] Failed to cache static assets:', error); 59 }), 60 ); 61}); 62 63/** 64 * Activate event - clean up old caches 65 */ 66self.addEventListener('activate', (event) => { 67 console.log('[Service Worker] Activating...'); 68 69 event.waitUntil( 70 caches 71 .keys() 72 .then((cacheNames) => 73 Promise.all( 74 cacheNames 75 .filter((cacheName) => cacheName !== CACHE_VERSION) 76 .map((cacheName) => { 77 console.log('[Service Worker] Deleting old cache:', cacheName); 78 return caches.delete(cacheName); 79 }), 80 ), 81 ) 82 .then(() => { 83 console.log('[Service Worker] Activated successfully'); 84 // Take control of all pages immediately 85 return self.clients.claim(); 86 }), 87 ); 88}); 89 90/** 91 * Fetch event - serve from cache, fallback to network 92 * Strategy: 93 * - Network-first for HTML/JS/CSS (ensures updates are detected) 94 * - Cache-first for static assets and data 95 * - Network-first for external resources 96 */ 97self.addEventListener('fetch', (event) => { 98 const { request } = event; 99 const url = new URL(request.url); 100 101 // Cross-origin requests (CDN scripts, fonts, etc.) â let the browser handle directly 102 if (url.origin !== location.origin) { 103 return; 104 } 105 106 // Data files and version.json bypass SW cache â IDB handles data persistence 107 // Critical: version.json must never be cached by SW or update detection breaks 108 if ( 109 url.pathname.startsWith('/data/') && 110 !url.pathname.endsWith('/sages.json') 111 ) { 112 event.respondWith(fetch(request)); 113 return; 114 } 115 116 // Network-first for HTML, JS, CSS to ensure users get updates quickly 117 const isAppFile = 118 url.pathname.endsWith('.html') || 119 url.pathname.endsWith('.js') || 120 url.pathname.endsWith('.css') || 121 url.pathname === '/' || 122 url.pathname === ''; 123 124 if (isAppFile) { 125 event.respondWith( 126 fetch(request) 127 .then((networkResponse) => { 128 // Cache the new version 129 if (networkResponse && networkResponse.status === 200) { 130 const responseClone = networkResponse.clone(); 131 caches.open(CACHE_VERSION).then((cache) => { 132 cache.put(request, responseClone); 133 }); 134 } 135 return networkResponse; 136 }) 137 .catch(() => { 138 // Network failed, fall back to cache 139 return caches.match(request); 140 }), 141 ); 142 return; 143 } 144 145 // Cache-first strategy for other static assets (images, fonts, etc.) 146 event.respondWith( 147 caches 148 .match(request) 149 .then((cachedResponse) => { 150 if (cachedResponse) { 151 // Return cached version 152 return cachedResponse; 153 } 154 155 // Not in cache, fetch from network and cache it
156 return fetch(request).then((networkResponse) => { 157 // Only cache successful responses 158 if (networkResponse && networkResponse.status === 200) { 159 const responseClone = networkResponse.clone(); 160 caches.open(CACHE_VERSION).then((cache) => { 161 cache.put(request, responseClone); 162 }); 163 } 164 return networkResponse; 165 }); 166 }) 167 .catch((error) => { 168 console.error('[Service Worker] Fetch failed:', error); 169 // Could return a custom offline page here 170 throw error; 171 }), 172 ); 173}); 174 175/** 176 * Message event - handle messages from the app 177 */ 178self.addEventListener('message', (event) => { 179 if (event.data && event.data.type === 'SKIP_WAITING') { 180 console.log('[Service Worker] Received SKIP_WAITING message'); 181 self.skipWaiting(); 182 } 183 if (event.data && event.data.type === 'GET_VERSION') { 184 event.ports[0]?.postMessage({ cacheVersion: CACHE_VERSION }); 185 } 186}); 187 188console.log('[Service Worker] Script loaded');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.