1
mirror of https://github.com/jakejarvis/rdapper.git synced 2025-12-02 19:23:49 -05:00

Rename lookupDomain() to lookup() (with backwards compatibility)

This commit is contained in:
2025-10-20 15:05:59 -04:00
parent 5cbefe6a14
commit b3adbf8307
6 changed files with 79 additions and 58 deletions

View File

@@ -23,23 +23,14 @@ npm install rdapper
## Quick Start ## Quick Start
```ts ```ts
import { lookupDomain } from "rdapper"; import { lookup } from "rdapper";
const { ok, record, error } = await lookupDomain("example.com"); const { ok, record, error } = await lookup("example.com");
if (!ok) throw new Error(error); if (!ok) throw new Error(error);
console.log(record); // normalized DomainRecord console.log(record); // normalized DomainRecord
``` ```
Also available:
```ts
import { isRegistered, isAvailable } from "rdapper";
await isRegistered("example.com"); // => true
await isAvailable("likely-unregistered-thing-320485230458.com"); // => false
```
Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1): Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1):
```ts ```ts
@@ -50,11 +41,25 @@ toRegistrableDomain("spark-public.s3.amazonaws.com"); // => "amazonaws.com" (I
toRegistrableDomain("192.168.0.1"); // => null toRegistrableDomain("192.168.0.1"); // => null
``` ```
Convenience helpers to quickly check availability:
```ts
import { isRegistered, isAvailable } from "rdapper";
await isRegistered("example.com"); // => true
await isRegistered("likely-unregistered-thing-320485230458.com"); // => false
await isAvailable("example.com"); // => false
await isAvailable("likely-unregistered-thing-320485230458.com"); // => true
```
## API ## API
- `lookupDomain(domain, options?) => Promise<LookupResult>` - `lookup(domain, options?) => Promise<LookupResult>`
- Tries RDAP first if supported by the domains TLD; if unavailable or fails, falls back to WHOIS (unless toggled off). - Tries RDAP first if supported by the domains TLD; if unavailable or fails, falls back to WHOIS (unless toggled off).
- Result is `{ ok: boolean, record?: DomainRecord, error?: string }`. - Result is `{ ok: boolean, record?: DomainRecord, error?: string }`.
- `toRegistrableDomain(input, options?) => string | null`
- Normalizes a domain or URL to its registrable domain (eTLD+1).
- Returns the registrable domain string, or `null` for IPs/invalid input; [options](https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts) are forwarded to `tldts` (e.g., `allowPrivateDomains`).
- `isRegistered(domain, options?) => Promise<boolean>` - `isRegistered(domain, options?) => Promise<boolean>`
- `isAvailable(domain, options?) => Promise<boolean>` - `isAvailable(domain, options?) => Promise<boolean>`
@@ -74,9 +79,9 @@ WHOIS requires a raw TCP connection over port 43 via `node:net`, which is not av
- Prefer RDAP only on edge: - Prefer RDAP only on edge:
```ts ```ts
import { lookupDomain } from "rdapper"; import { lookup } from "rdapper";
const res = await lookupDomain("example.com", { rdapOnly: true }); const res = await lookup("example.com", { rdapOnly: true });
``` ```
- If `rdapOnly` is omitted and the code path reaches WHOIS on edge, rdapper throws a clear runtime error advising to run in Node or set `{ rdapOnly: true }`. - If `rdapOnly` is omitted and the code path reaches WHOIS on edge, rdapper throws a clear runtime error advising to run in Node or set `{ rdapOnly: true }`.

View File

@@ -6,14 +6,14 @@
// echo "example.com" | npx rdapper // echo "example.com" | npx rdapper
import { createInterface } from "node:readline"; import { createInterface } from "node:readline";
import { lookupDomain } from "../dist/index.js"; import { lookup } from "../dist/index.js";
async function main() { async function main() {
if (process.argv.length > 2) { if (process.argv.length > 2) {
// URL(s) specified in the command arguments // URL(s) specified in the command arguments
console.log( console.log(
JSON.stringify( JSON.stringify(
await lookupDomain(process.argv[process.argv.length - 1]), await lookup(process.argv[process.argv.length - 1]),
null, null,
2, 2,
), ),
@@ -24,7 +24,7 @@ async function main() {
input: process.stdin, input: process.stdin,
}); });
rlInterface.on("line", async (line) => { rlInterface.on("line", async (line) => {
console.log(JSON.stringify(await lookupDomain(line), null, 2)); console.log(JSON.stringify(await lookup(line), null, 2));
}); });
} }
} }

View File

@@ -20,7 +20,7 @@ import {
* High-level lookup that prefers RDAP and falls back to WHOIS. * High-level lookup that prefers RDAP and falls back to WHOIS.
* Ensures a standardized DomainRecord, independent of the source. * Ensures a standardized DomainRecord, independent of the source.
*/ */
export async function lookupDomain( export async function lookup(
domain: string, domain: string,
opts?: LookupOptions, opts?: LookupOptions,
): Promise<LookupResult> { ): Promise<LookupResult> {
@@ -118,28 +118,37 @@ export async function lookupDomain(
} }
} }
/** Determine if a domain appears available (not registered). /**
* Performs a lookup and resolves to a boolean. Rejects on lookup error. */ * Determine if a domain appears available (not registered).
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
*/
export async function isAvailable( export async function isAvailable(
domain: string, domain: string,
opts?: LookupOptions, opts?: LookupOptions,
): Promise<boolean> { ): Promise<boolean> {
const res = await lookupDomain(domain, opts); const res = await lookup(domain, opts);
if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed"); if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed");
return res.record.isRegistered === false; return res.record.isRegistered === false;
} }
/** Determine if a domain appears registered. /**
* Performs a lookup and resolves to a boolean. Rejects on lookup error. */ * Determine if a domain appears registered.
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
*/
export async function isRegistered( export async function isRegistered(
domain: string, domain: string,
opts?: LookupOptions, opts?: LookupOptions,
): Promise<boolean> { ): Promise<boolean> {
const res = await lookupDomain(domain, opts); const res = await lookup(domain, opts);
if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed"); if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed");
return res.record.isRegistered === true; return res.record.isRegistered === true;
} }
/**
* @deprecated Use `lookup` instead.
*/
export const lookupDomain = lookup;
export { export {
getDomainParts, getDomainParts,
getDomainTld, getDomainTld,

View File

@@ -3,7 +3,7 @@ import { parse } from "tldts";
type ParseOptions = Parameters<typeof parse>[1]; type ParseOptions = Parameters<typeof parse>[1];
/** /**
* Parse a domain into its parts. Accepts options which are passed to tldts.parse(). * Parse a domain into its parts. Passes options to `tldts.parse()`.
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts * @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
*/ */
export function getDomainParts( export function getDomainParts(
@@ -13,7 +13,10 @@ export function getDomainParts(
return parse(domain, { ...opts }); return parse(domain, { ...opts });
} }
/** Get the TLD (ICANN-only public suffix) of a domain. */ /**
* Get the TLD (ICANN-only public suffix) of a domain. Passes options to `tldts.parse()`.
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
*/
export function getDomainTld( export function getDomainTld(
domain: string, domain: string,
opts?: ParseOptions, opts?: ParseOptions,
@@ -47,7 +50,9 @@ export function punyToUnicode(domain: string): string {
/** /**
* Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1). * Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1).
* Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs). * Passes options to `tldts.parse()`.
* Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs)
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
*/ */
export function toRegistrableDomain( export function toRegistrableDomain(
input: string, input: string,
@@ -69,27 +74,3 @@ export function toRegistrableDomain(
if (domain === "") return null; if (domain === "") return null;
return domain.toLowerCase(); return domain.toLowerCase();
} }
// Common WHOIS availability phrases seen across registries/registrars
const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
/\bno match\b/i,
/\bnot found\b/i,
/\bno entries found\b/i,
/\bno data found\b/i,
/\bavailable for registration\b/i,
/\bdomain\s+available\b/i,
/\bdomain status[:\s]+available\b/i,
/\bobject does not exist\b/i,
/\bthe queried object does not exist\b/i,
// Common variants across ccTLDs/registrars
/\bstatus:\s*free\b/i,
/\bstatus:\s*available\b/i,
/\bno object found\b/i,
/\bnicht gefunden\b/i,
/\bpending release\b/i, // often signals not registered/being deleted
];
export function isWhoisAvailable(text: string | undefined): boolean {
if (!text) return false;
return WHOIS_AVAILABLE_PATTERNS.some((re) => re.test(text));
}

View File

@@ -1,5 +1,4 @@
import { toISO } from "../lib/dates"; import { toISO } from "../lib/dates";
import { isWhoisAvailable } from "../lib/domain";
import { isPrivacyName } from "../lib/privacy"; import { isPrivacyName } from "../lib/privacy";
import { parseKeyValueLines, uniq } from "../lib/text"; import { parseKeyValueLines, uniq } from "../lib/text";
import type { import type {
@@ -9,6 +8,33 @@ import type {
RegistrarInfo, RegistrarInfo,
} from "../types"; } from "../types";
// Common WHOIS availability phrases seen across registries/registrars
const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [
/\bno match\b/i,
/\bnot found\b/i,
/\bno entries found\b/i,
/\bno data found\b/i,
/\bavailable for registration\b/i,
/\bdomain\s+available\b/i,
/\bdomain status[:\s]+available\b/i,
/\bobject does not exist\b/i,
/\bthe queried object does not exist\b/i,
// Common variants across ccTLDs/registrars
/\bstatus:\s*free\b/i,
/\bstatus:\s*available\b/i,
/\bno object found\b/i,
/\bnicht gefunden\b/i,
/\bpending release\b/i, // often signals not registered/being deleted
];
/**
* Best-effort heuristic to determine if a WHOIS response indicates the domain is available.
*/
export function isAvailableByWhois(text: string | undefined): boolean {
if (!text) return false;
return WHOIS_AVAILABLE_PATTERNS.some((re) => re.test(text));
}
/** /**
* Convert raw WHOIS text into our normalized DomainRecord. * Convert raw WHOIS text into our normalized DomainRecord.
* Heuristics cover many gTLD and ccTLD formats; exact fields vary per registry. * Heuristics cover many gTLD and ccTLD formats; exact fields vary per registry.
@@ -163,7 +189,7 @@ export function normalizeWhois(
const record: DomainRecord = { const record: DomainRecord = {
domain, domain,
tld, tld,
isRegistered: !isWhoisAvailable(whoisText), isRegistered: !isAvailableByWhois(whoisText),
isIDN: /(^|\.)xn--/i.test(domain), isIDN: /(^|\.)xn--/i.test(domain),
unicodeName: undefined, unicodeName: undefined,
punycodeName: undefined, punycodeName: undefined,

View File

@@ -1,8 +1,8 @@
import { isWhoisAvailable } from "../lib/domain";
import type { LookupOptions } from "../types"; import type { LookupOptions } from "../types";
import type { WhoisQueryResult } from "./client"; import type { WhoisQueryResult } from "./client";
import { whoisQuery } from "./client"; import { whoisQuery } from "./client";
import { extractWhoisReferral } from "./discovery"; import { extractWhoisReferral } from "./discovery";
import { isAvailableByWhois } from "./normalize";
/** /**
* Follow registrar WHOIS referrals up to a configured hop limit. * Follow registrar WHOIS referrals up to a configured hop limit.
@@ -30,8 +30,8 @@ export async function followWhoisReferrals(
try { try {
const res = await whoisQuery(next, domain, opts); const res = await whoisQuery(next, domain, opts);
// Prefer authoritative TLD response when registrar contradicts availability // Prefer authoritative TLD response when registrar contradicts availability
const registeredBefore = !isWhoisAvailable(current.text); const registeredBefore = !isAvailableByWhois(current.text);
const registeredAfter = !isWhoisAvailable(res.text); const registeredAfter = !isAvailableByWhois(res.text);
if (registeredBefore && !registeredAfter) { if (registeredBefore && !registeredAfter) {
// Registrar claims availability but TLD shows registered: keep TLD // Registrar claims availability but TLD shows registered: keep TLD
break; break;
@@ -74,8 +74,8 @@ export async function collectWhoisReferralChain(
try { try {
const res = await whoisQuery(next, domain, opts); const res = await whoisQuery(next, domain, opts);
// If registrar claims availability while TLD indicated registered, stop. // If registrar claims availability while TLD indicated registered, stop.
const registeredBefore = !isWhoisAvailable(current.text); const registeredBefore = !isAvailableByWhois(current.text);
const registeredAfter = !isWhoisAvailable(res.text); const registeredAfter = !isAvailableByWhois(res.text);
if (registeredBefore && !registeredAfter) { if (registeredBefore && !registeredAfter) {
// Do not adopt or append contradictory registrar; keep authoritative TLD only. // Do not adopt or append contradictory registrar; keep authoritative TLD only.
break; break;