fix(gateway): 302 to trailing slash on directory-index rescues

/bns/sirius.x/theseus was resolving to theseus/index.html but served in
place under the old URL. The injected <base> then stayed at the parent
directory (/bns/sirius.x/), so the sub-page's own relative references
went wrong in two visible ways:

  * <link rel=icon href="./assets/favicon.svg"> 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.
This commit is contained in:
Local Dev 2026-09-20 20:09:58 +02:00
parent e130596584
commit 79a8a6fa16

View file

@ -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 <base> 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}`);