WHOIS & RDAP Lookup — JavaScript source
Look up registration data for any domain, IP address, or ASN — straight from your browser to the authoritative RDAP registry over HTTPS. No backend, no database of lookups, fully private.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* RDAP lookup — WHOIS registration data over HTTPS (RFCs 7480-7485).
*
* Language: JavaScript (ES module, Fetch API — Node 18+ or browser)
* CosmoDev polyglot showcase port of the `whois` tool.
* Ported from src/lib/whois.ts — display source, part of CosmoDev's
* polyglot tool pages.
*
* RDAP is the JSON successor to port-43 WHOIS. The client first asks
* IANA's bootstrap registry which server is authoritative for the TLD
* (dns.json) or the ASN block (asn.json), then fetches the record
* straight from that server. Two plain HTTPS GETs — no WHOIS port, no
* backend. RDAP servers send permissive CORS headers, so this exact
* code runs in a browser (which is how the CosmoDev tool itself works).
*/
// IANA's bootstrap registries: match keys -> authoritative RDAP servers.
const BOOTSTRAP_URLS = {
domain: 'https://data.iana.org/RDAP/dns.json',
autnum: 'https://data.iana.org/RDAP/asn.json',
};
/** Classify a query: an AS number ("as3356" / "3356") or a domain name. */
function classify(query) {
const m = /^(?:as)?(\d{1,10})$/i.exec(query.trim());
if (m && Number(m[1]) > 0) return { kind: 'autnum', value: String(Number(m[1])) };
const value = query.trim().toLowerCase().replace(/\.+$/, '');
return { kind: 'domain', value, tld: value.split('.').pop() };
}
/** True when `asn` falls inside an asn.json key ("lo-hi" or a bare number). */
function asnInRange(key, asn) {
const [lo, hi] = key.split('-');
return hi === undefined ? Number(lo) === asn : asn >= Number(lo) && asn <= Number(hi);
}
/** Ask IANA's bootstrap which RDAP server is authoritative for the query. */
async function resolveRdapBase(q) {
const { services } = await (await fetch(BOOTSTRAP_URLS[q.kind])).json();
for (const [keys, urls] of services) {
const hit =
q.kind === 'autnum'
? keys.some((k) => asnInRange(k, Number(q.value)))
: keys.some((k) => k.toLowerCase() === q.tld);
if (hit) return urls[0];
}
throw new Error(`No RDAP server for ${q.value}`);
}
/** The eventDate of one action ("registration", "expiration"), if present. */
function eventDate(events, action) {
return events?.find((e) => e.eventAction?.toLowerCase() === action)?.eventDate;
}
/** Registrar name/email live in an entity with role "registrar", in a jCard. */
function registrarInfo(entities) {
const props = entities?.find((e) => e.roles?.includes('registrar'))?.vcardArray?.[1] ?? [];
const first = (kind) => props.find((p) => p[0] === kind)?.[3];
return { name: first('fn'), email: first('email') };
}
/**
* Look up a domain or ASN over RDAP and return the essentials.
*
* @returns {Promise<{registrar: {name?: string, email?: string},
* registered?: string, expires?: string,
* status: string[], nameservers: string[]}>}
* @throws {Error} On network failure, unknown TLD/ASN, or a non-2xx response.
*/
export async function whoisLookup(query) {
const q = classify(query);
const base = await resolveRdapBase(q);
const res = await fetch(`${base.replace(/\/+$/, '')}/${q.kind}/${q.value}`);
if (!res.ok) throw new Error(`RDAP server returned HTTP ${res.status}`);
const rdap = await res.json();
return {
registrar: registrarInfo(rdap.entities),
registered: eventDate(rdap.events, 'registration'),
expires: eventDate(rdap.events, 'expiration'),
status: rdap.status ?? [],
nameservers: (rdap.nameservers ?? [])
.map((ns) => ns.ldhName)
.filter(Boolean),
};
}
// Usage:
// const record = await whoisLookup('cosmolabs.org');
// console.log(record.registrar, record.registered, record.expires);
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →