feat: /__reset recovery page + no-store on 5xx responses
/__reset returns a self-contained page that unregisters every service worker, clears every cache, drops the heatmap API key, and reloads. Use when a PWA is stuck on a bad cached chunk or SW state — visiting photos.jeffemmett.com/__reset (even inside the broken PWA) fixes it. Also mark any upstream 5xx response as no-store so a transient error during a container restart can't get pinned into a browser or CDN cache and brick the PWA long-term. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
af3468e0d5
commit
76f8816b82
|
|
@ -70,7 +70,65 @@ function immichRequest(method, apiPath, headers, body) {
|
|||
});
|
||||
}
|
||||
|
||||
// Standalone recovery page: unregister service workers, clear all caches,
|
||||
// clear storage, and reload. Use when a PWA is stuck on a broken cached
|
||||
// chunk or service worker state.
|
||||
const RESET_HTML = `<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><title>Resetting…</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background:#111; color:#eee;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
height:100vh; margin:0; flex-direction:column; gap:16px; padding:20px; }
|
||||
.spinner { width:40px; height:40px; border:4px solid #333;
|
||||
border-top-color:#e94560; border-radius:50%;
|
||||
animation: spin 0.8s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.status { font-size:15px; text-align:center; max-width:320px; }
|
||||
a { color:#e94560; }
|
||||
</style>
|
||||
</head><body>
|
||||
<div class="spinner"></div>
|
||||
<div class="status" id="status">Clearing caches…</div>
|
||||
<script>
|
||||
(async function(){
|
||||
const s = document.getElementById('status');
|
||||
const log = (m) => { s.textContent = m; };
|
||||
try {
|
||||
if ('serviceWorker' in navigator) {
|
||||
log('Unregistering service workers…');
|
||||
const regs = await navigator.serviceWorker.getRegistrations();
|
||||
await Promise.all(regs.map(r => r.unregister().catch(() => null)));
|
||||
}
|
||||
if (window.caches) {
|
||||
log('Clearing caches…');
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map(k => caches.delete(k).catch(() => null)));
|
||||
}
|
||||
try { localStorage.removeItem('ls-heatmap-api-key'); } catch {}
|
||||
} catch (e) {
|
||||
log('Error: ' + e.message + ' — reloading anyway.');
|
||||
}
|
||||
log('Done. Reloading…');
|
||||
setTimeout(() => {
|
||||
location.replace('/?_ts=' + Date.now());
|
||||
}, 500);
|
||||
})();
|
||||
</script>
|
||||
</body></html>`;
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
// --- Recovery page: /__reset ---
|
||||
if ((req.url === '/__reset' || req.url.startsWith('/__reset?')) && req.method === 'GET') {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
||||
'Service-Worker-Allowed': '/'
|
||||
});
|
||||
res.end(RESET_HTML);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Custom endpoint: report current injected-script version ---
|
||||
if (req.url === '/api/custom/inject-version' && req.method === 'GET') {
|
||||
res.writeHead(200, {
|
||||
|
|
@ -184,7 +242,16 @@ const server = http.createServer((req, res) => {
|
|||
});
|
||||
} else {
|
||||
// Stream non-HTML responses directly (videos, images, API JSON)
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
const streamHeaders = { ...proxyRes.headers };
|
||||
// Don't let browsers or CDNs cache error responses — a 5xx
|
||||
// that sneaks into a long-lived cache entry can brick the PWA.
|
||||
if (proxyRes.statusCode >= 500) {
|
||||
streamHeaders['cache-control'] = 'no-store, no-cache, must-revalidate';
|
||||
delete streamHeaders['expires'];
|
||||
delete streamHeaders['etag'];
|
||||
delete streamHeaders['last-modified'];
|
||||
}
|
||||
res.writeHead(proxyRes.statusCode, streamHeaders);
|
||||
proxyRes.pipe(res);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue