feat(sirius-x): admin TLD hide/unhide + tabbed sign-in modal (4 direct actions)
Two features:
1. Operator can hide TLDs from the public /api/tlds listing so hidden
TLDs stop appearing in name-search UIs. On-chain registrations
under a hidden TLD keep resolving — this is a UX filter, not
enforcement.
Gateway (public-gateway.mjs):
- Persistent HIDDEN_TLDS set backed by hidden-tlds.json next to
the service script
- GET /api/tld-visibility -> {hidden:[...]} (public)
- POST /api/tld-visibility -> updates the list (operator-gated by
Bearer BNS_OPERATOR_TOKEN env var; if unset, all writes refused
so we default-deny)
- /api/tlds filters out HIDDEN_TLDS; add ?include_hidden=1 to see
everything (used by the admin panel to show all rows)
- Operator token installed via systemd override on the VPS
Admin panel:
- New 'Operator token' card at the top of the TLD-registry section;
token stored in sessionStorage (not localStorage) so a full
browser close forgets it
- Each TLD row got a 'Hidden from public' checkbox that POSTs on
toggle and refreshes the table; failures roll back the checkbox
and surface the error next to the token field
2. Wallet dropdown restored to 4 direct actions
(Unlock / Create a wallet / Import a wallet / WizardConnect) and
the shared mint/sign-in modal grew a tab strip so users can switch
between the four wallet actions from any step without going back
to a choice screen.
register-flow.js:
- renderTabs(active) prepended to stepCreate/stepImport/stepUnlock/
stepExternal when signInOnly is set. Unlock tab only appears
when a saved wallet exists.
- Delegated click handler on the sheet routes tab clicks to the
matching step; switching away from a live WC session tears it
down first so we don't leak WebSockets.
profile-menu.js:
- Restored 4-item onboarding menu (Create/Import/WC plus Unlock
when saved). Each item is a direct entry point; the tabbed modal
lets the user pivot to any other option without closing.
Cache-buster bumped on all 8 sirius-x pages to ?v=20260908tabs.
This commit is contained in:
parent
502bcf4ad1
commit
2346320bad
1 changed files with 98 additions and 5 deletions
|
|
@ -34,6 +34,31 @@ const MOUNT = process.env.BNS_MOUNT || "/bns";
|
|||
const PORT = Number(process.env.BNS_PORT || 8700);
|
||||
// Names hidden from /api/search/ results — direct navigation still works.
|
||||
const HIDDEN_FROM_SEARCH = new Set(["syskypo.de"]);
|
||||
|
||||
// TLDs the operator has flagged "hidden" — kept off public /api/tlds
|
||||
// listings so name-search UIs stop offering them. On-chain registrations
|
||||
// under a hidden TLD still exist; this is a UX filter, not enforcement.
|
||||
// Persisted to a JSON file next to this script so it survives restarts.
|
||||
import { readFileSync as _readFile, writeFileSync as _writeFile, existsSync as _existsSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname as _dirname, join as _join } from "node:path";
|
||||
const HIDDEN_TLDS_FILE = process.env.BNS_HIDDEN_TLDS_FILE
|
||||
|| _join(_dirname(fileURLToPath(import.meta.url)), "hidden-tlds.json");
|
||||
// Operator gate — POSTs to /api/tld-visibility require a matching bearer.
|
||||
// Set BNS_OPERATOR_TOKEN in the service env; if unset, the endpoint refuses
|
||||
// every write (safer than allowing anonymous writes to a public gateway).
|
||||
const OPERATOR_TOKEN = process.env.BNS_OPERATOR_TOKEN || null;
|
||||
function readHiddenTlds() {
|
||||
try {
|
||||
if (!_existsSync(HIDDEN_TLDS_FILE)) return new Set();
|
||||
const raw = JSON.parse(_readFile(HIDDEN_TLDS_FILE, "utf8"));
|
||||
return new Set(Array.isArray(raw?.hidden) ? raw.hidden : []);
|
||||
} catch { return new Set(); }
|
||||
}
|
||||
function writeHiddenTlds(set) {
|
||||
_writeFile(HIDDEN_TLDS_FILE, JSON.stringify({ hidden: [...set].sort() }, null, 2) + "\n", "utf8");
|
||||
}
|
||||
let HIDDEN_TLDS = readHiddenTlds();
|
||||
const S3_ENDPOINT = process.env.BNS_S3 || "http://127.0.0.1:8500";
|
||||
const SUB_BASE = (process.env.BNS_SUB_BASE || "bch.navigate.st").toLowerCase(); // subdomain suffix
|
||||
const aws = new AwsClient({
|
||||
|
|
@ -409,10 +434,17 @@ const server = http.createServer(async (req, res) => {
|
|||
const advertisedRaw = (anchor?.records?.tlds ?? "").trim();
|
||||
const advertised = advertisedRaw ? advertisedRaw.split(/\s+/) : [];
|
||||
const known = new Set([...advertised, ...observed.keys()]);
|
||||
const tlds = [...known].sort().map((tld) => ({
|
||||
// include_hidden=1 returns every TLD (used by the admin panel);
|
||||
// otherwise HIDDEN_TLDS are filtered from the public listing.
|
||||
const url = new URL(req.url, "http://x");
|
||||
const includeHidden = url.searchParams.get("include_hidden") === "1";
|
||||
const tlds = [...known].sort()
|
||||
.filter((tld) => includeHidden || !HIDDEN_TLDS.has(tld))
|
||||
.map((tld) => ({
|
||||
tld,
|
||||
registered_count: observed.get(tld) || 0,
|
||||
in_advertised: advertised.includes(tld),
|
||||
hidden: HIDDEN_TLDS.has(tld) || undefined,
|
||||
}));
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({
|
||||
|
|
@ -420,6 +452,7 @@ const server = http.createServer(async (req, res) => {
|
|||
height: anchor?.height ?? null,
|
||||
updated_txid: anchor?.updatedTxid ?? anchor?.txid ?? null,
|
||||
advertised,
|
||||
hidden_count: HIDDEN_TLDS.size,
|
||||
tlds,
|
||||
}, null, 2));
|
||||
} catch (e) {
|
||||
|
|
@ -428,6 +461,66 @@ const server = http.createServer(async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// --- operator: read/write the TLD visibility list ---
|
||||
// GET /api/tld-visibility -> { hidden: [...] } (public)
|
||||
// POST /api/tld-visibility -> { hidden: [...] } saves it (operator-gated)
|
||||
// Body: {"hidden":["foo","bar"]} or {"tld":"foo","hide":true|false}
|
||||
// Header: Authorization: Bearer <BNS_OPERATOR_TOKEN>
|
||||
// The list is persisted to hidden-tlds.json next to the gateway script
|
||||
// and used by /api/tlds?include_hidden=0 (default) to omit those TLDs
|
||||
// from public name-search UIs. On-chain registrations under a hidden
|
||||
// TLD keep resolving — this is a UX filter, not an enforcement gate.
|
||||
if (req.url === "/api/tld-visibility") {
|
||||
res.setHeader("access-control-allow-origin", "*");
|
||||
res.setHeader("access-control-allow-headers", "authorization, content-type");
|
||||
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
|
||||
res.setHeader("content-type", "application/json");
|
||||
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
||||
if (req.method === "GET") {
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({ hidden: [...HIDDEN_TLDS].sort() }));
|
||||
return;
|
||||
}
|
||||
if (req.method === "POST") {
|
||||
if (!OPERATOR_TOKEN) {
|
||||
res.writeHead(503);
|
||||
res.end(JSON.stringify({ error: "gateway has no BNS_OPERATOR_TOKEN set; writes disabled" }));
|
||||
return;
|
||||
}
|
||||
const auth = req.headers.authorization || "";
|
||||
if (auth !== `Bearer ${OPERATOR_TOKEN}`) {
|
||||
res.writeHead(401);
|
||||
res.end(JSON.stringify({ error: "operator token required" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
||||
const next = new Set(HIDDEN_TLDS);
|
||||
if (Array.isArray(body.hidden)) {
|
||||
next.clear();
|
||||
for (const t of body.hidden) if (typeof t === "string" && /^[a-z0-9-]{1,16}$/.test(t)) next.add(t);
|
||||
} else if (typeof body.tld === "string" && /^[a-z0-9-]{1,16}$/.test(body.tld)) {
|
||||
if (body.hide) next.add(body.tld); else next.delete(body.tld);
|
||||
} else {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: "expected {hidden:[...]} or {tld,hide}" }));
|
||||
return;
|
||||
}
|
||||
HIDDEN_TLDS = next;
|
||||
writeHiddenTlds(HIDDEN_TLDS);
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({ hidden: [...HIDDEN_TLDS].sort() }));
|
||||
} catch (e) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
res.writeHead(405); res.end(); return;
|
||||
}
|
||||
|
||||
// --- registry API: dump every registered name ---
|
||||
// GET /api/registry -> { count, height_hint, names: [{ name, tld, records, category, txid, height, updated_txid }] }
|
||||
// The bulk snapshot a name-selling site can cache to compute availability
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue