1// XOIGAC.TV Service Worker - éæèµæºç¼å + èªå¨çæ¬æ´æ° 2// åªç¼åéæèµæºï¼CSSãJSã徿 ãè¯è¨å ï¼ï¼å¨æå å®¹ç´æ¥è®¿é®ç½ç» 3 4const CACHE_VERSION = 'v2.5.0'; 5const STATIC_CACHE = `xoigac-static-${CACHE_VERSION}`; 6 7// éè¦ç¼åçéæèµæºæ¨¡å¼ 8const STATIC_RESOURCES = [ 9 // CSSæ ·å¼æä»¶ 10 /\.css($|\?)/, 11 // JavaScriptæä»¶ 12 /\.js($|\?)/, 13 // 徿 åå¾ç 14 /\.(png|jpg|jpeg|gif|svg|ico|webp)($|\?)/, 15 // è¯è¨å 16 /\/i18n\/.*\.json($|\?)/, 17 // å使件 18 /\.(woff|woff2|ttf|eot)($|\?)/, 19 // PWA manifest 20 /manifest\.json($|\?)/ 21]; 22 23// ä¸ç¼åç卿å å®¹æ¨¡å¼ 24const DYNAMIC_RESOURCES = [ 25 // APIæ¥å£ 26 /\/api\//, 27 // 页é¢HTML 28 /\.(html|htm)($|\?)/, 29 // SignalR Hub 30 /\/hub\//, 31 // å¨ææ°æ® 32 /\/match\//, 33 /\/admin\// 34]; 35 36console.log('[SW] éæèµæºç¼åå·²å¯ç¨ - çæ¬:', CACHE_VERSION); 37 38// 夿æ¯å¦ä¸ºéæèµæº 39function isStaticResource(url) { 40 const pathname = new URL(url).pathname; 41 return STATIC_RESOURCES.some(pattern => pattern.test(pathname)); 42} 43 44// 夿æ¯å¦ä¸ºå¨æå 容 45function isDynamicResource(url) { 46 const pathname = new URL(url).pathname; 47 return DYNAMIC_RESOURCES.some(pattern => pattern.test(pathname)); 48} 49 50// Service Worker å®è£ äºä»¶ - éæèµæºç¼å 51self.addEventListener('install', event => { 52 console.log('[SW] Installing Service Worker with static caching - Version:', CACHE_VERSION); 53 // ç«å³è·³è¿çå¾ ï¼å¼ºå¶æ¿æ´»æ°çæ¬ 54 self.skipWaiting(); 55 56 event.waitUntil( 57 caches.open(STATIC_CACHE) 58 .then(cache => { 59 console.log('[SW] Static cache opened:', STATIC_CACHE); 60 }) 61 .catch(error => { 62 console.error('[SW] Failed to open cache:', error); 63 }) 64 ); 65}); 66 67// Service Worker æ¿æ´»äºä»¶ - æ¸ çæææ§ç¼å 68self.addEventListener('activate', event => { 69 console.log('[SW] Activating Service Worker - Version:', CACHE_VERSION); 70 event.waitUntil( 71 caches.keys() 72 .then(cacheNames => { 73 console.log('[SW] Found caches:', cacheNames); 74 // å é¤æææ§ç¼åï¼çæ¬å·ä¸å¹é çï¼ 75 return Promise.all( 76 cacheNames.map(cacheName => { 77 if (cacheName !== STATIC_CACHE) { 78 console.log('[SW] Deleting old cache:', cacheName); 79 return caches.delete(cacheName); 80 } 81 }) 82 ); 83 }) 84 .then(() => { 85 console.log('[SW] All old caches cleared, new cache ready:', STATIC_CACHE); 86 // éç¥ææå®¢æ·ç«¯çæ¬æ´æ°ï¼éè¦æ¸ é¤ç¼åå¹¶å·æ° 87 return self.clients.matchAll().then(clients => { 88 console.log('[SW] Notifying', clients.length, 'clients about update'); 89 clients.forEach(client => { 90 client.postMessage({ 91 type: 'SW_UPDATED', 92 version: CACHE_VERSION, 93 action: 'clear_and_reload' // æ¸ é¤ç¼åå¹¶å·æ° 94 }); 95 }); 96 }); 97 }) 98 .then(() => self.clients.claim()) 99 ); 100}); 101 102// ç½ç»è¯·æ±æ¦æª - æºè½ç¼åçç¥ï¼éæèµæºç¼åï¼å¨æå å®¹ç´æ¥è®¿é®ç½ç» 103self.addEventListener('fetch', event => { 104 const { request } = event; 105 const url = new URL(request.url); 106 107 // è·³è¿é HTTP/HTTPS è¯·æ± 108 if (!url.protocol.startsWith('http')) { 109 return; 110 } 111 112 // è·³è¿å¤é¨èµæº 113 if (url.origin !== location.origin) { 114 return; 115 } 116 117 // è·³è¿ Chrome Extension è¯·æ± 118 if (url.protocol === 'chrome-extension:') { 119 return; 120 } 121 122 // ç¡®å®ç¼åçç¥ 123 if (isDynamicResource(request.url)) { 124 // 卿å 容ï¼ç´æ¥è®¿é®ç½ç»ï¼ä¸ä½¿ç¨ç¼å 125 event.respondWith( 126 fetch(request).catch(error => { 127 console.error('[SW] Dynamic request failed:', request.url, error); 128 // è¿åç½ç»é误ååºè䏿¯æåºé误 129 return new Response('Network error', { status: 503, statusText: 'Service Unavailable' }); 130 }) 131 ); 132 } else if (isStaticResource(request.url)) { 133 // éæèµæºï¼ç¼åä¼å ï¼ç½ç»åé 134 event.respondWith(cacheFirstStrategy(request)); 135 } else { 136 // å ¶ä»è¯·æ±ï¼å æ¬é¡µé¢HTMLï¼ï¼ç´æ¥éä¼ å°ç½ç»ï¼ä¸æ¦æª 137 // 让æµè§å¨æ£å¸¸å¤ç请æ±ï¼é¿å SW导è´500é误 138 return; 139 } 140}); 141 142// ç¼åä¼å çç¥ï¼ç¨äºéæèµæºï¼ 143async function cacheFirstStrategy(request) { 144 try { 145 // é¦å å°è¯ä»ç¼åè·å 146 const cache = await caches.open(STATIC_CACHE); 147 const cachedResponse = await cache.match(request); 148 149 if (cachedResponse) {
150 console.log('[SW] Cache hit:', request.url); 151 152 // åå°æ´æ°ç¼åï¼stale-while-revalidateçç¥ï¼ 153 fetch(request).then(networkResponse => { 154 if (networkResponse.ok) { 155 cache.put(request, networkResponse.clone()); 156 console.log('[SW] Background updated cache:', request.url); 157 } 158 }).catch(() => { 159 // éé»å¤±è´¥ï¼ä¸å½±åç¨æ·ä½éª 160 }); 161 162 return cachedResponse; 163 } 164 165 // ç¼åæªå½ä¸ï¼ä»ç½ç»è·å 166 console.log('[SW] Cache miss, fetching from network:', request.url); 167 const networkResponse = await fetch(request); 168 169 if (networkResponse.ok) { 170 // ç¼åæåçååº 171 cache.put(request, networkResponse.clone()); 172 console.log('[SW] Cached new resource:', request.url); 173 } 174 175 return networkResponse; 176 } catch (error) { 177 console.error('[SW] Cache strategy failed for:', request.url, error); 178 // 妿ç¼åçç¥å¤±è´¥ï¼å°è¯ç´æ¥ç½ç»è¯·æ± 179 return fetch(request); 180 } 181} 182 183// æ¶æ¯å¤ç - ç¨äºä¸ä¸»çº¿ç¨éä¿¡ 184self.addEventListener('message', event => { 185 if (event.data && event.data.type === 'SKIP_WAITING') { 186 self.skipWaiting(); 187 } 188 189 if (event.data && event.data.type === 'GET_VERSION') { 190 event.ports[0].postMessage({ version: CACHE_VERSION }); 191 } 192 193 if (event.data && event.data.type === 'CLEAR_CACHE') { 194 // æ¸ ç©ºéæèµæºç¼å 195 event.waitUntil( 196 caches.delete(STATIC_CACHE).then(() => { 197 console.log('[SW] Static cache cleared'); 198 event.ports[0].postMessage({ 199 success: true, 200 message: 'Static cache cleared successfully' 201 }); 202 }) 203 ); 204 } 205 206 if (event.data && event.data.type === 'CHECK_UPDATE') { 207 // æ£æ¥çæ¬æ´æ° 208 event.ports[0].postMessage({ 209 hasUpdate: false, // SWå·²æ¯ææ°çæ¬ 210 currentVersion: CACHE_VERSION 211 }); 212 } 213 214 // æ¸ é¤æ´æ°æéæ è®° 215 if (event.data && event.data.type === 'CLEAR_UPDATE_DISMISSED') { 216 event.ports[0].postMessage({ success: true }); 217 } 218}); 219 220// åå°åæ¥äºä»¶ï¼å¦ææ¯æï¼ 221self.addEventListener('sync', event => { 222 if (event.tag === 'background-sync') { 223 console.log('[SW] Background sync triggered'); 224 event.waitUntil( 225 // è¿éå¯ä»¥æ·»å åå°åæ¥é»è¾ï¼æ¯å¦ä¸ä¼ 离线æ¶çç¨æ·æ°æ® 226 Promise.resolve() 227 ); 228 } 229}); 230 231// Push éç¥äºä»¶ - iOS PWA Web Push 232self.addEventListener('push', event => { 233 console.log('[SW] Push event received'); 234 235 if (event.data) { 236 try { 237 const data = event.data.json(); 238 console.log('[SW] Push data:', data); 239 240 const options = { 241 body: data.body, 242 icon: data.icon || '/images/favicon/android-chrome-192x192.png', 243 badge: '/images/favicon/favicon-32x32.png', 244 tag: 'xoigac-push-notification', 245 requireInteraction: true, // æ¹ä¸º trueï¼ä¿æéç¥å±å¼ç¶æ 246 data: { 247 url: data.url || '/' 248 }, 249 actions: [ 250 { 251 action: 'open', 252 title: 'æ¥ç', 253 icon: '/images/favicon/favicon-16x16.png' 254 } 255 ] 256 }; 257 258 event.waitUntil( 259 self.registration.showNotification(data.title || 'XOIGAC.TV', options) 260 ); 261 } catch (error) { 262 console.error('[SW] Failed to parse push data:', error); 263 264 // Fallback notification 265 event.waitUntil( 266 self.registration.showNotification('XOIGAC.TV', { 267 body: 'æ¨ææ°çéç¥', 268 icon: '/images/favicon/android-chrome-192x192.png', 269 requireInteraction: true, // ä¿æå±å¼ç¶æ 270 data: { url: '/' } 271 }) 272 ); 273 } 274 } else { 275 console.log('[SW] Push event has no data'); 276 277 // Default notification when no data 278 event.waitUntil( 279 self.registration.showNotification('XOIGAC.TV', { 280 body: 'æ¨ææ°çéç¥', 281 icon: '/images/favicon/android-chrome-192x192.png', 282 requireInteraction: true, // ä¿æå±å¼ç¶æ 283 data: { url: '/' } 284 }) 285 ); 286 } 287}); 288 289// éç¥ç¹å»äºä»¶ 290self.addEventListener('notificationclick', event => { 291 console.log('[SW] Notification click:', event.action); 292 event.notification.close(); 293 294 // Get URL from notification data 295 const url = event.notification.data?.url || '/'; 296 297 // ç¡®ä¿URLæ¯å®æ´çç»å¯¹è·¯å¾ 298 const targetUrl = url.startsWith('http') ? url : (self.location.origin + url); 299 300 event.waitUntil( 301 clients.matchAll({ 302 type: 'window', 303 includeUncontrolled: true 304 }).then(clientList => { 305 // Check if there's already a window/tab open 306 for (const client of clientList) { 307 if ('focus' in client && client.url.includes(self.location.origin)) { 308 client.focus(); 309 if (url !== '/') { 310 client.postMessage({ 311 type: 'NAVIGATE_TO', 312 url: url 313 }); 314 } 315 return; 316 } 317 } 318 319 // If no window is open, open a new one 320 if (clients.openWindow) { 321 return clients.openWindow(targetUrl); 322 } 323 }) 324 ); 325});
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.