rdapper 0.10.4 → 0.12.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 +335 -1
- package/dist/index.d.mts +410 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +104 -19
- package/dist/index.mjs.map +1 -0
- package/package.json +6 -6
- package/dist/index.d.ts +0 -174
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { parse } from "tldts";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The data source used to retrieve domain information.
|
|
7
|
+
*
|
|
8
|
+
* - `rdap`: Data was retrieved via RDAP (Registration Data Access Protocol)
|
|
9
|
+
* - `whois`: Data was retrieved via WHOIS (port 43)
|
|
10
|
+
*/
|
|
11
|
+
type LookupSource = "rdap" | "whois";
|
|
12
|
+
/**
|
|
13
|
+
* Domain registrar information.
|
|
14
|
+
*
|
|
15
|
+
* Contains identifying details about the registrar responsible for the domain registration.
|
|
16
|
+
* Fields may be incomplete depending on the data source and registry policies.
|
|
17
|
+
*/
|
|
18
|
+
interface RegistrarInfo {
|
|
19
|
+
/** Registrar name (e.g., "GoDaddy.com, LLC") */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** IANA-assigned registrar ID */
|
|
22
|
+
ianaId?: string;
|
|
23
|
+
/** Registrar website URL */
|
|
24
|
+
url?: string;
|
|
25
|
+
/** Registrar contact email address */
|
|
26
|
+
email?: string;
|
|
27
|
+
/** Registrar contact phone number */
|
|
28
|
+
phone?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Contact information for various roles associated with a domain.
|
|
32
|
+
*
|
|
33
|
+
* Contacts may represent individuals or organizations responsible for different
|
|
34
|
+
* aspects of domain management. Availability and completeness of contact data
|
|
35
|
+
* varies by TLD, registrar, and privacy policies (GDPR, WHOIS privacy services).
|
|
36
|
+
*/
|
|
37
|
+
interface Contact {
|
|
38
|
+
type: "registrant" | "admin" | "tech" | "billing" | "abuse" | "registrar" | "reseller" | "unknown";
|
|
39
|
+
name?: string;
|
|
40
|
+
organization?: string;
|
|
41
|
+
email?: string | string[];
|
|
42
|
+
phone?: string | string[];
|
|
43
|
+
fax?: string | string[];
|
|
44
|
+
street?: string[];
|
|
45
|
+
city?: string;
|
|
46
|
+
state?: string;
|
|
47
|
+
postalCode?: string;
|
|
48
|
+
country?: string;
|
|
49
|
+
countryCode?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* DNS nameserver information.
|
|
53
|
+
*
|
|
54
|
+
* Represents a nameserver authoritative for the domain, including its hostname
|
|
55
|
+
* and optional glue records (IP addresses).
|
|
56
|
+
*/
|
|
57
|
+
interface Nameserver {
|
|
58
|
+
/** Nameserver hostname (e.g., "ns1.example.com") */
|
|
59
|
+
host: string;
|
|
60
|
+
/** IPv4 glue records, if provided */
|
|
61
|
+
ipv4?: string[];
|
|
62
|
+
/** IPv6 glue records, if provided */
|
|
63
|
+
ipv6?: string[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Domain status information.
|
|
67
|
+
*
|
|
68
|
+
* Represents EPP status codes and registry-specific statuses that indicate
|
|
69
|
+
* the operational state and restrictions on a domain.
|
|
70
|
+
*
|
|
71
|
+
* Common EPP statuses include: clientTransferProhibited, serverHold,
|
|
72
|
+
* serverDeleteProhibited, etc.
|
|
73
|
+
*
|
|
74
|
+
* @see {@link https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en ICANN EPP Status Codes}
|
|
75
|
+
*/
|
|
76
|
+
interface StatusEvent {
|
|
77
|
+
/** Normalized status code (e.g., "clientTransferProhibited") */
|
|
78
|
+
status: string;
|
|
79
|
+
/** Human-readable description of the status, if available */
|
|
80
|
+
description?: string;
|
|
81
|
+
/** Original raw status string from the source */
|
|
82
|
+
raw?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalized domain registration record.
|
|
86
|
+
*
|
|
87
|
+
* This is the primary data structure returned by domain lookups. It provides a unified
|
|
88
|
+
* view of domain registration data regardless of whether the information was obtained
|
|
89
|
+
* via RDAP or WHOIS.
|
|
90
|
+
*
|
|
91
|
+
* Field availability varies by:
|
|
92
|
+
* - TLD and registry policies
|
|
93
|
+
* - Data source (RDAP typically more structured than WHOIS)
|
|
94
|
+
* - Privacy protections (GDPR, WHOIS privacy services)
|
|
95
|
+
* - Registrar practices
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { lookup } from 'rdapper';
|
|
100
|
+
*
|
|
101
|
+
* const { ok, record } = await lookup('example.com');
|
|
102
|
+
* if (ok && record) {
|
|
103
|
+
* console.log(record.registrar?.name); // "Example Registrar, Inc."
|
|
104
|
+
* console.log(record.isRegistered); // true
|
|
105
|
+
* console.log(record.source); // "rdap"
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
interface DomainRecord {
|
|
110
|
+
/** Normalized domain name */
|
|
111
|
+
domain: string;
|
|
112
|
+
/** Terminal TLD */
|
|
113
|
+
tld: string;
|
|
114
|
+
/** Whether the domain is registered */
|
|
115
|
+
isRegistered: boolean;
|
|
116
|
+
/** Whether the domain is internationalized (IDN) */
|
|
117
|
+
isIDN?: boolean;
|
|
118
|
+
/** Unicode name */
|
|
119
|
+
unicodeName?: string;
|
|
120
|
+
/** Punycode name */
|
|
121
|
+
punycodeName?: string;
|
|
122
|
+
/** Registry operator */
|
|
123
|
+
registry?: string;
|
|
124
|
+
/** Registrar */
|
|
125
|
+
registrar?: RegistrarInfo;
|
|
126
|
+
/** Reseller (if applicable) */
|
|
127
|
+
reseller?: string;
|
|
128
|
+
/** EPP status codes */
|
|
129
|
+
statuses?: StatusEvent[];
|
|
130
|
+
/** Creation date in ISO 8601 */
|
|
131
|
+
creationDate?: string;
|
|
132
|
+
/** Updated date in ISO 8601 */
|
|
133
|
+
updatedDate?: string;
|
|
134
|
+
/** Expiration date in ISO 8601 */
|
|
135
|
+
expirationDate?: string;
|
|
136
|
+
/** Deletion date in ISO 8601 */
|
|
137
|
+
deletionDate?: string;
|
|
138
|
+
/** Transfer lock */
|
|
139
|
+
transferLock?: boolean;
|
|
140
|
+
/** DNSSEC data (if available) */
|
|
141
|
+
dnssec?: {
|
|
142
|
+
enabled: boolean;
|
|
143
|
+
dsRecords?: Array<{
|
|
144
|
+
keyTag?: number;
|
|
145
|
+
algorithm?: number;
|
|
146
|
+
digestType?: number;
|
|
147
|
+
digest?: string;
|
|
148
|
+
}>;
|
|
149
|
+
};
|
|
150
|
+
/** Nameservers */
|
|
151
|
+
nameservers?: Nameserver[];
|
|
152
|
+
/** Contacts (registrant, admin, tech, billing, abuse, etc.) */
|
|
153
|
+
contacts?: Contact[];
|
|
154
|
+
/** Best guess as to whether registrant is redacted based on keywords */
|
|
155
|
+
privacyEnabled?: boolean;
|
|
156
|
+
/** Authoritative WHOIS queried (if any) */
|
|
157
|
+
whoisServer?: string;
|
|
158
|
+
/** RDAP base URLs tried */
|
|
159
|
+
rdapServers?: string[];
|
|
160
|
+
/** Raw RDAP JSON */
|
|
161
|
+
rawRdap?: unknown;
|
|
162
|
+
/** Raw WHOIS text (last authoritative) */
|
|
163
|
+
rawWhois?: string;
|
|
164
|
+
/** Which source produced data */
|
|
165
|
+
source: LookupSource;
|
|
166
|
+
/** Warnings generated during lookup */
|
|
167
|
+
warnings?: string[];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* RDAP bootstrap JSON format as published by IANA at https://data.iana.org/rdap/dns.json
|
|
171
|
+
*
|
|
172
|
+
* This interface describes the structure of the RDAP bootstrap registry, which maps
|
|
173
|
+
* top-level domains to their authoritative RDAP servers.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```json
|
|
177
|
+
* {
|
|
178
|
+
* "version": "1.0",
|
|
179
|
+
* "publication": "2025-01-15T12:00:00Z",
|
|
180
|
+
* "description": "RDAP Bootstrap file for DNS top-level domains",
|
|
181
|
+
* "services": [
|
|
182
|
+
* [["com", "net"], ["https://rdap.verisign.com/com/v1/"]],
|
|
183
|
+
* [["org"], ["https://rdap.publicinterestregistry.org/"]]
|
|
184
|
+
* ]
|
|
185
|
+
* }
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7484 RFC 7484 - Finding the Authoritative RDAP Service}
|
|
189
|
+
*/
|
|
190
|
+
interface BootstrapData {
|
|
191
|
+
/** Bootstrap file format version */
|
|
192
|
+
version: string;
|
|
193
|
+
/** ISO 8601 timestamp of when this bootstrap data was published */
|
|
194
|
+
publication: string;
|
|
195
|
+
/** Optional human-readable description of the bootstrap file */
|
|
196
|
+
description?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Service mappings array. Each entry is a tuple of [TLDs, base URLs]:
|
|
199
|
+
* - First element: array of TLD strings (e.g., ["com", "net"])
|
|
200
|
+
* - Second element: array of RDAP base URL strings (e.g., ["https://rdap.verisign.com/com/v1/"])
|
|
201
|
+
*/
|
|
202
|
+
services: string[][][];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Configuration options for domain lookups.
|
|
206
|
+
*
|
|
207
|
+
* Controls the lookup behavior, including which protocols to use (RDAP/WHOIS),
|
|
208
|
+
* timeout settings, referral following, and caching options.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { lookup } from 'rdapper';
|
|
213
|
+
*
|
|
214
|
+
* // RDAP-only lookup for edge runtime compatibility
|
|
215
|
+
* const result = await lookup('example.com', {
|
|
216
|
+
* rdapOnly: true,
|
|
217
|
+
* timeoutMs: 10000
|
|
218
|
+
* });
|
|
219
|
+
*
|
|
220
|
+
* // Cached bootstrap data for high-volume scenarios
|
|
221
|
+
* const cachedBootstrap = await getFromCache();
|
|
222
|
+
* const result = await lookup('example.com', {
|
|
223
|
+
* customBootstrapData: cachedBootstrap,
|
|
224
|
+
* includeRaw: true
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
interface LookupOptions {
|
|
229
|
+
/** Total timeout budget */
|
|
230
|
+
timeoutMs?: number;
|
|
231
|
+
/** Don't fall back to WHOIS */
|
|
232
|
+
rdapOnly?: boolean;
|
|
233
|
+
/** Don't attempt RDAP */
|
|
234
|
+
whoisOnly?: boolean;
|
|
235
|
+
/** Follow referral server (default true) */
|
|
236
|
+
followWhoisReferral?: boolean;
|
|
237
|
+
/** Maximum registrar WHOIS referral hops (default 2) */
|
|
238
|
+
maxWhoisReferralHops?: number;
|
|
239
|
+
/** Follow RDAP related/entity links (default true) */
|
|
240
|
+
rdapFollowLinks?: boolean;
|
|
241
|
+
/** Maximum RDAP related link fetches (default 2) */
|
|
242
|
+
maxRdapLinkHops?: number;
|
|
243
|
+
/** RDAP link rels to consider (default ["related","entity","registrar","alternate"]) */
|
|
244
|
+
rdapLinkRels?: string[];
|
|
245
|
+
/**
|
|
246
|
+
* Pre-loaded RDAP bootstrap data to use instead of fetching from IANA.
|
|
247
|
+
*
|
|
248
|
+
* Pass your own cached version of https://data.iana.org/rdap/dns.json to control
|
|
249
|
+
* caching behavior and avoid redundant network requests. This is useful when you want
|
|
250
|
+
* to cache the bootstrap data in Redis, memory, filesystem, or any other caching layer.
|
|
251
|
+
*
|
|
252
|
+
* If provided, this takes precedence over `customBootstrapUrl` and the default IANA URL.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* import { lookup, type BootstrapData } from 'rdapper';
|
|
257
|
+
*
|
|
258
|
+
* // Fetch and cache the bootstrap data yourself
|
|
259
|
+
* const bootstrapData: BootstrapData = await fetchFromCache()
|
|
260
|
+
* ?? await fetchAndCache('https://data.iana.org/rdap/dns.json');
|
|
261
|
+
*
|
|
262
|
+
* // Pass the cached data to rdapper
|
|
263
|
+
* const result = await lookup('example.com', {
|
|
264
|
+
* customBootstrapData: bootstrapData
|
|
265
|
+
* });
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @see {@link BootstrapData} for the expected data structure
|
|
269
|
+
*/
|
|
270
|
+
customBootstrapData?: BootstrapData;
|
|
271
|
+
/** Override IANA bootstrap URL (ignored if customBootstrapData is provided) */
|
|
272
|
+
customBootstrapUrl?: string;
|
|
273
|
+
/**
|
|
274
|
+
* Custom fetch implementation to use for all HTTP requests.
|
|
275
|
+
*
|
|
276
|
+
* Provides complete control over how HTTP requests are made, enabling advanced use cases:
|
|
277
|
+
* - **Caching**: Cache bootstrap data, RDAP responses, and related link responses
|
|
278
|
+
* - **Logging**: Log all outgoing requests and responses for monitoring
|
|
279
|
+
* - **Retry Logic**: Implement custom retry strategies with exponential backoff
|
|
280
|
+
* - **Rate Limiting**: Control request frequency to respect API limits
|
|
281
|
+
* - **Proxies/Auth**: Route requests through proxies or add authentication headers
|
|
282
|
+
* - **Testing**: Inject mock responses for testing without network calls
|
|
283
|
+
*
|
|
284
|
+
* The custom fetch will be used for:
|
|
285
|
+
* - RDAP bootstrap registry requests (unless `customBootstrapData` is provided)
|
|
286
|
+
* - RDAP domain lookup requests
|
|
287
|
+
* - RDAP related/entity link requests
|
|
288
|
+
*
|
|
289
|
+
* If not provided, the global `fetch` function is used (Node.js 18+ or browser).
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* import { lookup } from 'rdapper';
|
|
294
|
+
*
|
|
295
|
+
* // Example 1: Simple in-memory cache
|
|
296
|
+
* const cache = new Map<string, Response>();
|
|
297
|
+
* const cachedFetch: typeof fetch = async (input, init) => {
|
|
298
|
+
* const key = typeof input === 'string' ? input : input.toString();
|
|
299
|
+
* if (cache.has(key)) return cache.get(key)!.clone();
|
|
300
|
+
* const response = await fetch(input, init);
|
|
301
|
+
* cache.set(key, response.clone());
|
|
302
|
+
* return response;
|
|
303
|
+
* };
|
|
304
|
+
*
|
|
305
|
+
* await lookup('example.com', { customFetch: cachedFetch });
|
|
306
|
+
*
|
|
307
|
+
* // Example 2: Request logging
|
|
308
|
+
* const loggingFetch: typeof fetch = async (input, init) => {
|
|
309
|
+
* const url = typeof input === 'string' ? input : input.toString();
|
|
310
|
+
* console.log('[Fetch]', url);
|
|
311
|
+
* const response = await fetch(input, init);
|
|
312
|
+
* console.log('[Response]', response.status, url);
|
|
313
|
+
* return response;
|
|
314
|
+
* };
|
|
315
|
+
*
|
|
316
|
+
* await lookup('example.com', { customFetch: loggingFetch });
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @see {@link FetchLike} for the expected function signature
|
|
320
|
+
*/
|
|
321
|
+
customFetch?: FetchLike;
|
|
322
|
+
/** Override/add authoritative WHOIS per TLD */
|
|
323
|
+
whoisHints?: Record<string, string>;
|
|
324
|
+
/** Include rawRdap/rawWhois in results (default false) */
|
|
325
|
+
includeRaw?: boolean;
|
|
326
|
+
/** Optional cancellation signal */
|
|
327
|
+
signal?: AbortSignal;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Result of a domain lookup operation.
|
|
331
|
+
*
|
|
332
|
+
* Provides a structured response indicating success or failure, with either
|
|
333
|
+
* a normalized domain record or an error message.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* import { lookup } from 'rdapper';
|
|
338
|
+
*
|
|
339
|
+
* const result = await lookup('example.com');
|
|
340
|
+
* if (result.ok) {
|
|
341
|
+
* console.log('Domain:', result.record.domain);
|
|
342
|
+
* console.log('Registered:', result.record.isRegistered);
|
|
343
|
+
* } else {
|
|
344
|
+
* console.error('Lookup failed:', result.error);
|
|
345
|
+
* }
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
interface LookupResult {
|
|
349
|
+
/** Whether the lookup completed successfully */
|
|
350
|
+
ok: boolean;
|
|
351
|
+
/** The normalized domain record, present when ok is true */
|
|
352
|
+
record?: DomainRecord;
|
|
353
|
+
/** Error message describing why the lookup failed, present when ok is false */
|
|
354
|
+
error?: string;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Fetch-compatible function signature.
|
|
358
|
+
*
|
|
359
|
+
* Used internally for dependency injection and testing. Matches the signature
|
|
360
|
+
* of the global `fetch` function available in Node.js 18+ and browsers.
|
|
361
|
+
*/
|
|
362
|
+
type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/lib/domain.d.ts
|
|
365
|
+
type ParseOptions = Parameters<typeof parse>[1];
|
|
366
|
+
/**
|
|
367
|
+
* Parse a domain into its parts. Passes options to `tldts.parse()`.
|
|
368
|
+
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
|
|
369
|
+
*/
|
|
370
|
+
declare function getDomainParts(domain: string, opts?: ParseOptions): ReturnType<typeof parse>;
|
|
371
|
+
/**
|
|
372
|
+
* Get the TLD (ICANN-only public suffix) of a domain. Passes options to `tldts.parse()`.
|
|
373
|
+
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
|
|
374
|
+
*/
|
|
375
|
+
declare function getDomainTld(domain: string, opts?: ParseOptions): string | null;
|
|
376
|
+
/**
|
|
377
|
+
* Basic domain validity check (hostname-like), not performing DNS or RDAP.
|
|
378
|
+
*/
|
|
379
|
+
declare function isLikelyDomain(value: string): boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1).
|
|
382
|
+
* Passes options to `tldts.parse()`.
|
|
383
|
+
* Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs)
|
|
384
|
+
* @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts
|
|
385
|
+
*/
|
|
386
|
+
declare function toRegistrableDomain(input: string, opts?: ParseOptions): string | null;
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/index.d.ts
|
|
389
|
+
/**
|
|
390
|
+
* High-level lookup that prefers RDAP and falls back to WHOIS.
|
|
391
|
+
* Ensures a standardized DomainRecord, independent of the source.
|
|
392
|
+
*/
|
|
393
|
+
declare function lookup(domain: string, opts?: LookupOptions): Promise<LookupResult>;
|
|
394
|
+
/**
|
|
395
|
+
* Determine if a domain appears available (not registered).
|
|
396
|
+
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
|
|
397
|
+
*/
|
|
398
|
+
declare function isAvailable(domain: string, opts?: LookupOptions): Promise<boolean>;
|
|
399
|
+
/**
|
|
400
|
+
* Determine if a domain appears registered.
|
|
401
|
+
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
|
|
402
|
+
*/
|
|
403
|
+
declare function isRegistered(domain: string, opts?: LookupOptions): Promise<boolean>;
|
|
404
|
+
/**
|
|
405
|
+
* @deprecated Use `lookup` instead.
|
|
406
|
+
*/
|
|
407
|
+
declare const lookupDomain: typeof lookup;
|
|
408
|
+
//#endregion
|
|
409
|
+
export { BootstrapData, Contact, DomainRecord, FetchLike, LookupOptions, LookupResult, LookupSource, Nameserver, RegistrarInfo, StatusEvent, getDomainParts, getDomainTld, isAvailable, isLikelyDomain, isRegistered, lookup, lookupDomain, toRegistrableDomain };
|
|
410
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/lib/domain.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAMA;AAQA;AAoBA;AA6BiB,KAzDL,YAAA,GAyDe,MAAA,GAAA,OAAA;AAoB3B;AAkCA;;;;;AA4Ca,UAnJI,aAAA,CAmJJ;EAYH;EAAY,IAAA,CAAA,EAAA,MAAA;EA0BL;EAuCA,MAAA,CAAA,EAAA,MAAA;EA0CO;EAmDR,GAAA,CAAA,EAAA,MAAA;EAED;EAIJ,KAAA,CAAA,EAAA,MAAA;EAAW;EAsBL,KAAA,CAAA,EAAA,MAAA;AAejB;;;;;;;;UApViB,OAAA;EChCZ,IAAA,EAAA,YAAY,GAAA,OAAqB,GAAA,MAAlB,GAAA,SAAU,GAAA,OAAA,GAAA,WAAA,GAAA,UAAA,GAAA,SAAA;EAMd,IAAA,CAAA,EAAA,MAAA;EAEP,YAAA,CAAA,EAAA,MAAA;EACY,KAAA,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA;EAAlB,KAAA,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA;EAAU,GAAA,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA;EAQG,MAAA,CAAA,EAAA,MAAY,EAAA;EAcZ,IAAA,CAAA,EAAA,MAAA;EAuBA,KAAA,CAAA,EAAA,MAAA;;;;AClChB;;;;;AAyGA;AAaA;AAYa,UFzFI,UAAA,CEyFiB;;;;;;;;;;;;;;;;;;;UFrEjB,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAkCA,YAAA;;;;;;;;;;;;;;;;cAgBH;;;;aAID;;;;;;;;;;;;;;gBAcG;;;;;;;;gBAQA;;aAEH;;;;;;;;;;;;UAYH;;;;;;;;;;;;;;;;;;;;;;;;;UA0BO,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuCA,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0CO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAmDR;;eAED;;;;WAIJ;;;;;;;;;;;;;;;;;;;;;UAsBM,YAAA;;;;WAIN;;;;;;;;;;KAWC,SAAA,oBACM,YACT,gBACJ,QAAQ;;;KCvXR,YAAA,GAAe,kBAAkB;;ADItC;AAQA;AAoBA;AA6BiB,iBCvDD,cAAA,CDuDW,MAAA,EAAA,MAAA,EAAA,IAAA,CAAA,ECrDlB,YDqDkB,CAAA,ECpDxB,UDoDwB,CAAA,OCpDN,KDoDM,CAAA;AAoB3B;AAkCA;;;AAkCgB,iBCpIA,YAAA,CDoIA,MAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EClIP,YDkIO,CAAA,EAAA,MAAA,GAAA,IAAA;;;;AAsBM,iBC5IN,cAAA,CD4IM,KAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAiEtB;;;;;;AAyHiB,iBC/SD,mBAAA,CDmTO,KAAA,EAAA,MAAA,EAAA,IAAA,CAAA,ECjTd,YDiTc,CAAA,EAAA,MAAA,GAAA,IAAA;;;;;AArWvB;AAQA;AAoBiB,iBEZK,MAAA,CFYE,MAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EEVf,aFUe,CAAA,EETrB,OFSqB,CETb,YFSa,CAAA;AA6BxB;AAoBA;AAkCA;;AAoBa,iBEVS,WAAA,CFUT,MAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EERJ,aFQI,CAAA,EEPV,OFOU,CAAA,OAAA,CAAA;;;;;AAoCS,iBEjCA,YAAA,CFiCA,MAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EE/Bb,aF+Ba,CAAA,EE9BnB,OF8BmB,CAAA,OAAA,CAAA;AA0BtB;AAuCA;;AA6FgB,cEnLH,YFmLG,EAAA,OEnLS,MFmLT"}
|
|
@@ -60,26 +60,91 @@ function withTimeout(promise, timeoutMs, reason = "Timeout") {
|
|
|
60
60
|
|
|
61
61
|
//#endregion
|
|
62
62
|
//#region src/lib/constants.ts
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The timeout for HTTP requests in milliseconds. Defaults to 10 seconds.
|
|
65
|
+
*/
|
|
66
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
67
|
+
/**
|
|
68
|
+
* The default URL for the IANA RDAP bootstrap file.
|
|
69
|
+
*
|
|
70
|
+
* @see {@link https://data.iana.org/rdap/dns.json IANA RDAP Bootstrap File (dns.json)}
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json";
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/lib/fetch.ts
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the fetch implementation to use for HTTP requests.
|
|
78
|
+
*
|
|
79
|
+
* Returns the custom fetch from options if provided, otherwise falls back
|
|
80
|
+
* to the global fetch function. This centralized helper ensures consistent
|
|
81
|
+
* fetch resolution across all RDAP HTTP operations.
|
|
82
|
+
*
|
|
83
|
+
* Used internally by:
|
|
84
|
+
* - Bootstrap registry fetching (`src/rdap/bootstrap.ts`)
|
|
85
|
+
* - RDAP domain lookups (`src/rdap/client.ts`)
|
|
86
|
+
* - RDAP related/entity link requests (`src/rdap/merge.ts`)
|
|
87
|
+
*
|
|
88
|
+
* @param options - Any object that may contain a custom fetch implementation
|
|
89
|
+
* @returns The fetch function to use for HTTP requests
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* import { resolveFetch } from './lib/fetch';
|
|
94
|
+
*
|
|
95
|
+
* const fetchFn = resolveFetch(options);
|
|
96
|
+
* const response = await fetchFn('https://example.com/api', { method: 'GET' });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function resolveFetch(options) {
|
|
100
|
+
return options?.customFetch ?? fetch;
|
|
101
|
+
}
|
|
64
102
|
|
|
65
103
|
//#endregion
|
|
66
104
|
//#region src/rdap/bootstrap.ts
|
|
67
105
|
/**
|
|
68
106
|
* Resolve RDAP base URLs for a given TLD using IANA's bootstrap registry.
|
|
69
107
|
* Returns zero or more base URLs (always suffixed with a trailing slash).
|
|
108
|
+
*
|
|
109
|
+
* Bootstrap data is resolved in the following priority order:
|
|
110
|
+
* 1. `options.customBootstrapData` - pre-loaded bootstrap data (no fetch)
|
|
111
|
+
* 2. `options.customBootstrapUrl` - custom URL to fetch bootstrap data from
|
|
112
|
+
* 3. Default IANA URL - https://data.iana.org/rdap/dns.json
|
|
113
|
+
*
|
|
114
|
+
* @param tld - The top-level domain to look up (e.g., "com", "co.uk")
|
|
115
|
+
* @param options - Optional lookup options including custom bootstrap data/URL
|
|
116
|
+
* @returns Array of RDAP base URLs for the TLD, or empty array if none found
|
|
70
117
|
*/
|
|
71
118
|
async function getRdapBaseUrlsForTld(tld, options) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
119
|
+
let data;
|
|
120
|
+
if (options && "customBootstrapData" in options) {
|
|
121
|
+
const provided = options.customBootstrapData;
|
|
122
|
+
if (!provided || typeof provided !== "object") throw new Error("Invalid customBootstrapData: expected an object. See BootstrapData type for required structure.");
|
|
123
|
+
if (!Array.isArray(provided.services)) throw new Error("Invalid customBootstrapData: missing or invalid \"services\" array. See BootstrapData type for required structure.");
|
|
124
|
+
provided.services.forEach((svc, idx) => {
|
|
125
|
+
if (!Array.isArray(svc) || svc.length < 2 || !Array.isArray(svc[0]) || !Array.isArray(svc[1])) throw new Error(`Invalid customBootstrapData: services[${idx}] must be a tuple of [string[], string[]].`);
|
|
126
|
+
});
|
|
127
|
+
data = provided;
|
|
128
|
+
} else {
|
|
129
|
+
const fetchFn = resolveFetch(options);
|
|
130
|
+
const bootstrapUrl = options?.customBootstrapUrl ?? DEFAULT_BOOTSTRAP_URL;
|
|
131
|
+
try {
|
|
132
|
+
const res = await withTimeout(fetchFn(bootstrapUrl, {
|
|
133
|
+
method: "GET",
|
|
134
|
+
headers: { accept: "application/json" },
|
|
135
|
+
signal: options?.signal
|
|
136
|
+
}), options?.timeoutMs ?? DEFAULT_TIMEOUT_MS, "RDAP bootstrap timeout");
|
|
137
|
+
if (!res.ok) return [];
|
|
138
|
+
data = await res.json();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (err instanceof Error && err.name === "AbortError") throw err;
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
80
144
|
const target = tld.toLowerCase();
|
|
81
145
|
const bases = [];
|
|
82
146
|
for (const svc of data.services) {
|
|
147
|
+
if (!svc[0] || !svc[1]) continue;
|
|
83
148
|
const tlds = svc[0].map((x) => x.toLowerCase());
|
|
84
149
|
const urls = svc[1];
|
|
85
150
|
if (tlds.includes(target)) for (const u of urls) {
|
|
@@ -98,7 +163,7 @@ async function getRdapBaseUrlsForTld(tld, options) {
|
|
|
98
163
|
*/
|
|
99
164
|
async function fetchRdapDomain(domain, baseUrl, options) {
|
|
100
165
|
const url = new URL(`domain/${encodeURIComponent(domain)}`, baseUrl).toString();
|
|
101
|
-
const res = await withTimeout(
|
|
166
|
+
const res = await withTimeout(resolveFetch(options)(url, {
|
|
102
167
|
method: "GET",
|
|
103
168
|
headers: { accept: "application/rdap+json, application/json" },
|
|
104
169
|
signal: options?.signal
|
|
@@ -201,7 +266,7 @@ async function fetchAndMergeRdapRelated(domain, baseDoc, opts) {
|
|
|
201
266
|
};
|
|
202
267
|
}
|
|
203
268
|
async function fetchRdapUrl(url, options) {
|
|
204
|
-
const res = await withTimeout(
|
|
269
|
+
const res = await withTimeout(resolveFetch(options)(url, {
|
|
205
270
|
method: "GET",
|
|
206
271
|
headers: { accept: "application/rdap+json, application/json" },
|
|
207
272
|
signal: options?.signal
|
|
@@ -287,6 +352,7 @@ function parseDateWithRegex(m, _re) {
|
|
|
287
352
|
try {
|
|
288
353
|
if (m[0].includes(":")) {
|
|
289
354
|
const [_$1, y, mo, d, hh, mm, ss, offH, offM] = m;
|
|
355
|
+
if (!y || !mo || !d || !hh || !mm || !ss) return void 0;
|
|
290
356
|
let dt = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(hh), Number(mm), Number(ss));
|
|
291
357
|
if (offH) {
|
|
292
358
|
const sign = offH.startsWith("-") ? -1 : 1;
|
|
@@ -299,11 +365,13 @@ function parseDateWithRegex(m, _re) {
|
|
|
299
365
|
}
|
|
300
366
|
if (m[0].includes("-")) {
|
|
301
367
|
const [_$1, dd$1, monStr$1, yyyy$1] = m;
|
|
368
|
+
if (!monStr$1 || !dd$1 || !yyyy$1) return void 0;
|
|
302
369
|
if (/^\d+$/.test(monStr$1)) return new Date(Date.UTC(Number(yyyy$1), Number(monStr$1) - 1, Number(dd$1)));
|
|
303
370
|
const mon$1 = monthMap[monStr$1.toLowerCase()];
|
|
304
371
|
return new Date(Date.UTC(Number(yyyy$1), mon$1, Number(dd$1)));
|
|
305
372
|
}
|
|
306
373
|
const [_, monStr, dd, yyyy] = m;
|
|
374
|
+
if (!monStr || !dd || !yyyy) return void 0;
|
|
307
375
|
const mon = monthMap[monStr.toLowerCase()];
|
|
308
376
|
return new Date(Date.UTC(Number(yyyy), mon, Number(dd)));
|
|
309
377
|
} catch {}
|
|
@@ -362,7 +430,7 @@ function parseKeyValueLines(text) {
|
|
|
362
430
|
const line = rawLine.replace(/\s+$/, "");
|
|
363
431
|
if (!line.trim()) continue;
|
|
364
432
|
const bracket = line.match(/^\s*\[([^\]]+)\]\s*(.*)$/);
|
|
365
|
-
if (bracket) {
|
|
433
|
+
if (bracket?.[1] !== void 0 && bracket?.[2] !== void 0) {
|
|
366
434
|
const key = bracket[1].trim().toLowerCase();
|
|
367
435
|
const value = bracket[2].trim();
|
|
368
436
|
const list = map.get(key) ?? [];
|
|
@@ -450,7 +518,7 @@ function normalizeRdap(inputDomain, tld, rdap, rdapServersTried, includeRaw = fa
|
|
|
450
518
|
const updatedDate = toISO(asDateLike(byAction("last changed")?.eventDate) ?? asDateLike(doc.lastChangedDate));
|
|
451
519
|
const expirationDate = toISO(asDateLike(byAction("expiration")?.eventDate) ?? asDateLike(doc.expirationDate));
|
|
452
520
|
const deletionDate = toISO(asDateLike(byAction("deletion")?.eventDate) ?? asDateLike(doc.deletionDate));
|
|
453
|
-
const transferLock = !!statuses?.some((s) => /
|
|
521
|
+
const transferLock = !!statuses?.some((s) => /transfer[-\s]*prohibited/i.test(s.status));
|
|
454
522
|
const whoisServer = asString(doc.port43);
|
|
455
523
|
return {
|
|
456
524
|
domain: unicodeName || ldhName || inputDomain,
|
|
@@ -865,16 +933,25 @@ const WHOIS_AVAILABLE_PATTERNS = [
|
|
|
865
933
|
/\bnot found\b/i,
|
|
866
934
|
/\bno entries found\b/i,
|
|
867
935
|
/\bno data found\b/i,
|
|
936
|
+
/\bno information available\b/i,
|
|
937
|
+
/\bno information was found\b/i,
|
|
938
|
+
/\bno data was found\b/i,
|
|
868
939
|
/\bavailable for registration\b/i,
|
|
869
940
|
/\bdomain\s+available\b/i,
|
|
870
941
|
/\bdomain status[:\s]+available\b/i,
|
|
871
942
|
/\bobject does not exist\b/i,
|
|
872
943
|
/\bthe queried object does not exist\b/i,
|
|
873
944
|
/\bqueried object does not exist\b/i,
|
|
945
|
+
/\bdoes not exist\b/i,
|
|
874
946
|
/\breturned 0 objects\b/i,
|
|
947
|
+
/\bnot been registered\b/i,
|
|
948
|
+
/\bunassignable\b/i,
|
|
949
|
+
/\bis free\b/i,
|
|
875
950
|
/\bstatus:\s*free\b/i,
|
|
876
951
|
/\bstatus:\s*available\b/i,
|
|
877
952
|
/\bno object found\b/i,
|
|
953
|
+
/\bobject_not_found\b/i,
|
|
954
|
+
/\bno se encuentra registrado\b/i,
|
|
878
955
|
/\bnicht gefunden\b/i,
|
|
879
956
|
/\bpending release\b/i
|
|
880
957
|
];
|
|
@@ -976,10 +1053,13 @@ function normalizeWhois(domain, tld, whoisText, whoisServer, includeRaw = false)
|
|
|
976
1053
|
};
|
|
977
1054
|
})();
|
|
978
1055
|
const statusLines = map["domain status"] || map.status || map.flags || map.state || map["registration status"] || map.eppstatus || [];
|
|
979
|
-
const statuses = statusLines.length ? statusLines.map((line) =>
|
|
980
|
-
status
|
|
981
|
-
|
|
982
|
-
|
|
1056
|
+
const statuses = statusLines.length ? statusLines.map((line) => {
|
|
1057
|
+
const status = line.split(/\s+/)[0];
|
|
1058
|
+
return status ? {
|
|
1059
|
+
status,
|
|
1060
|
+
raw: line
|
|
1061
|
+
} : null;
|
|
1062
|
+
}).filter((s) => s !== null) : void 0;
|
|
983
1063
|
const nsLines = [
|
|
984
1064
|
...map["name server"] || [],
|
|
985
1065
|
...map.nameserver || [],
|
|
@@ -1015,7 +1095,7 @@ function normalizeWhois(domain, tld, whoisText, whoisServer, includeRaw = false)
|
|
|
1015
1095
|
const privacyEnabled = !!(registrant && [registrant.name, registrant.organization].filter(Boolean).some(isPrivacyName));
|
|
1016
1096
|
const dnssecRaw = (map.dnssec?.[0] || "").toLowerCase();
|
|
1017
1097
|
const dnssec = dnssecRaw ? { enabled: /signed|yes|true/.test(dnssecRaw) } : void 0;
|
|
1018
|
-
const transferLock = !!statuses?.some((s) => /
|
|
1098
|
+
const transferLock = !!statuses?.some((s) => /transfer[-\s]*prohibited/i.test(s.raw || s.status || ""));
|
|
1019
1099
|
return {
|
|
1020
1100
|
domain,
|
|
1021
1101
|
tld,
|
|
@@ -1266,6 +1346,10 @@ async function lookup(domain, opts) {
|
|
|
1266
1346
|
};
|
|
1267
1347
|
}
|
|
1268
1348
|
const [first, ...rest] = chain.map((r) => normalizeWhois(domain, tld, r.text, r.serverQueried, !!opts?.includeRaw));
|
|
1349
|
+
if (!first) return {
|
|
1350
|
+
ok: false,
|
|
1351
|
+
error: "No WHOIS data retrieved"
|
|
1352
|
+
};
|
|
1269
1353
|
return {
|
|
1270
1354
|
ok: true,
|
|
1271
1355
|
record: rest.length ? mergeWhoisRecords(first, rest) : first
|
|
@@ -1301,4 +1385,5 @@ async function isRegistered(domain, opts) {
|
|
|
1301
1385
|
const lookupDomain = lookup;
|
|
1302
1386
|
|
|
1303
1387
|
//#endregion
|
|
1304
|
-
export { getDomainParts, getDomainTld, isAvailable, isLikelyDomain, isRegistered, lookup, lookupDomain, toRegistrableDomain };
|
|
1388
|
+
export { getDomainParts, getDomainTld, isAvailable, isLikelyDomain, isRegistered, lookup, lookupDomain, toRegistrableDomain };
|
|
1389
|
+
//# sourceMappingURL=index.mjs.map
|