mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Fix broken service worker + device auth rejection on playlist refresh
Bug 1 (SW): Rewrote service worker fetch handler: - Skip range requests (video seeking) to avoid caching partial responses - Skip non-GET requests entirely - Use ignoreSearch on cache match to avoid query-param misses - Don't cache opaque cross-origin responses - Outer catch on Cache API failures - Don't intercept catch-all requests (let browser handle natively) - Bump cache version to v4 to purge broken cached responses Bug 2 (auth): Playlist refresh register was missing device_token, causing auth rejection every 5 minutes. Fixed by including token in the refresh-register emit. Added diagnostic logging on both client and server for token validation failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dc7450b6a7
commit
b4ac2fb821
|
|
@ -421,6 +421,7 @@
|
|||
};
|
||||
// Browser fingerprint (survives localStorage clear)
|
||||
data.fingerprint = generateBrowserFingerprint();
|
||||
console.log(`[register] device_id=${data.device_id || 'none'}, has_token=${!!data.device_token}, token_len=${data.device_token?.length || 0}, paired=${config.paired}, pairing_code=${data.pairing_code || 'none'}`);
|
||||
socket.emit('device:register', data);
|
||||
}
|
||||
|
||||
|
|
@ -455,7 +456,10 @@
|
|||
if (refreshTimer) clearInterval(refreshTimer);
|
||||
refreshTimer = setInterval(() => {
|
||||
if (socket?.connected && config.deviceId && config.paired) {
|
||||
socket.emit('device:register', { device_id: config.deviceId, device_info: {} });
|
||||
const data = { device_id: config.deviceId, device_info: {} };
|
||||
if (config.deviceToken) data.device_token = config.deviceToken;
|
||||
console.log(`[refresh-register] device_id=${config.deviceId}, has_token=${!!config.deviceToken}`);
|
||||
socket.emit('device:register', data);
|
||||
}
|
||||
}, 300000); // 5 minutes fallback
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const CACHE_NAME = 'rd-player-v3';
|
||||
const CACHE_NAME = 'rd-player-v4';
|
||||
const CONTENT_CACHE = 'rd-content-v1';
|
||||
|
||||
// Install: skip waiting to activate immediately
|
||||
|
|
@ -19,18 +19,39 @@ self.addEventListener('activate', (event) => {
|
|||
self.addEventListener('fetch', (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Content files (videos, images): cache on first fetch for offline playback
|
||||
// Only handle GET requests — let POST/PUT/DELETE pass through
|
||||
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.match(event.request).then(cached => {
|
||||
if (cached) return cached;
|
||||
return fetch(event.request).then(response => {
|
||||
if (response.ok) {
|
||||
const clone = response.clone();
|
||||
caches.open(CONTENT_CACHE).then(cache => cache.put(event.request, clone));
|
||||
}
|
||||
return response;
|
||||
}).catch(() => new Response('Offline', { status: 503 }));
|
||||
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;
|
||||
|
|
@ -40,16 +61,24 @@ self.addEventListener('fetch', (event) => {
|
|||
if (url.pathname.startsWith('/player') || url.pathname === '/socket.io/socket.io.js') {
|
||||
event.respondWith(
|
||||
fetch(event.request).then(response => {
|
||||
if (response.ok) {
|
||||
if (response.ok && response.type !== 'opaque') {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
|
||||
}
|
||||
return response;
|
||||
}).catch(() => caches.match(event.request).then(cached => cached || new Response('Offline', { status: 503 })))
|
||||
}).catch(() =>
|
||||
caches.match(event.request, { ignoreSearch: true }).then(cached =>
|
||||
cached || new Response('Offline', {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else: network only
|
||||
event.respondWith(fetch(event.request));
|
||||
// Everything else: network only, don't intercept failures
|
||||
// (Returning without calling event.respondWith lets the browser handle it natively)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ module.exports = function setupDeviceSocket(io) {
|
|||
if (device) {
|
||||
// Validate device token (skip for legacy devices that don't have a token yet)
|
||||
if (device.device_token && !validateDeviceToken(device_id, device_token)) {
|
||||
console.warn(`Invalid device token for ${device_id} from ${getClientIp(socket)}`);
|
||||
console.warn(`Invalid device token for ${device_id} from ${getClientIp(socket)} — received_len=${(device_token || '').length}, stored_len=${device.device_token.length}, received_prefix=${(device_token || '').substring(0, 8)}, stored_prefix=${device.device_token.substring(0, 8)}`);
|
||||
socket.emit('device:auth-error', { error: 'Invalid device token' });
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue