diff --git a/deployment/cloud/admin.mjs b/deployment/cloud/admin.mjs index a39939e50..464063eca 100644 --- a/deployment/cloud/admin.mjs +++ b/deployment/cloud/admin.mjs @@ -22,10 +22,23 @@ const groupsOf = (req) => (req.headers.get('x-forwarded-groups') || '').split(', const isAdmin = (req) => ADMIN.length > 0 && groupsOf(req).some((g) => ADMIN.includes(g)) const text = (s, status = 200) => new Response(s + '\n', { status, headers: { 'content-type': 'text/plain; charset=utf-8' } }) +// fetch() hands back the body DECOMPRESSED, so the upstream Content-Encoding and Content-Length no longer describe +// what is sent on: forwarding them made browsers try to gunzip plain HTML, and PocketBase's dashboard never rendered +// (rega, 2026-09-29). And PocketBase redirects to its own root (/_ -> /_/), which is put back under the mount (/pb/_/). async function proxy(req, url, prefix, port) { const target = new URL(url.pathname.slice(prefix.length - 1) + url.search, `http://127.0.0.1:${port}`) const r = await fetch(target, { method: req.method, headers: req.headers, body: ['GET', 'HEAD'].includes(req.method) ? undefined : req.body, redirect: 'manual' }) - return new Response(r.body, { status: r.status, headers: r.headers }) + const headers = new Headers(r.headers) + headers.delete('content-encoding') + headers.delete('content-length') + const loc = headers.get('location') + if (loc) { + const mount = prefix.slice(0, -1) // '/pb/' -> '/pb' + const upstream = `http://127.0.0.1:${port}` + const path = loc.startsWith(upstream) ? loc.slice(upstream.length) || '/' : loc + if (path.startsWith('/') && !path.startsWith('//') && !path.startsWith(`${mount}/`)) headers.set('location', mount + path) + } + return new Response(r.body, { status: r.status, headers }) } // One public build at a time (build-cdn.sh also holds a lock), started by an admin or by the watcher below. diff --git a/deployment/cloud/embed-test.html b/deployment/cloud/embed-test.html index 7f5a60972..e5669e4f3 100644 --- a/deployment/cloud/embed-test.html +++ b/deployment/cloud/embed-test.html @@ -59,6 +59,10 @@ const r = await fetch(`${base}/manifest.json`, { cache: 'no-cache' }) if (!r.ok) throw new Error(`HTTP ${r.status}`) manifest = await r.json() + // The data API the maps read (people groups, statistics). Every generated page and copy-paste snippet sets it + // before its bundle loads; this page builds its cards itself, so it sets it from the manifest. Without it the + // bundles asked this host's own /api, which the research host doesn't serve (404s, fewer nodes drawn). + if (!window.MAP_APP_API_URL && manifest.runtime && manifest.runtime.apiBaseUrl) window.MAP_APP_API_URL = manifest.runtime.apiBaseUrl } catch (e) { status.textContent = `The manifest did not load from ${base}/manifest.json (${e.message}).` return