| const CACHE_NAME = 'media-player-cache-v2'; |
|
|
| let cachedFiles = [ |
| "/index.html", |
| "/version.txt", |
|
|
| "/eruda.js", |
|
|
| "/f/sa.opus", |
| "/f/sv.mp4", |
| |
| "/f/aa.opus", |
| "/f/av.mp4", |
|
|
| "/f/ta.opus", |
| "/f/tv.mp4", |
|
|
| "/fonts/MPLUSRounded1c-Bold.ttf", |
| |
| "/f/piano.mid", |
| "/mei-ver.mei", |
| |
| "/midipane.js", |
| "/musicxmlpane.js", |
| "/tools-bg-sw-metronome.js", |
| |
| "/Midi.js", |
| "/CustomEase.min.js", |
| "/draggabilly.pkgd.min.js", |
| "/gsap.js", |
| "/verovio-toolkit-wasm.js", |
| ]; |
|
|
|
|
|
|
| |
| |
| |
|
|
| self.addEventListener('install', (event) => { |
|
|
| console.log('[SW] install'); |
|
|
| self.skipWaiting(); |
|
|
| event.waitUntil( |
| (async () => { |
|
|
| const cache = await caches.open(CACHE_NAME); |
|
|
| console.log('[SW] precache start'); |
|
|
| await Promise.allSettled( |
|
|
| cachedFiles.map(async (file) => { |
|
|
| try { |
|
|
| console.group('[SW] PRECACHE'); |
|
|
| console.log('file:', file); |
|
|
| |
| const response = await fetch(file, { |
| mode: 'no-cors' |
| }); |
|
|
| console.log('response.type:', response.type); |
| console.log('response.status:', response.status); |
| console.log('response.url:', response.url); |
|
|
| const request = new Request(file); |
|
|
| await cache.put(request, response); |
|
|
| console.log('cache.put success'); |
|
|
| console.groupEnd(); |
|
|
| } catch (err) { |
|
|
| console.error('[SW] precache failed:', file, err); |
|
|
| console.groupEnd(); |
| } |
| }) |
| ); |
|
|
| console.log('[SW] precache complete'); |
|
|
| })() |
| ); |
| }); |
|
|
|
|
|
|
| |
| |
| |
|
|
| self.addEventListener('activate', (event) => { |
|
|
| console.log('[SW] activate'); |
|
|
| event.waitUntil( |
|
|
| (async () => { |
|
|
| const keys = await caches.keys(); |
|
|
| await Promise.all( |
|
|
| keys.map((key) => { |
|
|
| if (key !== CACHE_NAME) { |
|
|
| console.log('[SW] delete old cache:', key); |
|
|
| return caches.delete(key); |
| } |
| }) |
| ); |
|
|
| await self.clients.claim(); |
|
|
| console.log('[SW] clients claimed'); |
|
|
| })() |
| ); |
| }); |
|
|
|
|
|
|
| |
| |
| |
|
|
| self.addEventListener('fetch', (event) => { |
|
|
| event.respondWith(handleRequest(event.request)); |
| }); |
|
|
|
|
|
|
| async function handleRequest(req) { |
|
|
| const url = new URL(req.url); |
|
|
| console.group('[SW FETCH]'); |
|
|
| console.log('url:', url.href); |
| console.log('method:', req.method); |
| console.log('destination:', req.destination); |
|
|
| const range = req.headers.get('range'); |
|
|
| if (range) { |
| console.warn('Range Request:', range); |
| } |
|
|
| const accept = req.headers.get('accept') || ''; |
|
|
| const cache = await caches.open(CACHE_NAME); |
|
|
|
|
| if (url.pathname === '/p1/version.txt') { |
| const normalizedRequest = new Request('/p1/version.txt', { |
| method: 'GET' |
| }); |
|
|
| const cacheResp = await cache.match(normalizedRequest); |
| if (cacheResp) return cacheResp; |
|
|
| try { |
| const networkResp = await fetch(req); |
| await cache.put(normalizedRequest, networkResp.clone()); |
| return networkResp; |
| } catch { |
| return new Response('Offline and no cached version.txt', { |
| status: 504, |
| headers: { 'content-type': 'text/plain' } |
| }); |
| } |
| } |
|
|
| |
| |
| |
|
|
| if (accept.includes('text/html')) { |
|
|
| console.log('strategy: network-first'); |
|
|
| try { |
|
|
| const networkResponse = await fetch(req); |
|
|
| console.log('network success'); |
|
|
| console.log('network.type:', networkResponse.type); |
| console.log('network.status:', networkResponse.status); |
|
|
| try { |
|
|
| await cache.put(req, networkResponse.clone()); |
|
|
| console.log('cache updated'); |
|
|
| } catch (cacheErr) { |
|
|
| console.error('cache.put failed:', cacheErr); |
| } |
|
|
| console.groupEnd(); |
|
|
| return networkResponse; |
|
|
| } catch (networkErr) { |
|
|
| console.error('network failed:', networkErr); |
|
|
| const cacheResp = await cache.match(req.url); |
|
|
| if (cacheResp) { |
|
|
| console.log('fallback cache hit'); |
|
|
| console.groupEnd(); |
|
|
| return cacheResp; |
| } |
|
|
| const fallback = await cache.match('/p1/index.html'); |
|
|
| if (fallback) { |
|
|
| console.log('fallback index.html'); |
|
|
| console.groupEnd(); |
|
|
| return fallback; |
| } |
|
|
| console.error('no html fallback'); |
|
|
| console.groupEnd(); |
|
|
| return new Response( |
| 'Offline and no cached HTML', |
| { |
| status: 504, |
| headers: { |
| 'content-type': 'text/plain' |
| } |
| } |
| ); |
| } |
| } |
|
|
|
|
|
|
| |
| |
| |
|
|
| console.log('strategy: cache-first'); |
|
|
| try { |
|
|
| const normalizedUrl = new URL(req.url); |
| normalizedUrl.search = ''; |
| |
| const cacheResp = await cache.match( |
| normalizedUrl.pathname |
| ); |
|
|
| if (cacheResp) { |
|
|
| console.log('CACHE HIT'); |
|
|
| console.log('cache.type:', cacheResp.type); |
| console.log('cache.status:', cacheResp.status); |
|
|
| try { |
|
|
| console.log( |
| 'content-type:', |
| cacheResp.headers.get('content-type') |
| ); |
|
|
| console.log( |
| 'content-length:', |
| cacheResp.headers.get('content-length') |
| ); |
|
|
| console.log( |
| 'accept-ranges:', |
| cacheResp.headers.get('accept-ranges') |
| ); |
|
|
| } catch (headerErr) { |
|
|
| console.warn('header read failed:', headerErr); |
| } |
|
|
| |
| if (range && cacheResp.type === 'opaque') { |
|
|
| console.error( |
| '[SW] opaque response cannot properly handle range requests offline' |
| ); |
| } |
|
|
| console.groupEnd(); |
|
|
| return cacheResp; |
| } |
|
|
| console.warn('CACHE MISS'); |
|
|
| } catch (cacheErr) { |
|
|
| console.error('cache.match failed:', cacheErr); |
| } |
|
|
|
|
|
|
| |
| |
| |
|
|
| try { |
|
|
| const networkResp = await fetch(req); |
|
|
| console.log('NETWORK OK'); |
|
|
| console.log('network.type:', networkResp.type); |
| console.log('network.status:', networkResp.status); |
|
|
| try { |
|
|
| await cache.put(req, networkResp.clone()); |
|
|
| console.log('cache.put success'); |
|
|
| } catch (cachePutErr) { |
|
|
| console.error('cache.put failed:', cachePutErr); |
| } |
|
|
| console.groupEnd(); |
|
|
| return networkResp; |
|
|
| } catch (networkErr) { |
|
|
| console.error('NETWORK FAILED'); |
|
|
| console.error(networkErr); |
|
|
| console.groupEnd(); |
|
|
| return new Response( |
|
|
| JSON.stringify({ |
| error: 'offline', |
| url: req.url, |
| range: range, |
| destination: req.destination, |
| timestamp: Date.now() |
| }, null, 2), |
|
|
| { |
| status: 504, |
|
|
| headers: { |
| 'content-type': 'application/json' |
| } |
| } |
| ); |
| } |
| } |
|
|
|
|
|
|
| |
| |
| |
|
|
| self.addEventListener('message', (event) => { |
|
|
| console.log('[SW] message:', event.data); |
|
|
| |
| |
| |
|
|
| if (event.data?.type === 'CACHE_FILES') { |
|
|
| const files = event.data.files || []; |
|
|
| cachedFiles = [ |
| '/p1/index.html', |
| ...files |
| ]; |
|
|
| event.waitUntil( |
|
|
| (async () => { |
|
|
| const cache = await caches.open(CACHE_NAME); |
|
|
| console.log('[SW] updating cache list'); |
|
|
| |
| await Promise.allSettled( |
|
|
| cachedFiles.map(async (file) => { |
|
|
| try { |
|
|
| console.group('[SW] CACHE_FILES'); |
|
|
| console.log('file:', file); |
|
|
| const response = await fetch(file, { |
| mode: 'no-cors' |
| }); |
|
|
| console.log('response.type:', response.type); |
| console.log('response.status:', response.status); |
|
|
| const request = new Request(file); |
| |
| await cache.put(request, response); |
|
|
| console.log('cache.put success'); |
|
|
| console.groupEnd(); |
|
|
| } catch (err) { |
|
|
| console.error('cache add failed:', file, err); |
|
|
| console.groupEnd(); |
| } |
| }) |
| ); |
|
|
|
|
|
|
| |
| const keys = await cache.keys(); |
|
|
| const validPaths = new Set( |
|
|
| cachedFiles.map((p) => { |
|
|
| try { |
|
|
| return ( |
| new URL(p, self.location.origin).pathname + |
| (p.includes('?') |
| ? '?' + p.split('?')[1] |
| : '') |
| ); |
|
|
| } catch { |
|
|
| return p; |
| } |
| }) |
| ); |
|
|
|
|
|
|
| for (const request of keys) { |
|
|
| const url = new URL(request.url); |
|
|
| const pathWithQuery = |
| url.pathname + |
| (url.search ? url.search : ''); |
|
|
| if ( |
| !validPaths.has(pathWithQuery) && |
| !validPaths.has(url.pathname) |
| ) { |
|
|
| console.log('[SW] deleting old cache:', pathWithQuery); |
|
|
| await cache.delete(request); |
| } |
| } |
|
|
| console.log('[SW] cache update complete'); |
|
|
| })() |
| ); |
| } |
|
|
|
|
|
|
| |
| |
| |
|
|
| if (event.data?.type === 'SKIP_WAITING') { |
|
|
| console.log('[SW] skipWaiting'); |
|
|
| self.skipWaiting(); |
| } |
| }); |