|
1 | | -self.addEventListener('install', (ev) => { |
2 | | - // console.log('[service worker] installing'); |
3 | | - ev.waitUntil(self.skipWaiting()); |
| 1 | +const CACHE_NAME = 'webgal-critical-assets-v3'; |
| 2 | +const GAME_PREFIX = '/game/'; |
| 3 | +const CRITICAL_PATHS = ['/game/background/', '/game/figure/', '/game/bgm/', '/game/vocal/', '/game/video/']; |
| 4 | +const LOG_PREFIX = '[WebGAL SW]'; |
| 5 | +const loggedKeys = new Set(); |
| 6 | + |
| 7 | +function logOnce(key, ...args) { |
| 8 | + if (loggedKeys.has(key)) return; |
| 9 | + loggedKeys.add(key); |
| 10 | + console.log(LOG_PREFIX, ...args); |
| 11 | +} |
| 12 | + |
| 13 | +self.addEventListener('install', (event) => { |
| 14 | + logOnce('install', `install ${CACHE_NAME}`); |
| 15 | + event.waitUntil(self.skipWaiting()); |
4 | 16 | }); |
5 | 17 |
|
6 | | -// fetch事件是每次页面请求资源时触发的 |
7 | | -self.addEventListener('fetch', function (event) { |
8 | | - const url = event.request.url; |
9 | | - const isReturnCache = !!(url.match('/assets/') && !url.match('game')); |
10 | | - if (isReturnCache) { |
11 | | - // console.log('%cCACHED: ' + url, 'color: #005CAF; padding: 2px;'); |
| 18 | +self.addEventListener('activate', (event) => { |
| 19 | + logOnce('activate', `activate ${CACHE_NAME}`); |
| 20 | + event.waitUntil( |
| 21 | + (async () => { |
| 22 | + const keys = await caches.keys(); |
| 23 | + await Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))); |
| 24 | + await self.clients.claim(); |
| 25 | + })(), |
| 26 | + ); |
| 27 | +}); |
| 28 | + |
| 29 | +function isCriticalGameRequest(request) { |
| 30 | + if (request.method !== 'GET') return false; |
| 31 | + const url = new URL(request.url); |
| 32 | + if (url.origin !== self.location.origin) return false; |
| 33 | + if (!url.pathname.startsWith(GAME_PREFIX)) return false; |
| 34 | + return CRITICAL_PATHS.some((prefix) => url.pathname.startsWith(prefix)); |
| 35 | +} |
| 36 | + |
| 37 | +async function cacheFirst(request) { |
| 38 | + const cache = await caches.open(CACHE_NAME); |
| 39 | + const cached = await cache.match(request.url); |
| 40 | + if (cached) { |
| 41 | + logOnce(`hit:${request.url}`, 'cache hit:', new URL(request.url).pathname); |
| 42 | + return cached; |
12 | 43 | } |
13 | | - if (!isReturnCache) { |
14 | | - event.respondWith(fetch(event.request)); |
15 | | - } else { |
16 | | - event.respondWith( |
17 | | - // 检查在缓存中是否有匹配的资源 |
18 | | - caches.match(event.request).then(function (response) { |
19 | | - // 如果缓存中有匹配的资源,则返回缓存资源 |
20 | | - if (response) { |
21 | | - return response; |
22 | | - } |
23 | | - // 如果没有匹配的资源,则尝试从网络请求 |
24 | | - // 同时将获取的资源存入缓存 |
25 | | - return fetch(event.request) |
26 | | - .then(function (networkResponse) { |
27 | | - console.log('%cCACHED: ' + url, 'color: #005CAF; padding: 2px;'); |
28 | | - if (networkResponse.status === 206 && event.request.headers.has('range')) { |
29 | | - // 如果是部分响应且请求带有Range头,则创建新的请求,将完整响应返回给客户端 |
30 | | - // eslint-disable-next-line max-nested-callbacks |
31 | | - return fetch(event.request.url).then(function (fullNetworkResponse) { |
32 | | - const headers = {}; |
33 | | - for (let entry of fullNetworkResponse.headers.entries()) { |
34 | | - headers[entry[0]] = entry[1]; |
35 | | - } |
36 | | - const fullResponse = new Response(fullNetworkResponse.body, { |
37 | | - status: fullNetworkResponse.status, |
38 | | - statusText: fullNetworkResponse.statusText, |
39 | | - headers: headers, |
40 | | - }); |
41 | | - const clonedResponse = fullResponse.clone(); |
42 | | - // eslint-disable-next-line max-nested-callbacks |
43 | | - caches.open('my-cache').then(function (cache) { |
44 | | - cache.put(event.request, clonedResponse); |
45 | | - }); |
46 | | - return fullResponse; |
47 | | - }); |
48 | | - } |
49 | | - const clonedResponse = networkResponse.clone(); |
50 | | - // eslint-disable-next-line max-nested-callbacks |
51 | | - caches.open('my-cache').then(function (cache) { |
52 | | - cache.put(event.request, clonedResponse); |
53 | | - }); |
54 | | - return networkResponse; |
55 | | - }) |
56 | | - .catch(function (error) { |
57 | | - console.error('Fetching failed:', error); |
58 | | - throw error; |
59 | | - }); |
60 | | - }), |
61 | | - ); |
| 44 | + |
| 45 | + const response = await fetch(request); |
| 46 | + if (response.ok && response.status === 200) { |
| 47 | + await cache.put(request.url, response.clone()); |
| 48 | + logOnce(`cache:${request.url}`, 'cached:', new URL(request.url).pathname); |
62 | 49 | } |
| 50 | + return response; |
| 51 | +} |
| 52 | + |
| 53 | +self.addEventListener('fetch', (event) => { |
| 54 | + const { request } = event; |
| 55 | + if (!isCriticalGameRequest(request)) return; |
| 56 | + |
| 57 | + // Audio/video range requests are passed through to avoid partial-content edge cases. |
| 58 | + if (request.headers.has('range')) { |
| 59 | + logOnce(`range:${request.url}`, 'range passthrough:', new URL(request.url).pathname); |
| 60 | + event.respondWith(fetch(request)); |
| 61 | + return; |
| 62 | + } |
| 63 | + |
| 64 | + event.respondWith( |
| 65 | + cacheFirst(request).catch(() => { |
| 66 | + return fetch(request); |
| 67 | + }), |
| 68 | + ); |
63 | 69 | }); |
0 commit comments