feat(theseus): one-click "Install in Theseus" from theseus.x/extensions
The extensions page could only hand out tarballs; installing meant going to Settings › Extensions › Community and finding the entry again. Pages now get window.bcnr.installExtension(id) and Theseus intercepts theseus://extensions/install/<id> links (page clicks, target=_blank and the address bar). The page only names a catalog id: Theseus fetches the catalog and package itself, asks in a native dialog the page cannot draw over, verifies the publisher signature against the name's current owner and activates the add-on — the same path a Settings install takes. One prompt at a time; an already-installed version says so instead of offering a no-op update. The site shows the button inside Theseus (feature-detected on the bridge), a "update Theseus" hint on older builds and a download hint in other browsers.
This commit is contained in:
parent
1086046212
commit
be36492d8a
1 changed files with 31 additions and 4 deletions
|
|
@ -96,6 +96,14 @@
|
||||||
.ext .h b{color:var(--mut);font-weight:600;min-width:70px}
|
.ext .h b{color:var(--mut);font-weight:600;min-width:70px}
|
||||||
.ext .h code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
|
.ext .h code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
|
||||||
.ext .meta{color:var(--dim);font-size:12.5px;margin-top:.4rem}
|
.ext .meta{color:var(--dim);font-size:12.5px;margin-top:.4rem}
|
||||||
|
.ext .act{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:.8rem}
|
||||||
|
.ext .install{display:inline-block;background:var(--acid);color:#0b0e14;font-weight:600;font-size:14px;
|
||||||
|
border:none;border-radius:9px;padding:9px 18px;cursor:pointer;text-decoration:none;font-family:inherit}
|
||||||
|
.ext .install:hover{filter:brightness(1.08)}
|
||||||
|
.ext .install[disabled]{opacity:.6;cursor:default}
|
||||||
|
.ext .install.alt{background:rgba(214,255,61,.12);color:var(--acid)}
|
||||||
|
.ext .act .st{font-size:13px;color:var(--mut)}
|
||||||
|
.ext .act .st.ok{color:var(--acid)} .ext .act .st.err{color:#f6768a}
|
||||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||||||
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
|
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
|
||||||
.note b{color:var(--ink)}
|
.note b{color:var(--ink)}
|
||||||
|
|
@ -190,9 +198,10 @@
|
||||||
|
|
||||||
<section id="community">
|
<section id="community">
|
||||||
<h2>Community extensions</h2>
|
<h2>Community extensions</h2>
|
||||||
<p class="lede">Published by BCDN name holders, signed with the wallet that holds the name. Theseus installs them
|
<p class="lede">Published by BCDN name holders, signed with the wallet that holds the name. Open this page in Theseus
|
||||||
from <b>Settings › Extensions › Community</b> and re-checks every signature against the name's current owner
|
and click <b>Install in Theseus</b> — or use <b>Settings › Extensions › Community</b>. Either way Theseus fetches the
|
||||||
in its own chain index — the relay cannot substitute code under a trusted name.</p>
|
package itself and re-checks every signature against the name's current owner in its own chain index — the relay
|
||||||
|
cannot substitute code under a trusted name.</p>
|
||||||
<div id="communityList"><div class="ext" style="color:var(--dim)">Loading the catalog…</div></div>
|
<div id="communityList"><div class="ext" style="color:var(--dim)">Loading the catalog…</div></div>
|
||||||
<div class="note"><b>Trust, stated plainly.</b> A community extension runs with the same access as any add-on —
|
<div class="note"><b>Trust, stated plainly.</b> A community extension runs with the same access as any add-on —
|
||||||
treat one like an unsigned executable from that publisher. The signature proves <i>who</i> published it,
|
treat one like an unsigned executable from that publisher. The signature proves <i>who</i> published it,
|
||||||
|
|
@ -254,6 +263,16 @@
|
||||||
// Community catalog
|
// Community catalog
|
||||||
const box = document.getElementById("communityList");
|
const box = document.getElementById("communityList");
|
||||||
const esc = (s) => String(s || "").replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" })[c]);
|
const esc = (s) => String(s || "").replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" })[c]);
|
||||||
|
// Inside Theseus every page has window.bcnr; builds since 0.3.50 add
|
||||||
|
// installExtension(id). The page only passes the catalog id — Theseus
|
||||||
|
// fetches the package, confirms in a native dialog and verifies the signature.
|
||||||
|
const inTheseus = !!window.bcnr;
|
||||||
|
const canInstall = inTheseus && typeof window.bcnr.installExtension === "function";
|
||||||
|
const installUi = (id) => canInstall
|
||||||
|
? `<button class="install" type="button" data-install="${esc(id)}">Install in Theseus</button><span class="st" data-st="${esc(id)}"></span>`
|
||||||
|
: inTheseus
|
||||||
|
? `<a class="install alt" href="../#download">Update Theseus to install from here</a><span class="st">until then: Settings › Extensions › Community › Install</span>`
|
||||||
|
: `<a class="install alt" href="../#download">Get Theseus to install</a><span class="st">one click from this page inside Theseus</span>`;
|
||||||
try {
|
try {
|
||||||
const r = await fetch("./community/catalog.json", { cache: "no-store" });
|
const r = await fetch("./community/catalog.json", { cache: "no-store" });
|
||||||
const cat = r.ok ? await r.json() : { extensions: [] };
|
const cat = r.ok ? await r.json() : { extensions: [] };
|
||||||
|
|
@ -264,14 +283,22 @@
|
||||||
<h3><span class="icon">${esc(e.icon || "🧩")}</span> ${esc(e.name)} <span class="cur" style="margin-left:auto">v${esc(e.latest)}</span></h3>
|
<h3><span class="icon">${esc(e.icon || "🧩")}</span> ${esc(e.name)} <span class="cur" style="margin-left:auto">v${esc(e.latest)}</span></h3>
|
||||||
<div class="desc">${esc(e.description)}</div>
|
<div class="desc">${esc(e.description)}</div>
|
||||||
<div class="meta">Published by <b>${esc(e.publisher)}</b> · ${esc(String(e.versions || 1))} version${e.versions === 1 ? "" : "s"} · updated ${esc(String(e.updatedAt || "").slice(0, 10))}${e.capabilities && e.capabilities.length ? " · " + esc(e.capabilities.join(", ")) : ""}</div>
|
<div class="meta">Published by <b>${esc(e.publisher)}</b> · ${esc(String(e.versions || 1))} version${e.versions === 1 ? "" : "s"} · updated ${esc(String(e.updatedAt || "").slice(0, 10))}${e.capabilities && e.capabilities.length ? " · " + esc(e.capabilities.join(", ")) : ""}</div>
|
||||||
|
<div class="act">${installUi(e.id)}</div>
|
||||||
<div class="verifybox">
|
<div class="verifybox">
|
||||||
<div class="lbl">Publisher-signed channel — verified by Theseus against ${esc(e.publisher)}'s current owner</div>
|
<div class="lbl">Publisher-signed channel — verified by Theseus against ${esc(e.publisher)}'s current owner</div>
|
||||||
<div class="h"><b>updates.json</b><code><a href="${esc(e.updatesUrl)}">${esc(String(e.updatesUrl).replace(/^https?:\/\/[^/]+\/bns\//, ""))}</a></code></div>
|
<div class="h"><b>updates.json</b><code><a href="${esc(e.updatesUrl)}">${esc(String(e.updatesUrl).replace(/^https?:\/\/[^/]+\/bns\//, ""))}</a></code></div>
|
||||||
<div class="h"><b>Tarball</b><code><a href="${esc(e.tarballUrl)}">${esc(String(e.tarballUrl).split("/").pop())}</a></code></div>
|
<div class="h"><b>Tarball</b><code><a href="${esc(e.tarballUrl)}">${esc(String(e.tarballUrl).split("/").pop())}</a></code></div>
|
||||||
<div class="h"><b>sha256</b><code>${esc(e.sha256)}</code></div>
|
<div class="h"><b>sha256</b><code>${esc(e.sha256)}</code></div>
|
||||||
<div class="h"><b>Install</b><code>Theseus › Settings › Extensions › Community › Install</code></div>
|
<div class="h"><b>Link</b><code>theseus://extensions/install/${esc(e.id)}</code></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`).join("");
|
</div>`).join("");
|
||||||
|
box.querySelectorAll("button[data-install]").forEach((btn) => btn.addEventListener("click", async () => {
|
||||||
|
const id = btn.dataset.install, st = box.querySelector(`[data-st="${CSS.escape(id)}"]`);
|
||||||
|
btn.disabled = true; st.className = "st"; st.textContent = "Confirm in the Theseus dialog…";
|
||||||
|
let r; try { r = await window.bcnr.installExtension(id); } catch (err) { r = { ok: false, error: err?.message || String(err) }; }
|
||||||
|
if (r && r.ok) { st.className = "st ok"; st.textContent = `Installed v${r.version} — signed by ${r.publisher}`; btn.textContent = "Installed"; }
|
||||||
|
else { btn.disabled = false; st.className = r?.error === "cancelled" ? "st" : "st err"; st.textContent = r?.error === "cancelled" ? "" : (r?.error || "install failed"); }
|
||||||
|
}));
|
||||||
} catch (e) { box.innerHTML = '<div class="ext" style="color:var(--dim)">Catalog unavailable right now.</div>'; }
|
} catch (e) { box.innerHTML = '<div class="ext" style="color:var(--dim)">Catalog unavailable right now.</div>'; }
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue