theseus/error.html

190 lines
8.7 KiB
HTML
Raw Normal View History

Theseus 0.1.3: branded error page for load failures (BUILT, NOT DEPLOYED) Setup f2afc14efc63008cbb9dad44176e94146386db4c0afda4459f1d4eb929172b6d Portable 5d08b1415526934db8de780949a610896064fe9567aa0e5e1702ebabd7eb7df2 Chromium's default 'This site can't be reached' replaced with a Theseus- themed error page. did-fail-load on every tab's webContents (main frame only, non-ignorable code) routes the tab to error.html with the attempt URL, host, error code, and description as query params. The page keeps t.url pointing at the failed URL so the address bar shows what the user typed and they can edit + retry - refreshTabUrl's existing file:// skip means the error page's own path never leaks back into the bar. Five kinds, chosen by pickErrorKind(code, host): name-not-registered BCNR-eligible host + ERR_NAME_NOT_RESOLVED. Says "no BCDN record on chain, no clearnet host either." Offers Register on Sirius + Search + Retry + Home. name-unreachable ERR_NAME_NOT_RESOLVED on a non-BCNR host. DNS failed - offers Retry + Search + Register + Home. unreachable CONN_REFUSED/RESET/TIMED_OUT/CLOSED/NETWORK_CHANGED. Offers Retry + Tor guide + Home. tls ERR_CERT_* range (-200..-299). Offers Retry + Home. generic Everything else. home-preload.js gains `window.errorpage` alongside `window.home`. Both APIs are sender-URL-gated in main - a random page seeing the shape can't invoke them (isErrorPageSender / isHomePageSender). The external- open handler additionally allowlists Silent Mode domains only. package.json build.files gets error.html + error-preload.js so electron-builder actually bundles them (GOTCHAS rule: an unlisted runtime-loaded file silently opens blank). Ship pages (releases-manifest.json, tools/index.html, releases/index.html, site-theseus-x/index.html) updated to 0.1.3 with the new hashes. DEPLOY STATUS - blocked on VPS SSH: my IP was hit with a full-port ban mid-turn (likely fail2ban from the burst of scp during the 0.1.0-0.1.2 iterations). Site pages/manifest/installers are committed locally but NOT yet on dl.silentmode.st or the Sia mirror. Live still reads 0.1.2. User needs to unban 195.184.247.106 on their end, or wait for the ban to expire, before the ship pages match reality.
2026-08-31 13:38:05 +02:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Load error</title>
<style>
:root { color-scheme: light dark;
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--warn:#ffc75f; --err:#f6768a; --info:#4b7bec; }
@media (prefers-color-scheme: light) {
:root { --bg:#f6f8fb; --panel:#ffffff; --panel2:#f0f4f9; --line:rgba(0,0,0,.10);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; }
}
* { box-sizing: border-box }
html, body { margin: 0; height: 100% }
body {
background: radial-gradient(1100px 560px at 50% -12%, #16202e, var(--bg));
color: var(--ink); font: 15px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: grid; place-items: center; padding: 2rem 1.2rem;
}
.card { max-width: 640px; width: 100%; background: var(--panel); border: 1px solid var(--line);
border-radius: 14px; padding: 2rem 2.2rem 1.7rem; }
.head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 1rem; }
.icon { width: 42px; height: 42px; flex: none; border-radius: 10px; display: grid; place-items: center;
font-size: 22px; background: rgba(246,118,138,.10); }
.icon.warn { background: rgba(255,199,95,.10) }
.icon.info { background: rgba(75,123,236,.10) }
h1 { font-size: 1.15rem; font-weight: 600; margin: .1rem 0 .1rem; letter-spacing: .1px }
.sub { color: var(--mut); font-size: 13.5px; margin: 0; }
.body { color: var(--ink); font-size: 14px; line-height: 1.62; margin: 1rem 0 .6rem; }
.body b { color: var(--acid); }
.body .host { font-family: ui-monospace, monospace; background: var(--panel2); padding: 1px 6px;
border-radius: 5px; color: var(--acid); word-break: break-all; }
.tried { margin: 1rem 0 0; padding: 12px 14px; background: var(--panel2);
border: 1px dashed var(--line); border-radius: 10px; font-size: 12.5px; color: var(--mut); }
.tried .t { display: flex; gap: 8px; align-items: center; padding: 3px 0; }
.tried .t .m { width: 12px; text-align: center; color: var(--err); }
.tried .t .m.ok { color: var(--acid) }
.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 1.3rem; }
.btn { font-family: inherit; font-size: 13.5px; padding: 9px 16px; border-radius: 9px;
border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
cursor: pointer; transition: filter .1s, background .1s, border-color .1s; }
.btn:hover { background: var(--panel); border-color: rgba(214,255,61,.35); filter: brightness(1.05); }
.btn.primary { background: var(--acid); color: #0b0e14; border-color: transparent; font-weight: 600; }
.btn.primary:hover { background: var(--acid); filter: brightness(1.08); }
.details { margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--line);
font: 11.5px/1.55 ui-monospace, monospace; color: var(--dim); }
.details .row { display: grid; grid-template-columns: 78px 1fr; gap: 8px; }
.details .row b { color: var(--mut); font-weight: 500; }
</style>
</head>
<body>
<div class="card">
<div class="head">
<div class="icon" id="icon"></div>
<div>
<h1 id="title">Couldn't load this page</h1>
<p class="sub" id="sub">The tab kept its address so you can edit and try again.</p>
</div>
</div>
<div class="body" id="body"></div>
<div class="tried" id="tried" hidden>
<div class="t"><span class="m"></span> <span>Bitcoin Cash Domain Names (BCDN) — no record on chain</span></div>
<div class="t"><span class="m"></span> <span>Clearnet DNS — no host at this name either</span></div>
</div>
<div class="actions" id="actions"></div>
<div class="details">
<div class="row"><b>URL</b><span id="dUrl"></span></div>
<div class="row"><b>Error</b><span id="dErr"></span></div>
</div>
</div>
<script>
const q = new URLSearchParams(location.search);
const kind = q.get("kind") || "generic"; // name-not-registered | name-unreachable | unreachable | tls | generic
const host = q.get("host") || "";
const url = q.get("url") || "";
const code = q.get("code") || "";
const desc = q.get("desc") || "";
const $ = (id) => document.getElementById(id);
const el = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt != null) e.textContent = txt; return e; };
const hostSpan = (h) => { const s = el("span", "host"); s.textContent = h || "(no host)"; return s; };
document.title = host ? "Error — " + host : "Load error";
$("dUrl").textContent = url || "(none)";
$("dErr").textContent = (code ? code + " " : "") + (desc || "");
// Kind → title, subtitle, body, actions.
const configs = {
"name-not-registered": {
icon: "?", iconCls: "warn",
title: "That name isn't registered",
sub: "No BCDN record on chain, and no clearnet host at " + host,
body: (b) => {
b.append("Nothing answered when we asked for ");
b.append(hostSpan(host));
b.append(". Bitcoin Cash Domain Names had no record for it, and ordinary DNS has no host at that name either.");
b.append(document.createElement("br"));
b.append(document.createElement("br"));
b.append("If it's supposed to be yours, register it on the chain. If you were guessing, try a search instead.");
},
tried: true,
actions: ["register", "search", "retry", "home"],
},
"name-unreachable": {
icon: "⌘", iconCls: "warn",
title: "This name didn't resolve",
sub: "Your DNS resolver didn't know " + host,
body: (b) => {
b.append("We couldn't turn ");
b.append(hostSpan(host));
b.append(" into an address. Your network's resolver may be blocking it, offline, or the name simply doesn't exist. BCDN was tried first — if the host is a BCDN name, it's not registered on the chain either.");
},
actions: ["retry", "search", "register", "home"],
},
"unreachable": {
icon: "✕", iconCls: "",
title: "This site is unreachable",
sub: host ? ("Couldn't connect to " + host) : "The server didn't respond",
body: (b) => {
b.append("The name resolved, but the server at ");
b.append(hostSpan(host));
b.append(" didn't answer. It may be down, blocking your network, or reachable only through Tor.");
},
actions: ["retry", "tor-hint", "home"],
},
"tls": {
icon: "!", iconCls: "warn",
title: "The site's certificate isn't trustworthy",
sub: host ? ("HTTPS handshake with " + host + " failed") : "Certificate error",
body: (b) => {
b.append("The server at ");
b.append(hostSpan(host));
b.append(" presented a certificate the browser couldn't verify. This can mean the certificate has expired, doesn't match the hostname, was self-signed, or that someone is intercepting the connection.");
},
actions: ["retry", "home"],
},
"generic": {
icon: "✕", iconCls: "",
title: "Couldn't load this page",
sub: host ? ("Something went wrong loading " + host) : "Load failed",
body: (b) => {
b.append("Something interrupted the load of ");
b.append(hostSpan(host));
b.append(". Details are at the bottom of this page.");
},
actions: ["retry", "home"],
},
};
const c = configs[kind] || configs.generic;
$("icon").textContent = c.icon;
$("icon").className = "icon" + (c.iconCls ? " " + c.iconCls : "");
$("title").textContent = c.title;
$("sub").textContent = c.sub;
c.body($("body"));
if (c.tried) $("tried").hidden = false;
// Actions — order matters (first is primary). "register" only when we can
// construct a Sirius URL (need a host).
const btnDefs = {
retry: { label: "Retry", primary: true, fn: () => window.errorpage.retry(url) },
search: { label: "Search for '" + host + "'", primary: false, fn: () => window.errorpage.searchFor(host) },
register: { label: "Register on Sirius", primary: false, fn: () => window.errorpage.registerOnSirius(host) },
home: { label: "Home", primary: false, fn: () => window.errorpage.goHome() },
"tor-hint": {
label: "Open Tor guide", primary: false,
fn: () => window.errorpage.openExternal("https://silentmode.st/tools/#tor"),
},
};
const actionsBox = $("actions");
let primaryUsed = false;
for (const a of c.actions) {
const def = btnDefs[a]; if (!def) continue;
if (a === "register" && !host) continue;
if (a === "search" && !host) continue;
const b = el("button", "btn" + (def.primary && !primaryUsed ? " primary" : ""), def.label);
if (def.primary) primaryUsed = true;
b.addEventListener("click", def.fn);
actionsBox.appendChild(b);
}
</script>
</body>
</html>