From dcc629cad23ce0ca89aabb6f8b1eacfde599774e Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sun, 31 May 2026 22:35:00 +0200 Subject: [PATCH] fix(csp): external bootstrap script to survive strict reverse-proxy CSP (#564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit demo.picpeak.app sits behind Caddy + Cloudflare; Caddy replaces the nginx CSP entirely with one that omits 'unsafe-inline' / hash / nonce, so the #358 inline theme-bootstrap was being blocked there — admin loaded a black page, the SPA bundle 404'd, link buttons did nothing. Move the bootstrap to /public/bootstrap.js served as 'self' so the script runs under every reasonable CSP without further coordination. Vite copies /public/* to the dist root at build time (same pipeline as /favicon-32x32.png), and it remains in without defer/async so it still runs before paints. The OS-preference @media CSS above still handles the first-frame dark/light baseline. --- frontend/index.html | 36 ++++++++---------------------------- frontend/public/bootstrap.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 frontend/public/bootstrap.js diff --git a/frontend/index.html b/frontend/index.html index 9af5be8f..0e9b724a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -39,9 +39,9 @@ +
diff --git a/frontend/public/bootstrap.js b/frontend/public/bootstrap.js new file mode 100644 index 00000000..f40e14b2 --- /dev/null +++ b/frontend/public/bootstrap.js @@ -0,0 +1,32 @@ +/* + * Pre-React theme bootstrap (#358). + * + * Loaded as an external script (rather than inline) so a strict CSP + * with no 'unsafe-inline' / hash / nonce — like the one Caddy puts in + * front of demo.picpeak.app — does not block it (#564). + * + * Reads the per-gallery cached background written by ThemeContext on + * the previous visit and applies it before React mounts, so revisits + * land on the right colour from the first frame. The OS-preference + * default is already handled by the @media CSS in index.html for + * first-visit / cache-miss callers. + * + * Placed in /public so vite copies it to /bootstrap.js at build time + * (same pipeline as /favicon-32x32.png). Kept in without + * defer/async so it runs before paints. + */ +(function () { + try { + var m = location.pathname.match(/\/gallery\/([^\/?#]+)/); + var bg = null; + if (m && m[1]) { + bg = localStorage.getItem('gallery-theme-bg-' + decodeURIComponent(m[1])); + } + if (bg) { + var root = document.documentElement; + root.style.backgroundColor = bg; + document.body && (document.body.style.backgroundColor = bg); + root.style.setProperty('--color-background', bg); + } + } catch (e) { /* never block render on a cache miss */ } +})();