mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Simplify service worker: stop intercepting content requests
The SW was causing "unexpected error" on video/image fetches due to range request handling, opaque response caching, and stale SW races. Fix: SW now ONLY caches player page + socket.io JS for offline boot. Content files are left to browser native HTTP cache (server already sets Cache-Control: public, max-age=2592000, immutable). Also: auto-reload player when new SW activates so deploys take effect immediately without manual hard refresh. Bumped cache to v5 — activate purges all old caches (including the broken rd-content-v1 content cache). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b4ac2fb821
commit
d73abc809d
|
|
@ -952,10 +952,21 @@
|
||||||
|
|
||||||
// Register service worker for offline content caching
|
// Register service worker for offline content caching
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/player/sw.js').then(
|
navigator.serviceWorker.register('/player/sw.js').then(reg => {
|
||||||
() => console.log('Service Worker registered'),
|
console.log('Service Worker registered');
|
||||||
(err) => console.warn('SW registration failed:', err)
|
// When a new SW activates, reload so the fresh code takes effect immediately
|
||||||
);
|
reg.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = reg.installing;
|
||||||
|
if (newWorker) {
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'activated' && navigator.serviceWorker.controller) {
|
||||||
|
console.log('New Service Worker activated — reloading for fresh code');
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, (err) => console.warn('SW registration failed:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Keyboard shortcuts ====================
|
// ==================== Keyboard shortcuts ====================
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,29 @@
|
||||||
const CACHE_NAME = 'rd-player-v4';
|
const CACHE_NAME = 'rd-player-v5';
|
||||||
const CONTENT_CACHE = 'rd-content-v1';
|
|
||||||
|
|
||||||
// Install: skip waiting to activate immediately
|
// Install: skip waiting to activate immediately
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Activate: clean old caches, claim clients
|
// Activate: clean old caches (including old content cache), claim clients
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener('activate', (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.keys().then(keys => Promise.all(
|
caches.keys().then(keys => Promise.all(
|
||||||
keys.filter(k => k !== CACHE_NAME && k !== CONTENT_CACHE).map(k => caches.delete(k))
|
keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
|
||||||
)).then(() => self.clients.claim())
|
)).then(() => self.clients.claim())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch handler
|
// Fetch handler — ONLY cache player page and static assets.
|
||||||
|
// Content files (/uploads/content/) are NOT intercepted — the server sets
|
||||||
|
// Cache-Control: public, max-age=2592000, immutable which lets the browser
|
||||||
|
// cache them natively without SW complications (range requests, opaque
|
||||||
|
// responses, video seeking, etc.)
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener('fetch', (event) => {
|
||||||
const url = new URL(event.request.url);
|
// Only handle GET requests
|
||||||
|
|
||||||
// Only handle GET requests — let POST/PUT/DELETE pass through
|
|
||||||
if (event.request.method !== 'GET') return;
|
if (event.request.method !== 'GET') return;
|
||||||
|
|
||||||
// Content files (videos, images): cache-first for offline playback
|
const url = new URL(event.request.url);
|
||||||
if (url.pathname.startsWith('/uploads/content/')) {
|
|
||||||
// Skip range requests (video seeking) — serve from network, don't cache partial responses
|
|
||||||
if (event.request.headers.get('range')) {
|
|
||||||
return; // Let the browser handle range requests directly
|
|
||||||
}
|
|
||||||
|
|
||||||
event.respondWith(
|
|
||||||
caches.open(CONTENT_CACHE).then(cache =>
|
|
||||||
cache.match(event.request, { ignoreSearch: true }).then(cached => {
|
|
||||||
if (cached) return cached;
|
|
||||||
return fetch(event.request).then(response => {
|
|
||||||
// Only cache successful, complete (non-opaque) responses
|
|
||||||
if (response.ok && response.status === 200 && response.type !== 'opaque') {
|
|
||||||
cache.put(event.request, response.clone());
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}).catch(() => {
|
|
||||||
return new Response('Content unavailable offline', {
|
|
||||||
status: 503,
|
|
||||||
statusText: 'Service Unavailable',
|
|
||||||
headers: { 'Content-Type': 'text/plain' }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
).catch(() => {
|
|
||||||
// Cache API itself failed — fall through to network
|
|
||||||
return fetch(event.request).catch(() =>
|
|
||||||
new Response('Offline', { status: 503, headers: { 'Content-Type': 'text/plain' } })
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player page and static assets: network-first, fall back to cache
|
// Player page and static assets: network-first, fall back to cache
|
||||||
if (url.pathname.startsWith('/player') || url.pathname === '/socket.io/socket.io.js') {
|
if (url.pathname.startsWith('/player') || url.pathname === '/socket.io/socket.io.js') {
|
||||||
|
|
@ -79,6 +47,6 @@ self.addEventListener('fetch', (event) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything else: network only, don't intercept failures
|
// Everything else (content files, API calls, etc.): don't intercept.
|
||||||
// (Returning without calling event.respondWith lets the browser handle it natively)
|
// Returning without event.respondWith lets the browser handle it natively.
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue