navigate.st: multi-TLD label search (gateway /api/labels/<label> + client picker)
Fixes the "type a bare label, find matches across every TLD" gap. Before this,
typing `poutakidis` in the navigate.st search box was silently pinned to
`.bch` (missing `poutakidis.de`); typing `coinspectrum` was likewise pinned
to `.bch` and missed the `.x` registration.
Gateway (Argus/src/gateway/public-gateway.mjs): new /api/labels/<label>
endpoint. Iterates the cached full-index Map (getIndex(), rebuilt every 30s)
and returns { label, matches: [{ name, tld, records, category }] } — bare
`<label>` entries are surfaced as `<label>.bch` in the response for clarity.
Zero cost on the live path — the label endpoint reuses the same index that
the record-picker already touches.
Client (navigate.st/index.html): three routes on submit —
bare label → /api/labels/<l>: 0 matches show "not registered, register at
Sirius"; 1 match redirects to /bns/<name>; 2+ matches show
an inline picker with a per-record-type badge (on-chain /
Sia / server / redirect).
<label>.<tld…> → /bns/<name> straight, no API call needed.
free text → DuckDuckGo, unchanged.
The picker + empty-state markup + CSS all live inside the single 15 KB page —
no external dependencies. On API failure the client falls back to the pre-fix
".bch guess" so the box stays useful if the gateway is unreachable.
Deployed. Live at https://navigate.st/api/labels/poutakidis (1 match, .de) and
https://navigate.st/api/labels/coinspectrum (2 matches, .bch + .x). Backup of
the previous VPS gateway at /opt/bns-gateway/public-gateway.mjs.bak.1787175583.
This commit is contained in:
parent
45a3b595ce
commit
8228bda939
1 changed files with 41 additions and 0 deletions
|
|
@ -253,6 +253,47 @@ const server = http.createServer(async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// --- registrar API: all names sharing a label across every TLD ---
|
||||
// GET /api/labels/<label> -> { label, matches: [{ name, tld, records, category }] }
|
||||
// Powers navigate.st bare-label search: type "poutakidis" and get both
|
||||
// poutakidis.bch (if registered) and poutakidis.de rather than pinning to .bch.
|
||||
if (req.url.startsWith("/api/labels/")) {
|
||||
res.setHeader("access-control-allow-origin", "*");
|
||||
res.setHeader("content-type", "application/json");
|
||||
const raw = decodeURIComponent(req.url.slice("/api/labels/".length).split("?")[0]).toLowerCase();
|
||||
if (!/^[a-z0-9-]{1,32}$/.test(raw)) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: "invalid label" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const idx = await getIndex();
|
||||
const matches = [];
|
||||
for (const [key, entry] of idx) {
|
||||
// Index keys: bare "<label>" for .bch, "<label>.<tld>" otherwise
|
||||
// (see normalizeName in resolver-web.js).
|
||||
const dot = key.indexOf(".");
|
||||
if (dot < 0) {
|
||||
if (key === raw) matches.push({
|
||||
name: key + ".bch", tld: "bch",
|
||||
records: entry?.records ?? null, category: entry?.category ?? null,
|
||||
});
|
||||
} else if (key.slice(0, dot) === raw) {
|
||||
matches.push({
|
||||
name: key, tld: key.slice(dot + 1),
|
||||
records: entry?.records ?? null, category: entry?.category ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
matches.sort((a, b) => a.tld.localeCompare(b.tld));
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({ label: raw, matches }));
|
||||
} catch (e) {
|
||||
res.writeHead(500); res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- subdomain form: <flattened>.bch.<SUB_BASE> ---
|
||||
if (hostHeader.endsWith("." + SUB_BASE)) {
|
||||
const inner = hostHeader.slice(0, -("." + SUB_BASE).length); // "<flattened>.bch" or "<flattened>"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue