PageSourceSearch

https://xoigac.top/sw.js

js xoigac.top collected 2026-10-10 02:22:06 UTC 9,628 bytes, 325 lines download raw bytes

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.