diff --git a/search-app/server.js b/search-app/server.js index 1ffc8ca..3b0da82 100644 --- a/search-app/server.js +++ b/search-app/server.js @@ -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 = ` +Resetting… + + + +
+
Clearing caches…
+ +`; + 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); } });