From 79a8a6fa1699f74b9199a5bf77ef5c7fc2d950a6 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 20 Sep 2026 20:09:58 +0200 Subject: [PATCH] fix(gateway): 302 to trailing slash on directory-index rescues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /bns/sirius.x/theseus was resolving to theseus/index.html but served in place under the old URL. The injected then stayed at the parent directory (/bns/sirius.x/), so the sub-page's own relative references went wrong in two visible ways: * loaded the SIRIUS favicon, not the theseus one — visible as the theseus subdirectory "borrowing" its parent's icon. * href="../market/" inside theseus/index.html resolved to /bns/market/ (dropping the sirius.x name) — reported as "the market link goes to /bns/market/ with usage /bns/./ as result". Fix: when the directory-index fallback fires, 302 to add the trailing slash instead of serving in place. The browser follows the redirect to /bns/sirius.x/theseus/, my per-directory baseHref computes correctly there, and every relative reference lands where the source expects. Same behaviour as nginx `try_files ... /$uri/`. Flat-file rescues (/tld -> tld.html) and SPA fallbacks keep serving in place — those don't have the base-mismatch problem. Verified live: /bns/sirius.x/theseus 302 -> /theseus/; theseus's own favicon loads; ../market/, ../tld, ../portal all stay inside sirius.x. --- src/gateway/public-gateway.mjs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/gateway/public-gateway.mjs b/src/gateway/public-gateway.mjs index c83eacd..6d7b4b2 100644 --- a/src/gateway/public-gateway.mjs +++ b/src/gateway/public-gateway.mjs @@ -507,19 +507,30 @@ async function serve(res, name, reqPath, baseHref) { const cleanPath = reqPath.slice(1).replace(/\/+$/, ""); // 1) Extensionless-URL support: /tld -> tld.html. Lets sites // publish clean URLs without keeping mirror files around. + // Base injection stays anchored at the directory of reqPath, + // which is correct because tld.html is a flat file. if (!reqPath.endsWith("/")) { const htmlKey = cleanBase + cleanPath + ".html"; const htmlHit = await aws.fetch(`${S3_ENDPOINT}/${htmlKey}`); if (htmlHit.ok) { up = htmlHit; } } - // 2) Directory-index: /docs -> docs/index.html + // 2) Directory-index: /docs -> docs/index.html. Redirect to add + // the trailing slash instead of serving in place — otherwise + // the injected stays at the parent directory and any + // relative asset/href inside the served page (e.g. + // ./assets/favicon.svg on /theseus/index.html) resolves to + // the WRONG directory. nginx behaves the same way. if (up.status === 404 && !reqPath.endsWith("/")) { const dirKey = cleanBase + cleanPath + "/index.html"; const dir = await aws.fetch(`${S3_ENDPOINT}/${dirKey}`); - if (dir.ok) { up = dir; } + if (dir.ok) { + res.writeHead(302, { location: `${MOUNT}/${name}${reqPath}/` }); + return res.end(); + } } // 3) SPA root fallback — last resort so client-side routers - // can pick up /katalogos, /anthemus, etc. + // can pick up /katalogos, /anthemus, etc. Base stays at the + // site root, which is what SPAs expect. if (up.status === 404) { const fallbackKey = cleanBase + "index.html"; const fb = await aws.fetch(`${S3_ENDPOINT}/${fallbackKey}`);