rdapper 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +40 -36
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -35,6 +35,16 @@ await isRegistered("example.com"); // => true
|
|
|
35
35
|
await isAvailable("likely-unregistered-thing-320485230458.com"); // => false
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1):
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { toRegistrableDomain } from "rdapper";
|
|
42
|
+
|
|
43
|
+
toRegistrableDomain("https://sub.example.co.uk/page"); // => "example.co.uk"
|
|
44
|
+
toRegistrableDomain("spark-public.s3.amazonaws.com"); // => "amazonaws.com" (ICANN-only default)
|
|
45
|
+
toRegistrableDomain("192.168.0.1"); // => null
|
|
46
|
+
```
|
|
47
|
+
|
|
38
48
|
## API
|
|
39
49
|
|
|
40
50
|
- `lookupDomain(domain, options?) => Promise<LookupResult>`
|
|
@@ -43,6 +53,20 @@ await isAvailable("likely-unregistered-thing-320485230458.com"); // => false
|
|
|
43
53
|
- `isRegistered(domain, options?) => Promise<boolean>`
|
|
44
54
|
- `isAvailable(domain, options?) => Promise<boolean>`
|
|
45
55
|
|
|
56
|
+
### Edge runtimes (e.g., Vercel Edge)
|
|
57
|
+
|
|
58
|
+
WHOIS requires a raw TCP connection over port 43 via `node:net`, which is not available on edge runtimes. This package lazily loads `node:net` only when the WHOIS code path runs. To use rdapper safely on edge:
|
|
59
|
+
|
|
60
|
+
- Prefer RDAP only:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { lookupDomain } from "rdapper";
|
|
64
|
+
|
|
65
|
+
const res = await lookupDomain("example.com", { rdapOnly: true });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- If `rdapOnly` is omitted and the code path reaches WHOIS on edge, rdapper throws a clear runtime error indicating WHOIS is unsupported on edge and to run in Node or set `rdapOnly: true`.
|
|
69
|
+
|
|
46
70
|
### Options
|
|
47
71
|
|
|
48
72
|
- `timeoutMs?: number` – Total timeout budget per network operation (default `15000`).
|
|
@@ -181,6 +205,7 @@ Project layout:
|
|
|
181
205
|
- Some TLDs provide no RDAP service; `rdapOnly: true` will fail for them.
|
|
182
206
|
- Registries may throttle or block WHOIS; respect rate limits and usage policies.
|
|
183
207
|
- Field presence depends on source and privacy policies (e.g., redaction/withholding).
|
|
208
|
+
- Public suffix detection uses `tldts` with ICANN‑only defaults (Private section is ignored). If you need behavior closer to `psl` that considers private suffixes, see the `allowPrivateDomains` option in the `tldts` docs (rdapper currently sticks to ICANN‑only by default). See: [tldts migration notes](https://github.com/remusao/tldts#migrating-from-other-libraries).
|
|
184
209
|
|
|
185
210
|
## License
|
|
186
211
|
|
package/dist/index.d.ts
CHANGED
|
@@ -124,6 +124,13 @@ interface LookupResult {
|
|
|
124
124
|
}
|
|
125
125
|
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
126
126
|
//#endregion
|
|
127
|
+
//#region src/lib/domain.d.ts
|
|
128
|
+
/**
|
|
129
|
+
* Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1).
|
|
130
|
+
* Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs).
|
|
131
|
+
*/
|
|
132
|
+
declare function toRegistrableDomain(input: string): string | null;
|
|
133
|
+
//#endregion
|
|
127
134
|
//#region src/index.d.ts
|
|
128
135
|
/**
|
|
129
136
|
* High-level lookup that prefers RDAP and falls back to WHOIS.
|
|
@@ -137,4 +144,4 @@ declare function isAvailable(domain: string, opts?: LookupOptions): Promise<bool
|
|
|
137
144
|
* Performs a lookup and resolves to a boolean. Rejects on lookup error. */
|
|
138
145
|
declare function isRegistered(domain: string, opts?: LookupOptions): Promise<boolean>;
|
|
139
146
|
//#endregion
|
|
140
|
-
export { Contact, DomainRecord, FetchLike, LookupOptions, LookupResult, LookupSource, Nameserver, RegistrarInfo, StatusEvent, isAvailable, isRegistered, lookupDomain };
|
|
147
|
+
export { Contact, DomainRecord, FetchLike, LookupOptions, LookupResult, LookupSource, Nameserver, RegistrarInfo, StatusEvent, isAvailable, isRegistered, lookupDomain, toRegistrableDomain };
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createConnection } from "node:net";
|
|
1
|
+
import { parse } from "tldts";
|
|
3
2
|
|
|
4
3
|
//#region src/lib/domain.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse a domain into its parts.
|
|
6
|
+
*/
|
|
5
7
|
function getDomainParts(domain) {
|
|
6
|
-
|
|
7
|
-
let publicSuffix;
|
|
8
|
-
try {
|
|
9
|
-
publicSuffix = (psl.parse?.(lower))?.tld;
|
|
10
|
-
} catch {}
|
|
11
|
-
if (!publicSuffix) {
|
|
12
|
-
const parts = lower.split(".").filter(Boolean);
|
|
13
|
-
publicSuffix = parts.length ? parts[parts.length - 1] : lower;
|
|
14
|
-
}
|
|
15
|
-
const labels = publicSuffix.split(".").filter(Boolean);
|
|
16
|
-
const tld = labels.length ? labels[labels.length - 1] : publicSuffix;
|
|
17
|
-
return {
|
|
18
|
-
publicSuffix,
|
|
19
|
-
tld
|
|
20
|
-
};
|
|
8
|
+
return parse(domain);
|
|
21
9
|
}
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Basic domain validity check (hostname-like), not performing DNS or RDAP.
|
|
12
|
+
*/
|
|
13
|
+
function isLikelyDomain(value) {
|
|
14
|
+
const v = (value ?? "").trim();
|
|
15
|
+
return /^(?=.{1,253}$)(?:(?!-)[a-z0-9-]{1,63}(?<!-)\.)+(?!-)[a-z0-9-]{2,63}(?<!-)$/.test(v.toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1).
|
|
19
|
+
* Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs).
|
|
20
|
+
*/
|
|
21
|
+
function toRegistrableDomain(input) {
|
|
22
|
+
const raw = (input ?? "").trim();
|
|
23
|
+
if (raw === "") return null;
|
|
24
|
+
const result = parse(raw);
|
|
25
|
+
if (result.isIp) return null;
|
|
26
|
+
if (!result.isIcann) return null;
|
|
27
|
+
const domain = result.domain ?? "";
|
|
28
|
+
if (domain === "") return null;
|
|
29
|
+
return domain.toLowerCase();
|
|
24
30
|
}
|
|
25
31
|
const WHOIS_AVAILABLE_PATTERNS = [
|
|
26
32
|
/\bno match\b/i,
|
|
@@ -555,9 +561,16 @@ async function whoisQuery(server, query, options) {
|
|
|
555
561
|
text
|
|
556
562
|
};
|
|
557
563
|
}
|
|
558
|
-
function queryTcp(host, port, query, options) {
|
|
564
|
+
async function queryTcp(host, port, query, options) {
|
|
565
|
+
let net;
|
|
566
|
+
try {
|
|
567
|
+
net = await import("net");
|
|
568
|
+
} catch {
|
|
569
|
+
net = null;
|
|
570
|
+
}
|
|
571
|
+
if (!net?.createConnection) throw new Error("WHOIS client is only available in Node.js runtimes; try setting `rdapOnly: true`.");
|
|
559
572
|
return new Promise((resolve, reject) => {
|
|
560
|
-
const socket = createConnection({
|
|
573
|
+
const socket = net.createConnection({
|
|
561
574
|
host,
|
|
562
575
|
port
|
|
563
576
|
});
|
|
@@ -977,7 +990,11 @@ async function lookupDomain(domain, opts) {
|
|
|
977
990
|
ok: false,
|
|
978
991
|
error: "Input does not look like a domain"
|
|
979
992
|
};
|
|
980
|
-
const { publicSuffix
|
|
993
|
+
const { publicSuffix: tld } = getDomainParts(domain);
|
|
994
|
+
if (!tld) return {
|
|
995
|
+
ok: false,
|
|
996
|
+
error: "Invalid TLD"
|
|
997
|
+
};
|
|
981
998
|
if (!opts?.whoisOnly) {
|
|
982
999
|
const bases = await getRdapBaseUrlsForTld(tld, opts);
|
|
983
1000
|
const tried = [];
|
|
@@ -1008,19 +1025,6 @@ async function lookupDomain(domain, opts) {
|
|
|
1008
1025
|
};
|
|
1009
1026
|
}
|
|
1010
1027
|
const res = await followWhoisReferrals(whoisServer, domain, opts);
|
|
1011
|
-
if (publicSuffix.includes(".") && /no match|not found/i.test(res.text) && opts?.followWhoisReferral !== false) {
|
|
1012
|
-
const candidates = [];
|
|
1013
|
-
const ps = publicSuffix.toLowerCase();
|
|
1014
|
-
const exception = WHOIS_TLD_EXCEPTIONS[ps];
|
|
1015
|
-
if (exception) candidates.push(exception);
|
|
1016
|
-
for (const server of candidates) try {
|
|
1017
|
-
const alt = await whoisQuery(server, domain, opts);
|
|
1018
|
-
if (alt.text && !/error/i.test(alt.text)) return {
|
|
1019
|
-
ok: true,
|
|
1020
|
-
record: normalizeWhois(domain, tld, alt.text, alt.serverQueried, !!opts?.includeRaw)
|
|
1021
|
-
};
|
|
1022
|
-
} catch {}
|
|
1023
|
-
}
|
|
1024
1028
|
return {
|
|
1025
1029
|
ok: true,
|
|
1026
1030
|
record: normalizeWhois(domain, tld, res.text, res.serverQueried, !!opts?.includeRaw)
|
|
@@ -1048,4 +1052,4 @@ async function isRegistered(domain, opts) {
|
|
|
1048
1052
|
}
|
|
1049
1053
|
|
|
1050
1054
|
//#endregion
|
|
1051
|
-
export { isAvailable, isRegistered, lookupDomain };
|
|
1055
|
+
export { isAvailable, isRegistered, lookupDomain, toRegistrableDomain };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rdapper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "🎩 RDAP/WHOIS fetcher, parser, and normalizer for Node",
|
|
6
6
|
"repository": {
|
|
@@ -39,14 +39,13 @@
|
|
|
39
39
|
"prepublishOnly": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"
|
|
42
|
+
"tldts": "7.0.17"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@biomejs/biome": "2.2.
|
|
46
|
-
"@types/node": "24.
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"typescript": "5.9.2",
|
|
45
|
+
"@biomejs/biome": "2.2.5",
|
|
46
|
+
"@types/node": "24.7.2",
|
|
47
|
+
"tsdown": "0.15.6",
|
|
48
|
+
"typescript": "5.9.3",
|
|
50
49
|
"vitest": "^3.2.4"
|
|
51
50
|
},
|
|
52
51
|
"engines": {
|