PageSourceSearch

https://jastrow.app/sw.js

js jastrow.app collected 2026-10-09 01:17:04 UTC 5,215 bytes, 188 lines download raw bytes

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.