rdapper 0.3.0 → 0.4.1
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 +3 -4
- package/dist/index.d.ts +38 -1
- package/dist/index.js +95 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ RDAP‑first domain registration lookups with WHOIS fallback. Produces a single,
|
|
|
4
4
|
|
|
5
5
|
- RDAP discovery via IANA bootstrap (`https://data.iana.org/rdap/dns.json`)
|
|
6
6
|
- WHOIS TCP 43 client with TLD discovery, registrar referral follow, and curated exceptions
|
|
7
|
-
- Normalized output: registrar, contacts, nameservers, statuses, dates, DNSSEC, source metadata
|
|
7
|
+
- Normalized output: registrar, contacts, nameservers, statuses, dates, DNSSEC, privacy flag, source metadata
|
|
8
8
|
- TypeScript types included; ESM‑only; no external HTTP client (uses global `fetch`)
|
|
9
9
|
|
|
10
10
|
### [🦉 See it in action on hoot.sh!](https://hoot.sh)
|
|
@@ -117,12 +117,12 @@ interface DomainRecord {
|
|
|
117
117
|
country?: string;
|
|
118
118
|
countryCode?: string;
|
|
119
119
|
}>;
|
|
120
|
+
privacyEnabled?: boolean; // registrant appears privacy-redacted based on keyword heuristics
|
|
120
121
|
whoisServer?: string; // authoritative WHOIS queried (if any)
|
|
121
122
|
rdapServers?: string[]; // RDAP base URLs tried
|
|
122
123
|
rawRdap?: unknown; // raw RDAP JSON (only when options.includeRaw)
|
|
123
124
|
rawWhois?: string; // raw WHOIS text (only when options.includeRaw)
|
|
124
125
|
source: "rdap" | "whois"; // which path produced data
|
|
125
|
-
fetchedAt: string; // ISO 8601 timestamp
|
|
126
126
|
warnings?: string[];
|
|
127
127
|
}
|
|
128
128
|
```
|
|
@@ -138,8 +138,7 @@ interface DomainRecord {
|
|
|
138
138
|
"statuses": [{ "status": "clientTransferProhibited" }],
|
|
139
139
|
"nameservers": [{ "host": "a.iana-servers.net" }, { "host": "b.iana-servers.net" }],
|
|
140
140
|
"dnssec": { "enabled": true },
|
|
141
|
-
"source": "rdap"
|
|
142
|
-
"fetchedAt": "2025-01-01T00:00:00Z"
|
|
141
|
+
"source": "rdap"
|
|
143
142
|
}
|
|
144
143
|
```
|
|
145
144
|
|
package/dist/index.d.ts
CHANGED
|
@@ -32,21 +32,37 @@ interface StatusEvent {
|
|
|
32
32
|
raw?: string;
|
|
33
33
|
}
|
|
34
34
|
interface DomainRecord {
|
|
35
|
+
/** Normalized domain name */
|
|
35
36
|
domain: string;
|
|
37
|
+
/** Terminal TLD */
|
|
36
38
|
tld: string;
|
|
39
|
+
/** Whether the domain is registered */
|
|
37
40
|
isRegistered: boolean;
|
|
41
|
+
/** Whether the domain is internationalized (IDN) */
|
|
38
42
|
isIDN?: boolean;
|
|
43
|
+
/** Unicode name */
|
|
39
44
|
unicodeName?: string;
|
|
45
|
+
/** Punycode name */
|
|
40
46
|
punycodeName?: string;
|
|
47
|
+
/** Registry operator */
|
|
41
48
|
registry?: string;
|
|
49
|
+
/** Registrar */
|
|
42
50
|
registrar?: RegistrarInfo;
|
|
51
|
+
/** Reseller (if applicable) */
|
|
43
52
|
reseller?: string;
|
|
53
|
+
/** EPP status codes */
|
|
44
54
|
statuses?: StatusEvent[];
|
|
55
|
+
/** Creation date in ISO 8601 */
|
|
45
56
|
creationDate?: string;
|
|
57
|
+
/** Updated date in ISO 8601 */
|
|
46
58
|
updatedDate?: string;
|
|
59
|
+
/** Expiration date in ISO 8601 */
|
|
47
60
|
expirationDate?: string;
|
|
61
|
+
/** Deletion date in ISO 8601 */
|
|
48
62
|
deletionDate?: string;
|
|
63
|
+
/** Transfer lock */
|
|
49
64
|
transferLock?: boolean;
|
|
65
|
+
/** DNSSEC data (if available) */
|
|
50
66
|
dnssec?: {
|
|
51
67
|
enabled: boolean;
|
|
52
68
|
dsRecords?: Array<{
|
|
@@ -56,28 +72,49 @@ interface DomainRecord {
|
|
|
56
72
|
digest?: string;
|
|
57
73
|
}>;
|
|
58
74
|
};
|
|
75
|
+
/** Nameservers */
|
|
59
76
|
nameservers?: Nameserver[];
|
|
77
|
+
/** Contacts (registrant, admin, tech, billing, abuse, etc.) */
|
|
60
78
|
contacts?: Contact[];
|
|
79
|
+
/** Best guess as to whether registrant is redacted based on keywords */
|
|
80
|
+
privacyEnabled?: boolean;
|
|
81
|
+
/** Authoritative WHOIS queried (if any) */
|
|
61
82
|
whoisServer?: string;
|
|
83
|
+
/** RDAP base URLs tried */
|
|
62
84
|
rdapServers?: string[];
|
|
85
|
+
/** Raw RDAP JSON */
|
|
63
86
|
rawRdap?: unknown;
|
|
87
|
+
/** Raw WHOIS text (last authoritative) */
|
|
64
88
|
rawWhois?: string;
|
|
89
|
+
/** Which source produced data */
|
|
65
90
|
source: LookupSource;
|
|
66
|
-
|
|
91
|
+
/** Warnings generated during lookup */
|
|
67
92
|
warnings?: string[];
|
|
68
93
|
}
|
|
69
94
|
interface LookupOptions {
|
|
95
|
+
/** Total timeout budget */
|
|
70
96
|
timeoutMs?: number;
|
|
97
|
+
/** Don't fall back to WHOIS */
|
|
71
98
|
rdapOnly?: boolean;
|
|
99
|
+
/** Don't attempt RDAP */
|
|
72
100
|
whoisOnly?: boolean;
|
|
101
|
+
/** Follow referral server (default true) */
|
|
73
102
|
followWhoisReferral?: boolean;
|
|
103
|
+
/** Maximum registrar WHOIS referral hops (default 2) */
|
|
74
104
|
maxWhoisReferralHops?: number;
|
|
105
|
+
/** Follow RDAP related/entity links (default true) */
|
|
75
106
|
rdapFollowLinks?: boolean;
|
|
107
|
+
/** Maximum RDAP related link fetches (default 2) */
|
|
76
108
|
maxRdapLinkHops?: number;
|
|
109
|
+
/** RDAP link rels to consider (default ["related","entity","registrar","alternate"]) */
|
|
77
110
|
rdapLinkRels?: string[];
|
|
111
|
+
/** Override IANA bootstrap */
|
|
78
112
|
customBootstrapUrl?: string;
|
|
113
|
+
/** Override/add authoritative WHOIS per TLD */
|
|
79
114
|
whoisHints?: Record<string, string>;
|
|
115
|
+
/** Include rawRdap/rawWhois in results (default false) */
|
|
80
116
|
includeRaw?: boolean;
|
|
117
|
+
/** Optional cancellation signal */
|
|
81
118
|
signal?: AbortSignal;
|
|
82
119
|
}
|
|
83
120
|
interface LookupResult {
|
package/dist/index.js
CHANGED
|
@@ -1,74 +1,6 @@
|
|
|
1
1
|
import psl from "psl";
|
|
2
2
|
import { createConnection } from "node:net";
|
|
3
3
|
|
|
4
|
-
//#region src/lib/dates.ts
|
|
5
|
-
function toISO(dateLike) {
|
|
6
|
-
if (dateLike == null) return void 0;
|
|
7
|
-
if (dateLike instanceof Date) return toIsoFromDate(dateLike);
|
|
8
|
-
if (typeof dateLike === "number") return toIsoFromDate(new Date(dateLike));
|
|
9
|
-
const raw = String(dateLike).trim();
|
|
10
|
-
if (!raw) return void 0;
|
|
11
|
-
for (const re of [
|
|
12
|
-
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:Z|([+-]\d{2})(?::?(\d{2}))?)?$/,
|
|
13
|
-
/^(\d{4})\/(\d{2})\/(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:Z|([+-]\d{2})(?::?(\d{2}))?)?$/,
|
|
14
|
-
/^(\d{2})-([A-Za-z]{3})-(\d{4})$/,
|
|
15
|
-
/^([A-Za-z]{3})\s+(\d{1,2})\s+(\d{4})$/
|
|
16
|
-
]) {
|
|
17
|
-
const m = raw.match(re);
|
|
18
|
-
if (!m) continue;
|
|
19
|
-
const d = parseDateWithRegex(m, re);
|
|
20
|
-
if (d) return toIsoFromDate(d);
|
|
21
|
-
}
|
|
22
|
-
const native = new Date(raw);
|
|
23
|
-
if (!Number.isNaN(native.getTime())) return toIsoFromDate(native);
|
|
24
|
-
}
|
|
25
|
-
function toIsoFromDate(d) {
|
|
26
|
-
try {
|
|
27
|
-
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), 0)).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
28
|
-
} catch {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function parseDateWithRegex(m, _re) {
|
|
33
|
-
const monthMap = {
|
|
34
|
-
jan: 0,
|
|
35
|
-
feb: 1,
|
|
36
|
-
mar: 2,
|
|
37
|
-
apr: 3,
|
|
38
|
-
may: 4,
|
|
39
|
-
jun: 5,
|
|
40
|
-
jul: 6,
|
|
41
|
-
aug: 7,
|
|
42
|
-
sep: 8,
|
|
43
|
-
oct: 9,
|
|
44
|
-
nov: 10,
|
|
45
|
-
dec: 11
|
|
46
|
-
};
|
|
47
|
-
try {
|
|
48
|
-
if (m[0].includes(":")) {
|
|
49
|
-
const [_$1, y, mo, d, hh, mm, ss, offH, offM] = m;
|
|
50
|
-
let dt = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(hh), Number(mm), Number(ss));
|
|
51
|
-
if (offH) {
|
|
52
|
-
const sign = offH.startsWith("-") ? -1 : 1;
|
|
53
|
-
const hours = Math.abs(Number(offH));
|
|
54
|
-
const minutes = offM ? Number(offM) : 0;
|
|
55
|
-
const offsetMs = sign * (hours * 60 + minutes) * 60 * 1e3;
|
|
56
|
-
dt -= offsetMs;
|
|
57
|
-
}
|
|
58
|
-
return new Date(dt);
|
|
59
|
-
}
|
|
60
|
-
if (m[0].includes("-")) {
|
|
61
|
-
const [_$1, dd$1, monStr$1, yyyy$1] = m;
|
|
62
|
-
const mon$1 = monthMap[monStr$1.toLowerCase()];
|
|
63
|
-
return new Date(Date.UTC(Number(yyyy$1), mon$1, Number(dd$1)));
|
|
64
|
-
}
|
|
65
|
-
const [_, monStr, dd, yyyy] = m;
|
|
66
|
-
const mon = monthMap[monStr.toLowerCase()];
|
|
67
|
-
return new Date(Date.UTC(Number(yyyy), mon, Number(dd)));
|
|
68
|
-
} catch {}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
//#endregion
|
|
72
4
|
//#region src/lib/domain.ts
|
|
73
5
|
function getDomainParts(domain) {
|
|
74
6
|
const lower = domain.toLowerCase().trim();
|
|
@@ -302,6 +234,90 @@ function sameDomain(a, b) {
|
|
|
302
234
|
return a.toLowerCase() === b.toLowerCase();
|
|
303
235
|
}
|
|
304
236
|
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/lib/dates.ts
|
|
239
|
+
function toISO(dateLike) {
|
|
240
|
+
if (dateLike == null) return void 0;
|
|
241
|
+
if (dateLike instanceof Date) return toIsoFromDate(dateLike);
|
|
242
|
+
if (typeof dateLike === "number") return toIsoFromDate(new Date(dateLike));
|
|
243
|
+
const raw = String(dateLike).trim();
|
|
244
|
+
if (!raw) return void 0;
|
|
245
|
+
for (const re of [
|
|
246
|
+
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:Z|([+-]\d{2})(?::?(\d{2}))?)?$/,
|
|
247
|
+
/^(\d{4})\/(\d{2})\/(\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:Z|([+-]\d{2})(?::?(\d{2}))?)?$/,
|
|
248
|
+
/^(\d{2})-([A-Za-z]{3})-(\d{4})$/,
|
|
249
|
+
/^([A-Za-z]{3})\s+(\d{1,2})\s+(\d{4})$/
|
|
250
|
+
]) {
|
|
251
|
+
const m = raw.match(re);
|
|
252
|
+
if (!m) continue;
|
|
253
|
+
const d = parseDateWithRegex(m, re);
|
|
254
|
+
if (d) return toIsoFromDate(d);
|
|
255
|
+
}
|
|
256
|
+
const native = new Date(raw);
|
|
257
|
+
if (!Number.isNaN(native.getTime())) return toIsoFromDate(native);
|
|
258
|
+
}
|
|
259
|
+
function toIsoFromDate(d) {
|
|
260
|
+
try {
|
|
261
|
+
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), 0)).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function parseDateWithRegex(m, _re) {
|
|
267
|
+
const monthMap = {
|
|
268
|
+
jan: 0,
|
|
269
|
+
feb: 1,
|
|
270
|
+
mar: 2,
|
|
271
|
+
apr: 3,
|
|
272
|
+
may: 4,
|
|
273
|
+
jun: 5,
|
|
274
|
+
jul: 6,
|
|
275
|
+
aug: 7,
|
|
276
|
+
sep: 8,
|
|
277
|
+
oct: 9,
|
|
278
|
+
nov: 10,
|
|
279
|
+
dec: 11
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
if (m[0].includes(":")) {
|
|
283
|
+
const [_$1, y, mo, d, hh, mm, ss, offH, offM] = m;
|
|
284
|
+
let dt = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(hh), Number(mm), Number(ss));
|
|
285
|
+
if (offH) {
|
|
286
|
+
const sign = offH.startsWith("-") ? -1 : 1;
|
|
287
|
+
const hours = Math.abs(Number(offH));
|
|
288
|
+
const minutes = offM ? Number(offM) : 0;
|
|
289
|
+
const offsetMs = sign * (hours * 60 + minutes) * 60 * 1e3;
|
|
290
|
+
dt -= offsetMs;
|
|
291
|
+
}
|
|
292
|
+
return new Date(dt);
|
|
293
|
+
}
|
|
294
|
+
if (m[0].includes("-")) {
|
|
295
|
+
const [_$1, dd$1, monStr$1, yyyy$1] = m;
|
|
296
|
+
const mon$1 = monthMap[monStr$1.toLowerCase()];
|
|
297
|
+
return new Date(Date.UTC(Number(yyyy$1), mon$1, Number(dd$1)));
|
|
298
|
+
}
|
|
299
|
+
const [_, monStr, dd, yyyy] = m;
|
|
300
|
+
const mon = monthMap[monStr.toLowerCase()];
|
|
301
|
+
return new Date(Date.UTC(Number(yyyy), mon, Number(dd)));
|
|
302
|
+
} catch {}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/lib/privacy.ts
|
|
307
|
+
const PRIVACY_NAME_KEYWORDS = [
|
|
308
|
+
"redacted",
|
|
309
|
+
"privacy",
|
|
310
|
+
"private",
|
|
311
|
+
"withheld",
|
|
312
|
+
"not disclosed",
|
|
313
|
+
"protected",
|
|
314
|
+
"protection"
|
|
315
|
+
];
|
|
316
|
+
function isPrivacyName(value) {
|
|
317
|
+
const v = value.toLowerCase();
|
|
318
|
+
return PRIVACY_NAME_KEYWORDS.some((k) => v.includes(k));
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
//#endregion
|
|
306
322
|
//#region src/lib/text.ts
|
|
307
323
|
function uniq(arr) {
|
|
@@ -366,7 +382,7 @@ function asDateLike(value) {
|
|
|
366
382
|
* Convert RDAP JSON into our normalized DomainRecord.
|
|
367
383
|
* This function is defensive: RDAP servers vary in completeness and field naming.
|
|
368
384
|
*/
|
|
369
|
-
function normalizeRdap(inputDomain, tld, rdap, rdapServersTried,
|
|
385
|
+
function normalizeRdap(inputDomain, tld, rdap, rdapServersTried, includeRaw = false) {
|
|
370
386
|
const doc = rdap ?? {};
|
|
371
387
|
const ldhName = asString(doc.ldhName) || asString(doc.handle);
|
|
372
388
|
const unicodeName = asString(doc.unicodeName);
|
|
@@ -382,6 +398,8 @@ function normalizeRdap(inputDomain, tld, rdap, rdapServersTried, fetchedAtISO, i
|
|
|
382
398
|
return n;
|
|
383
399
|
}).filter((n) => !!n.host) : void 0;
|
|
384
400
|
const contacts = extractContacts(doc.entities);
|
|
401
|
+
const registrant = contacts?.find((c) => c.type === "registrant");
|
|
402
|
+
const privacyEnabled = !!(registrant && [registrant.name, registrant.organization].filter(Boolean).some(isPrivacyName));
|
|
385
403
|
const statuses = Array.isArray(doc.status) ? doc.status.filter((s) => typeof s === "string").map((s) => ({
|
|
386
404
|
status: s,
|
|
387
405
|
raw: s
|
|
@@ -426,12 +444,12 @@ function normalizeRdap(inputDomain, tld, rdap, rdapServersTried, fetchedAtISO, i
|
|
|
426
444
|
host: n.host.toLowerCase()
|
|
427
445
|
}))) : void 0,
|
|
428
446
|
contacts,
|
|
447
|
+
privacyEnabled: privacyEnabled ? true : void 0,
|
|
429
448
|
whoisServer,
|
|
430
449
|
rdapServers: rdapServersTried,
|
|
431
450
|
rawRdap: includeRaw ? rdap : void 0,
|
|
432
451
|
rawWhois: void 0,
|
|
433
452
|
source: "rdap",
|
|
434
|
-
fetchedAt: fetchedAtISO,
|
|
435
453
|
warnings: void 0
|
|
436
454
|
};
|
|
437
455
|
}
|
|
@@ -718,7 +736,7 @@ function normalizeServer(server) {
|
|
|
718
736
|
* Convert raw WHOIS text into our normalized DomainRecord.
|
|
719
737
|
* Heuristics cover many gTLD and ccTLD formats; exact fields vary per registry.
|
|
720
738
|
*/
|
|
721
|
-
function normalizeWhois(domain, tld, whoisText, whoisServer,
|
|
739
|
+
function normalizeWhois(domain, tld, whoisText, whoisServer, includeRaw = false) {
|
|
722
740
|
const map = parseKeyValueLines(whoisText);
|
|
723
741
|
const creationDate = anyValue(map, [
|
|
724
742
|
"creation date",
|
|
@@ -799,6 +817,8 @@ function normalizeWhois(domain, tld, whoisText, whoisServer, fetchedAtISO, inclu
|
|
|
799
817
|
return ns;
|
|
800
818
|
}).filter((x) => !!x)) : void 0;
|
|
801
819
|
const contacts = collectContacts(map);
|
|
820
|
+
const registrant = contacts?.find((c) => c.type === "registrant");
|
|
821
|
+
const privacyEnabled = !!(registrant && [registrant.name, registrant.organization].filter(Boolean).some(isPrivacyName));
|
|
802
822
|
const dnssecRaw = (map.dnssec?.[0] || "").toLowerCase();
|
|
803
823
|
const dnssec = dnssecRaw ? { enabled: /signed|yes|true/.test(dnssecRaw) } : void 0;
|
|
804
824
|
const transferLock = !!statuses?.some((s) => /transferprohibited/i.test(s.status));
|
|
@@ -821,12 +841,12 @@ function normalizeWhois(domain, tld, whoisText, whoisServer, fetchedAtISO, inclu
|
|
|
821
841
|
dnssec,
|
|
822
842
|
nameservers,
|
|
823
843
|
contacts,
|
|
844
|
+
privacyEnabled: privacyEnabled ? true : void 0,
|
|
824
845
|
whoisServer,
|
|
825
846
|
rdapServers: void 0,
|
|
826
847
|
rawRdap: void 0,
|
|
827
848
|
rawWhois: includeRaw ? whoisText : void 0,
|
|
828
849
|
source: "whois",
|
|
829
|
-
fetchedAt: fetchedAtISO,
|
|
830
850
|
warnings: void 0
|
|
831
851
|
};
|
|
832
852
|
}
|
|
@@ -958,7 +978,6 @@ async function lookupDomain(domain, opts) {
|
|
|
958
978
|
error: "Input does not look like a domain"
|
|
959
979
|
};
|
|
960
980
|
const { publicSuffix, tld } = getDomainParts(domain);
|
|
961
|
-
const now = toISO(/* @__PURE__ */ new Date()) ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
962
981
|
if (!opts?.whoisOnly) {
|
|
963
982
|
const bases = await getRdapBaseUrlsForTld(tld, opts);
|
|
964
983
|
const tried = [];
|
|
@@ -969,7 +988,7 @@ async function lookupDomain(domain, opts) {
|
|
|
969
988
|
const rdapEnriched = await fetchAndMergeRdapRelated(domain, json, opts);
|
|
970
989
|
return {
|
|
971
990
|
ok: true,
|
|
972
|
-
record: normalizeRdap(domain, tld, rdapEnriched.merged, [...tried, ...rdapEnriched.serversTried],
|
|
991
|
+
record: normalizeRdap(domain, tld, rdapEnriched.merged, [...tried, ...rdapEnriched.serversTried], !!opts?.includeRaw)
|
|
973
992
|
};
|
|
974
993
|
} catch {}
|
|
975
994
|
}
|
|
@@ -998,13 +1017,13 @@ async function lookupDomain(domain, opts) {
|
|
|
998
1017
|
const alt = await whoisQuery(server, domain, opts);
|
|
999
1018
|
if (alt.text && !/error/i.test(alt.text)) return {
|
|
1000
1019
|
ok: true,
|
|
1001
|
-
record: normalizeWhois(domain, tld, alt.text, alt.serverQueried,
|
|
1020
|
+
record: normalizeWhois(domain, tld, alt.text, alt.serverQueried, !!opts?.includeRaw)
|
|
1002
1021
|
};
|
|
1003
1022
|
} catch {}
|
|
1004
1023
|
}
|
|
1005
1024
|
return {
|
|
1006
1025
|
ok: true,
|
|
1007
|
-
record: normalizeWhois(domain, tld, res.text, res.serverQueried,
|
|
1026
|
+
record: normalizeWhois(domain, tld, res.text, res.serverQueried, !!opts?.includeRaw)
|
|
1008
1027
|
};
|
|
1009
1028
|
} catch (err) {
|
|
1010
1029
|
return {
|