player2 / sw.js
izuemon's picture
Update sw.js
6361991 verified
Raw
History Blame Contribute Delete
10 kB
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",
];
// ======================================================
// install
// ======================================================
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);
// Git LFS redirect対策
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');
})()
);
});
// ======================================================
// activate
// ======================================================
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');
})()
);
});
// ======================================================
// fetch
// ======================================================
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' }
});
}
}
// ======================================================
// HTML
// ======================================================
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'
}
}
);
}
}
// ======================================================
// static/media
// ======================================================
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);
}
// opaque + range warning
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);
}
// ======================================================
// network fallback
// ======================================================
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'
}
}
);
}
}
// ======================================================
// message
// ======================================================
self.addEventListener('message', (event) => {
console.log('[SW] message:', event.data);
// ------------------------------------------------------
// dynamic cache update
// ------------------------------------------------------
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');
// add new files
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();
}
})
);
// remove old files
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');
})()
);
}
// ------------------------------------------------------
// skip waiting
// ------------------------------------------------------
if (event.data?.type === 'SKIP_WAITING') {
console.log('[SW] skipWaiting');
self.skipWaiting();
}
});