rdapper 0.4.1 → 0.5.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 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>`
@@ -181,6 +191,7 @@ Project layout:
181
191
  - Some TLDs provide no RDAP service; `rdapOnly: true` will fail for them.
182
192
  - Registries may throttle or block WHOIS; respect rate limits and usage policies.
183
193
  - Field presence depends on source and privacy policies (e.g., redaction/withholding).
194
+ - 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
195
 
185
196
  ## License
186
197
 
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,33 @@
1
- import psl from "psl";
1
+ import { parse } from "tldts";
2
2
  import { createConnection } from "node:net";
3
3
 
4
4
  //#region src/lib/domain.ts
5
+ /**
6
+ * Parse a domain into its parts.
7
+ */
5
8
  function getDomainParts(domain) {
6
- const lower = domain.toLowerCase().trim();
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
- };
9
+ return parse(domain);
10
+ }
11
+ /**
12
+ * Basic domain validity check (hostname-like), not performing DNS or RDAP.
13
+ */
14
+ function isLikelyDomain(value) {
15
+ const v = (value ?? "").trim();
16
+ return /^(?=.{1,253}$)(?:(?!-)[a-z0-9-]{1,63}(?<!-)\.)+(?!-)[a-z0-9-]{2,63}(?<!-)$/.test(v.toLowerCase());
21
17
  }
22
- function isLikelyDomain(input) {
23
- return /^[a-z0-9.-]+$/i.test(input) && input.includes(".");
18
+ /**
19
+ * Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1).
20
+ * Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs).
21
+ */
22
+ function toRegistrableDomain(input) {
23
+ const raw = (input ?? "").trim();
24
+ if (raw === "") return null;
25
+ const result = parse(raw);
26
+ if (result.isIp) return null;
27
+ if (!result.isIcann) return null;
28
+ const domain = result.domain ?? "";
29
+ if (domain === "") return null;
30
+ return domain.toLowerCase();
24
31
  }
25
32
  const WHOIS_AVAILABLE_PATTERNS = [
26
33
  /\bno match\b/i,
@@ -977,7 +984,11 @@ async function lookupDomain(domain, opts) {
977
984
  ok: false,
978
985
  error: "Input does not look like a domain"
979
986
  };
980
- const { publicSuffix, tld } = getDomainParts(domain);
987
+ const { publicSuffix: tld } = getDomainParts(domain);
988
+ if (!tld) return {
989
+ ok: false,
990
+ error: "Invalid TLD"
991
+ };
981
992
  if (!opts?.whoisOnly) {
982
993
  const bases = await getRdapBaseUrlsForTld(tld, opts);
983
994
  const tried = [];
@@ -1008,19 +1019,6 @@ async function lookupDomain(domain, opts) {
1008
1019
  };
1009
1020
  }
1010
1021
  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
1022
  return {
1025
1023
  ok: true,
1026
1024
  record: normalizeWhois(domain, tld, res.text, res.serverQueried, !!opts?.includeRaw)
@@ -1048,4 +1046,4 @@ async function isRegistered(domain, opts) {
1048
1046
  }
1049
1047
 
1050
1048
  //#endregion
1051
- export { isAvailable, isRegistered, lookupDomain };
1049
+ export { isAvailable, isRegistered, lookupDomain, toRegistrableDomain };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdapper",
3
- "version": "0.4.1",
3
+ "version": "0.5.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
- "psl": "1.15.0"
42
+ "tldts": "7.0.17"
43
43
  },
44
44
  "devDependencies": {
45
- "@biomejs/biome": "2.2.4",
46
- "@types/node": "24.5.2",
47
- "@types/psl": "1.1.3",
48
- "tsdown": "0.15.5",
49
- "typescript": "5.9.2",
45
+ "@biomejs/biome": "2.2.5",
46
+ "@types/node": "24.7.1",
47
+ "tsdown": "0.15.6",
48
+ "typescript": "5.9.3",
50
49
  "vitest": "^3.2.4"
51
50
  },
52
51
  "engines": {