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
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/player/sw.js').then(
|
||||
() => console.log('Service Worker registered'),
|
||||
(err) => console.warn('SW registration failed:', err)
|
||||
);
|
||||
navigator.serviceWorker.register('/player/sw.js').then(reg => {
|
||||
console.log('Service Worker registered');
|
||||
// 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 ====================
|
||||
|
|
|
|||
|
|
@ -1,61 +1,29 @@
|
|||
const CACHE_NAME = 'rd-player-v4';
|
||||
const CONTENT_CACHE = 'rd-content-v1';
|
||||
const CACHE_NAME = 'rd-player-v5';
|
||||
|
||||
// Install: skip waiting to activate immediately
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
// Activate: clean old caches, claim clients
|
||||
// Activate: clean old caches (including old content cache), claim clients
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
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())
|
||||
);
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Only handle GET requests — let POST/PUT/DELETE pass through
|
||||
// Only handle GET requests
|
||||
if (event.request.method !== 'GET') return;
|
||||
|
||||
// Content files (videos, images): cache-first for offline playback
|
||||
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;
|
||||
}
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Player page and static assets: network-first, fall back to cache
|
||||
if (url.pathname.startsWith('/player') || url.pathname === '/socket.io/socket.io.js') {
|
||||
|
|
@ -79,6 +47,6 @@ self.addEventListener('fetch', (event) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Everything else: network only, don't intercept failures
|
||||
// (Returning without calling event.respondWith lets the browser handle it natively)
|
||||
// Everything else (content files, API calls, etc.): don't intercept.
|
||||
// Returning without event.respondWith lets the browser handle it natively.
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue