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:
33
README.md
33
README.md
@@ -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 domain’s TLD; if unavailable or fails, falls back to WHOIS (unless toggled off).
|
- Tries RDAP first if supported by the domain’s 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 }`.
|
||||||
|
|||||||
@@ -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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/index.ts
23
src/index.ts
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user