|
209
|
1 // Service Worker for MrJuneJune PWA
|
|
|
2 const CACHE_VERSION = 'v1';
|
|
|
3 const CACHE_NAME = `mrjunejune-${CACHE_VERSION}`;
|
|
|
4
|
|
|
5 // Files to cache immediately on install
|
|
|
6 const STATIC_CACHE = [
|
|
|
7 '/',
|
|
|
8 '/base.css',
|
|
|
9 '/public/epi_all_colors.svg',
|
|
|
10 '/public/fonts/Roboto-Regular.ttf',
|
|
|
11 '/public/fonts/Roboto-Thin.ttf',
|
|
|
12 '/public/fonts/more-sugar.regular.otf',
|
|
|
13 '/public/fonts/more-sugar.thin.otf',
|
|
|
14 ];
|
|
|
15
|
|
|
16 // Install event - cache static assets
|
|
|
17 self.addEventListener('install', (event) => {
|
|
|
18 console.log('[SW] Installing service worker...');
|
|
|
19
|
|
|
20 event.waitUntil(
|
|
|
21 caches.open(CACHE_NAME).then((cache) => {
|
|
|
22 console.log('[SW] Caching static assets');
|
|
|
23 return cache.addAll(STATIC_CACHE);
|
|
|
24 }).then(() => {
|
|
|
25 console.log('[SW] Skip waiting');
|
|
|
26 return self.skipWaiting();
|
|
|
27 })
|
|
|
28 );
|
|
|
29 });
|
|
|
30
|
|
|
31 // Activate event - clean up old caches
|
|
|
32 self.addEventListener('activate', (event) => {
|
|
|
33 console.log('[SW] Activating service worker...');
|
|
|
34
|
|
|
35 event.waitUntil(
|
|
|
36 caches.keys().then((cacheNames) => {
|
|
|
37 return Promise.all(
|
|
|
38 cacheNames.map((cacheName) => {
|
|
|
39 if (cacheName !== CACHE_NAME) {
|
|
|
40 console.log('[SW] Deleting old cache:', cacheName);
|
|
|
41 return caches.delete(cacheName);
|
|
|
42 }
|
|
|
43 })
|
|
|
44 );
|
|
|
45 }).then(() => {
|
|
|
46 console.log('[SW] Claiming clients');
|
|
|
47 return self.clients.claim();
|
|
|
48 })
|
|
|
49 );
|
|
|
50 });
|
|
|
51
|
|
|
52 // Fetch event - serve from cache, fallback to network
|
|
|
53 self.addEventListener('fetch', (event) => {
|
|
|
54 const { request } = event;
|
|
|
55 const url = new URL(request.url);
|
|
|
56
|
|
|
57 // Skip non-GET requests
|
|
|
58 if (request.method !== 'GET') {
|
|
|
59 return;
|
|
|
60 }
|
|
|
61
|
|
|
62 // Skip chrome-extension and other non-http(s) requests
|
|
|
63 if (!url.protocol.startsWith('http')) {
|
|
|
64 return;
|
|
|
65 }
|
|
|
66
|
|
|
67 // Skip API calls and media uploads (always go to network)
|
|
|
68 if (url.pathname.startsWith('/api/')) {
|
|
|
69 return;
|
|
|
70 }
|
|
|
71
|
|
|
72 event.respondWith(
|
|
|
73 caches.match(request).then((cachedResponse) => {
|
|
|
74 if (cachedResponse) {
|
|
|
75 console.log('[SW] Serving from cache:', url.pathname);
|
|
|
76 return cachedResponse;
|
|
|
77 }
|
|
|
78
|
|
|
79 // Not in cache, fetch from network
|
|
|
80 return fetch(request).then((networkResponse) => {
|
|
|
81 // Only cache successful responses
|
|
|
82 if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') {
|
|
|
83 return networkResponse;
|
|
|
84 }
|
|
|
85
|
|
|
86 // Cache specific file types
|
|
|
87 const shouldCache =
|
|
|
88 url.pathname.endsWith('.css') ||
|
|
|
89 url.pathname.endsWith('.js') ||
|
|
|
90 url.pathname.endsWith('.svg') ||
|
|
|
91 url.pathname.endsWith('.png') ||
|
|
|
92 url.pathname.endsWith('.jpg') ||
|
|
|
93 url.pathname.endsWith('.webp') ||
|
|
|
94 url.pathname.endsWith('.woff') ||
|
|
|
95 url.pathname.endsWith('.woff2') ||
|
|
|
96 url.pathname.endsWith('.ttf') ||
|
|
|
97 url.pathname.endsWith('.otf') ||
|
|
|
98 url.pathname.startsWith('/blog/') ||
|
|
|
99 url.pathname.startsWith('/notes/') ||
|
|
|
100 url.pathname === '/';
|
|
|
101
|
|
|
102 if (shouldCache) {
|
|
|
103 const responseToCache = networkResponse.clone();
|
|
|
104 caches.open(CACHE_NAME).then((cache) => {
|
|
|
105 console.log('[SW] Caching new resource:', url.pathname);
|
|
|
106 cache.put(request, responseToCache);
|
|
|
107 });
|
|
|
108 }
|
|
|
109
|
|
|
110 return networkResponse;
|
|
|
111 }).catch((error) => {
|
|
|
112 console.log('[SW] Fetch failed:', error);
|
|
|
113
|
|
|
114 // Return offline page for HTML requests
|
|
|
115 if (request.headers.get('Accept').includes('text/html')) {
|
|
|
116 return caches.match('/offline.html');
|
|
|
117 }
|
|
|
118 });
|
|
|
119 })
|
|
|
120 );
|
|
|
121 });
|
|
|
122
|
|
|
123 // Handle messages from the client
|
|
|
124 self.addEventListener('message', (event) => {
|
|
|
125 if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
|
126 self.skipWaiting();
|
|
|
127 }
|
|
|
128
|
|
|
129 if (event.data && event.data.type === 'CLEAR_CACHE') {
|
|
|
130 event.waitUntil(
|
|
|
131 caches.keys().then((cacheNames) => {
|
|
|
132 return Promise.all(
|
|
|
133 cacheNames.map((cacheName) => caches.delete(cacheName))
|
|
|
134 );
|
|
|
135 })
|
|
|
136 );
|
|
|
137 }
|
|
|
138 });
|