fix(theseus/bns): failed content fetches get a real error page naming the upstream and cause

A bns:// fetch that fails after the name resolved (relay unreachable, DNS
stalling, the site's own server down) used to answer with the bare text
"Theseus error: fetch failed", which reads as a broken browser. The
handler now returns a styled page that names the host, the upstream it
tried (navigate.st, the p-record origin or the ip record), the error and
its cause code, explains the likely reason per cause (unreachable vs DNS),
and offers a retry.
This commit is contained in:
Local Dev 2026-09-12 09:09:06 +02:00
parent 1596463b70
commit d7d127d7b4

30
main.js
View file

@ -1412,7 +1412,35 @@ async function serveBns(request) {
if (!isSubdomain && r.p) return await serveP(); if (!isSubdomain && r.p) return await serveP();
if (r.u) return Response.redirect(r.u, 302); if (r.u) return Response.redirect(r.u, 302);
return new Response(JSON.stringify(rec.entry, null, 2), { headers: { "content-type": "application/json" } }); return new Response(JSON.stringify(rec.entry, null, 2), { headers: { "content-type": "application/json" } });
} catch (e) { return new Response("Theseus error: " + e.message, { status: 502 }); } } catch (e) {
// The upstream fetch failed — relay unreachable, DNS stalling, site's
// own server down. A bare "fetch failed" reads as "Theseus is broken";
// name the upstream and the cause code so it reads as what it is,
// and give a retry.
const cause = e?.cause?.code || e?.cause?.message || "";
const upstream = r.s3 ? new URL(GATEWAY).host : r.p ? (() => { try { return new URL(r.p).host; } catch { return r.p; } })() : r.ip ? String(r.ip) : "";
return new Response(bnsErrorHtml({ host, message: e?.message || String(e), cause, upstream }),
{ status: 502, headers: { "content-type": "text/html; charset=utf-8" } });
}
}
// Error surface for a bns:// fetch that failed after the name resolved.
// Self-contained HTML (this is a protocol handler response, not a tab
// navigation, so error.html's loadFile path doesn't apply).
function bnsErrorHtml({ host, message, cause, upstream }) {
const esc = (s) => String(s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
const hint = /ETIMEDOUT|ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH/.test(cause)
? `Theseus resolved <b>${esc(host)}</b> but could not reach <b>${esc(upstream || "its server")}</b> from this network. Check your connection or VPN and try again.`
: /ENOTFOUND|EAI_AGAIN/.test(cause)
? `Your system DNS could not look up <b>${esc(upstream || "the server")}</b>. A stale or unreachable DNS server on one of your network adapters is the usual cause.`
: `Theseus resolved <b>${esc(host)}</b> but the content fetch from <b>${esc(upstream || "its server")}</b> failed.`;
return `<!doctype html><html><head><meta charset="utf-8"><title>Cant reach ${esc(host)}</title>
<style>:root{color-scheme:dark}body{margin:0;background:#0f1420;color:#e8ecf3;font:15px/1.5 system-ui,sans-serif;display:grid;place-items:center;min-height:100vh}
.card{max-width:560px;padding:32px 36px;background:#1b2330;border:1px solid #ffffff1f;border-radius:14px}h1{font-size:20px;margin:0 0 10px}p{margin:8px 0;color:#b9c2d0}
code{font:12.5px ui-monospace,monospace;color:#D6FF3D;background:#0f1420;padding:2px 6px;border-radius:5px}
a.btn{display:inline-block;margin-top:16px;padding:9px 16px;border-radius:999px;background:#D6FF3D;color:#0f1420;font-weight:600;text-decoration:none}</style></head>
<body><div class="card"><h1>Cant reach ${esc(host)}</h1><p>${hint}</p>
<p><code>${esc(message)}${cause ? " · " + esc(cause) : ""}</code></p>
<a class="btn" href="javascript:location.reload()">Try again</a></div></body></html>`;
} }
// ---- window + tabs ---- // ---- window + tabs ----