From 76f8816b82b18b613fc407fa4e2ac28cce93a4d3 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 17 Apr 2026 13:05:43 -0400 Subject: [PATCH] feat: /__reset recovery page + no-store on 5xx responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /__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) --- search-app/server.js | 69 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) 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); } });