From d7d127d7b4a8a4d1f807458cb9f074920cc1411a Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sat, 12 Sep 2026 09:09:06 +0200 Subject: [PATCH] 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. --- main.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/main.js b/main.js index c4bd453..dd73eab 100644 --- a/main.js +++ b/main.js @@ -1412,7 +1412,35 @@ async function serveBns(request) { if (!isSubdomain && r.p) return await serveP(); if (r.u) return Response.redirect(r.u, 302); 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, "&").replace(//g, ">").replace(/"/g, """); + const hint = /ETIMEDOUT|ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH/.test(cause) + ? `Theseus resolved ${esc(host)} but could not reach ${esc(upstream || "its server")} from this network. Check your connection or VPN and try again.` + : /ENOTFOUND|EAI_AGAIN/.test(cause) + ? `Your system DNS could not look up ${esc(upstream || "the server")}. A stale or unreachable DNS server on one of your network adapters is the usual cause.` + : `Theseus resolved ${esc(host)} but the content fetch from ${esc(upstream || "its server")} failed.`; + return `Can’t reach ${esc(host)} + +

Can’t reach ${esc(host)}

${hint}

+

${esc(message)}${cause ? " · " + esc(cause) : ""}

+Try again
`; } // ---- window + tabs ----