@cleandns/whois-rdap 1.0.52 → 1.0.57

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.
Files changed (40) hide show
  1. package/.github/workflows/test.yml +38 -0
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +75 -90
  4. package/dist/index.test.d.ts +1 -0
  5. package/dist/index.test.js +360 -0
  6. package/dist/ip.js +8 -1
  7. package/dist/polyfills.d.ts +11 -0
  8. package/dist/polyfills.js +64 -0
  9. package/dist/port43.js +28 -14
  10. package/dist/utils/escapeRegex.d.ts +5 -0
  11. package/dist/utils/escapeRegex.js +7 -0
  12. package/dist/utils/findInObject.js +14 -4
  13. package/dist/utils/findNameservers.d.ts +1 -0
  14. package/dist/utils/findNameservers.js +15 -0
  15. package/dist/utils/findStatus.d.ts +1 -0
  16. package/dist/utils/findStatus.js +10 -0
  17. package/dist/utils/findTimestamps.d.ts +6 -0
  18. package/dist/utils/findTimestamps.js +39 -0
  19. package/dist/utils/toArray.d.ts +5 -0
  20. package/dist/utils/toArray.js +17 -0
  21. package/dist/utils/validateDomain.d.ts +5 -0
  22. package/dist/utils/validateDomain.js +18 -0
  23. package/github-workflow.patch +1044 -0
  24. package/package.json +24 -17
  25. package/src/index.test.ts +440 -0
  26. package/src/index.ts +72 -91
  27. package/src/ip.ts +9 -1
  28. package/src/polyfills.ts +76 -0
  29. package/src/port43.ts +29 -21
  30. package/src/utils/escapeRegex.ts +7 -0
  31. package/src/utils/findInObject.ts +19 -4
  32. package/src/utils/findNameservers.ts +15 -0
  33. package/src/utils/findStatus.ts +12 -0
  34. package/src/utils/findTimestamps.ts +44 -0
  35. package/src/utils/toArray.ts +17 -0
  36. package/src/utils/validateDomain.ts +18 -0
  37. package/tsconfig.json +1 -1
  38. package/dist/wwwservers.d.ts +0 -1
  39. package/dist/wwwservers.js +0 -3
  40. package/pnpm-workspace.yaml +0 -2
@@ -0,0 +1,38 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [18, 20, 22]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Setup pnpm
21
+ uses: pnpm/action-setup@v4
22
+ with:
23
+ version: 9
24
+
25
+ - name: Setup Node.js ${{ matrix.node-version }}
26
+ uses: actions/setup-node@v4
27
+ with:
28
+ node-version: ${{ matrix.node-version }}
29
+ cache: pnpm
30
+
31
+ - name: Install dependencies
32
+ run: pnpm install
33
+
34
+ - name: Build
35
+ run: pnpm run build
36
+
37
+ - name: Run tests
38
+ run: pnpm test
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- import { WhoisOptions, WhoisResponse } from "../whois.js";
1
+ import "./polyfills.js";
2
+ import { WhoisOptions, WhoisResponse, WhoisTimestampFields } from "../whois.js";
3
+ export declare const eventMap: Map<string, WhoisTimestampFields>;
2
4
  export declare function whois(origDomain: string, options?: WhoisOptions): Promise<WhoisResponse>;
package/dist/index.js CHANGED
@@ -1,12 +1,21 @@
1
+ // Polyfills for Node.js 18 compatibility
2
+ import "./polyfills.js";
1
3
  import { parseIpResponse } from "./ip.js";
2
4
  import { determinePort43Domain, port43 } from "./port43.js";
3
5
  import { findInObject } from "./utils/findInObject.js";
4
6
  import { fixArrays } from "./utils/fixArrays.js";
5
7
  import { ianaIdToRegistrar } from "./utils/ianaIdToRegistrar.js";
6
8
  import { tldToRdap } from "./utils/tldToRdap.js";
7
- import { normalizeWhoisStatus } from "./whoisStatus.js";
8
9
  import { resolve4 } from "dns/promises";
9
- const eventMap = new Map([
10
+ import { toArray } from "./utils/toArray.js";
11
+ import { validateDomain } from "./utils/validateDomain.js";
12
+ import { findStatus } from "./utils/findStatus.js";
13
+ import { findNameservers } from "./utils/findNameservers.js";
14
+ import { findTimestamps } from "./utils/findTimestamps.js";
15
+ import createDebug from "debug";
16
+ // Debug logger - enable with DEBUG=whois:* environment variable
17
+ const debug = createDebug("whois:rdap");
18
+ export const eventMap = new Map([
10
19
  ["registration", "created"],
11
20
  ["last changed", "updated"],
12
21
  ["expiration", "expires"],
@@ -15,9 +24,26 @@ const eventMap = new Map([
15
24
  export async function whois(origDomain, options = { fetch: fetch, thinOnly: false }) {
16
25
  var _a, _b, _c, _d, _e, _f;
17
26
  const _fetch = options.fetch || fetch;
18
- let domain = origDomain;
27
+ // Validate and sanitize input
28
+ let domain;
29
+ try {
30
+ domain = validateDomain(origDomain);
31
+ }
32
+ catch (e) {
33
+ return {
34
+ found: false,
35
+ statusCode: 400,
36
+ error: e.message,
37
+ registrar: { id: 0, name: null },
38
+ reseller: null,
39
+ status: [],
40
+ statusDelta: [],
41
+ nameservers: [],
42
+ ts: { created: null, updated: null, expires: null },
43
+ };
44
+ }
19
45
  let url = null;
20
- [domain, url] = await tldToRdap(origDomain);
46
+ [domain, url] = await tldToRdap(domain);
21
47
  const response = {
22
48
  found: false,
23
49
  statusCode: 0,
@@ -48,7 +74,6 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
48
74
  thinResponse = await _fetch(thinRdap)
49
75
  .then((r) => {
50
76
  response.statusCode = r.status;
51
- // console.log({ ok: r.ok, status: r.status, statusText: r.statusText });
52
77
  if (r.status >= 200 && r.status < 400) {
53
78
  return r.json();
54
79
  }
@@ -56,7 +81,7 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
56
81
  return null;
57
82
  })
58
83
  .catch((error) => {
59
- console.warn(`thin RDAP lookup failure: ${error.message}`);
84
+ debug("thin RDAP lookup failure for %s: %s", domain, error.message);
60
85
  return null;
61
86
  });
62
87
  if (thinResponse && !thinResponse.errorCode) {
@@ -68,12 +93,16 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
68
93
  thinResponse = fixArrays(thinResponse);
69
94
  }
70
95
  const selfRdap = (_b = thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.links) === null || _b === void 0 ? void 0 : _b.find((link) => link.rel === "self");
71
- const thickRdap = ((_d = (_c = thinResponse.links) === null || _c === void 0 ? void 0 : _c.find((link) => link.href !== (selfRdap === null || selfRdap === void 0 ? void 0 : selfRdap.href) &&
96
+ // Find the thick RDAP URL from the thin response's links
97
+ const thickRdapFromLinks = (_d = (_c = thinResponse === null || thinResponse === void 0 ? void 0 : thinResponse.links) === null || _c === void 0 ? void 0 : _c.find((link) => link.href !== (selfRdap === null || selfRdap === void 0 ? void 0 : selfRdap.href) &&
72
98
  link.rel === "related" &&
73
- link.type === "application/rdap+json")) === null || _d === void 0 ? void 0 : _d.href.replace("/domain/domain/", "/domain/")) || `${options.server}/domain/${domain}`;
99
+ link.type === "application/rdap+json")) === null || _d === void 0 ? void 0 : _d.href.replace("/domain/domain/", "/domain/");
100
+ // Only use options.server as fallback if it's actually defined
101
+ // This prevents constructing invalid URLs like "undefined/domain/example.com"
102
+ const thickRdap = thickRdapFromLinks || (options.server ? `${options.server}/domain/${domain}` : null);
74
103
  let thickResponse = null;
75
104
  if (!options.thinOnly && thickRdap) {
76
- // console.log(`fetching thick RDAP: ${thickRdap}`);
105
+ debug("fetching thick RDAP: %s", thickRdap);
77
106
  thickResponse = await _fetch(thickRdap)
78
107
  .then((r) => r.json())
79
108
  .catch(() => null);
@@ -81,7 +110,7 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
81
110
  }
82
111
  else {
83
112
  thickResponse = null;
84
- // console.warn(`thick RDAP failed for ${domain}`);
113
+ debug("thick RDAP failed for %s", domain);
85
114
  }
86
115
  }
87
116
  if ((_e = thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.rdapConformance) === null || _e === void 0 ? void 0 : _e["0"]) {
@@ -90,11 +119,14 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
90
119
  const registrars = [];
91
120
  const resellers = [];
92
121
  async function extractRegistrarsAndResellers(response, url, isThick) {
93
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
94
- for (const ent of [
95
- ...(response.entities || []),
122
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
123
+ // Use toArray to safely handle entities that might not be iterable
124
+ const entities = toArray(response.entities);
125
+ const entityList = [
126
+ ...entities,
96
127
  response.entity ? { events: response.events, ...response.entity } : null,
97
- ].filter(Boolean)) {
128
+ ].filter(Boolean);
129
+ for (const ent of entityList) {
98
130
  if (((_a = ent.roles) === null || _a === void 0 ? void 0 : _a.includes("registrar")) || ent.role === "registrar") {
99
131
  const pubIds = [];
100
132
  if (ent.publicIds) {
@@ -116,53 +148,59 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
116
148
  || ((_h = pubIds.find((id) => id.type === "Registry Identifier")) === null || _h === void 0 ? void 0 : _h.identifier)
117
149
  || pubIds.find((id) => id.type === "IANA Registrar ID");
118
150
  if (reg) {
119
- // console.log(ent.vcardArray);
120
151
  const id = typeof reg === 'object' ? 0 : reg;
121
152
  const name = (parseInt(id) == id
122
153
  && ((_j = (await ianaIdToRegistrar(parseInt(id)))) === null || _j === void 0 ? void 0 : _j.name))
123
154
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], reg);
124
- const email = ((_k = [ent, ...(ent.entities || [])]
155
+ // Safely handle ent.entities
156
+ const entEntities = toArray(ent.entities);
157
+ const email = ((_k = [ent, ...entEntities]
125
158
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
126
159
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
127
160
  .filter(Boolean)) === null || _k === void 0 ? void 0 : _k[0]) || "";
128
- const abuseEmail = ((_l = [ent, ...(ent.entities || [])]
161
+ const abuseEmail = ((_l = [ent, ...entEntities]
129
162
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
130
163
  .map((e) => findInObject(e.vcardArray, (el) => { var _a; return Array.isArray(el) && ((_a = e.roles) === null || _a === void 0 ? void 0 : _a.includes("abuse")) && el[0] === "email"; }, (el) => el[3], ""))
131
164
  .filter(Boolean)) === null || _l === void 0 ? void 0 : _l[0]) || "";
132
165
  const events = ent.events || response.events || ent.enents || response.enents;
133
166
  registrars.push({ id, name, email, abuseEmail, events });
134
167
  }
135
- // handles .ca
168
+ // handles .ca - with safe optional chaining
136
169
  else if (((_p = (_o = (_m = ent.vcardArray) === null || _m === void 0 ? void 0 : _m[1]) === null || _o === void 0 ? void 0 : _o[3]) === null || _p === void 0 ? void 0 : _p[3]) === 'registrar') {
137
- const email = ((_q = [ent, ...(ent.entities || [])]
170
+ const entEntities = toArray(ent.entities);
171
+ const email = ((_q = [ent, ...entEntities]
138
172
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
139
173
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
140
174
  .filter(Boolean)) === null || _q === void 0 ? void 0 : _q[0]) || "";
141
- const abuseEmail = ((_r = [ent, ...(ent.entities || [])]
175
+ const abuseEmail = ((_r = [ent, ...entEntities]
142
176
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
143
177
  .map((e) => findInObject(e.vcardArray, (el) => { var _a; return Array.isArray(el) && ((_a = e.roles) === null || _a === void 0 ? void 0 : _a.includes("abuse")) && el[0] === "email"; }, (el) => el[3], ""))
144
178
  .filter(Boolean)) === null || _r === void 0 ? void 0 : _r[0]) || "";
145
- registrars.push({ id: 0, name: ent.vcardArray[1][1][3], email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
179
+ const vcardName = ((_u = (_t = (_s = ent.vcardArray) === null || _s === void 0 ? void 0 : _s[1]) === null || _t === void 0 ? void 0 : _t[1]) === null || _u === void 0 ? void 0 : _u[3]) || '';
180
+ registrars.push({ id: 0, name: vcardName, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
146
181
  }
147
- // handles .si
148
- else if (ent.vcardArray && ent.vcardArray[1] && ent.vcardArray[1].find((el) => el[0] === 'fn')) {
149
- const email = ((_s = [ent, ...(ent.entities || [])]
182
+ // handles .si - with safe array access
183
+ else if (ent.vcardArray && Array.isArray(ent.vcardArray[1]) && ent.vcardArray[1].find((el) => el[0] === 'fn')) {
184
+ const entEntities = toArray(ent.entities);
185
+ const email = ((_v = [ent, ...entEntities]
150
186
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
151
187
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
152
- .filter(Boolean)) === null || _s === void 0 ? void 0 : _s[0]) || "";
153
- const abuseEmail = ((_t = [ent, ...(ent.entities || [])]
188
+ .filter(Boolean)) === null || _v === void 0 ? void 0 : _v[0]) || "";
189
+ const abuseEmail = ((_w = [ent, ...entEntities]
154
190
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
155
191
  .map((e) => findInObject(e.vcardArray, (el) => { var _a; return Array.isArray(el) && ((_a = e.roles) === null || _a === void 0 ? void 0 : _a.includes("abuse")) && el[0] === "email"; }, (el) => el[3], ""))
156
- .filter(Boolean)) === null || _t === void 0 ? void 0 : _t[0]) || "";
192
+ .filter(Boolean)) === null || _w === void 0 ? void 0 : _w[0]) || "";
157
193
  if (ent.handle && ent.handle.toString().match(/^\d+$/)) {
158
194
  const id = ent.handle;
159
195
  const name = (parseInt(id) == id
160
- && ((_u = (await ianaIdToRegistrar(parseInt(id)))) === null || _u === void 0 ? void 0 : _u.name))
196
+ && ((_x = (await ianaIdToRegistrar(parseInt(id)))) === null || _x === void 0 ? void 0 : _x.name))
161
197
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], id);
162
198
  registrars.push({ id, name, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
163
199
  }
164
200
  else {
165
- registrars.push({ id: ent.handle || 0, name: ent.vcardArray[1].find((el) => el[0] === 'fn')[3], email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
201
+ const fnEntry = ent.vcardArray[1].find((el) => el[0] === 'fn');
202
+ const name = fnEntry ? fnEntry[3] : ent.handle || '';
203
+ registrars.push({ id: ent.handle || 0, name, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
166
204
  }
167
205
  }
168
206
  // handles .ar
@@ -171,23 +209,24 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
171
209
  }
172
210
  }
173
211
  if (domain.endsWith(".is") &&
174
- (((_v = ent.roles) === null || _v === void 0 ? void 0 : _v.includes("technical")) || ent.role === "technical")) {
212
+ (((_y = ent.roles) === null || _y === void 0 ? void 0 : _y.includes("technical")) || ent.role === "technical")) {
175
213
  const id = ent.handle;
176
214
  const name = (parseInt(id) == id
177
- && ((_w = (await ianaIdToRegistrar(parseInt(id)))) === null || _w === void 0 ? void 0 : _w.name))
215
+ && ((_z = (await ianaIdToRegistrar(parseInt(id)))) === null || _z === void 0 ? void 0 : _z.name))
178
216
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], id);
179
- const email = ((_x = [ent, ...(ent.entities || [])]
217
+ const entEntities = toArray(ent.entities);
218
+ const email = ((_0 = [ent, ...entEntities]
180
219
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
181
220
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
182
- .filter(Boolean)) === null || _x === void 0 ? void 0 : _x[0]) || "";
183
- const abuseEmail = ((_y = [ent, ...(ent.entities || [])]
221
+ .filter(Boolean)) === null || _0 === void 0 ? void 0 : _0[0]) || "";
222
+ const abuseEmail = ((_1 = [ent, ...entEntities]
184
223
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
185
224
  .map((e) => findInObject(e.vcardArray, (el) => { var _a; return Array.isArray(el) && ((_a = e.roles) === null || _a === void 0 ? void 0 : _a.includes("abuse")) && el[0] === "email"; }, (el) => el[3], ""))
186
- .filter(Boolean)) === null || _y === void 0 ? void 0 : _y[0]) || "";
225
+ .filter(Boolean)) === null || _1 === void 0 ? void 0 : _1[0]) || "";
187
226
  const events = ent.events || response.events || ent.enents || response.enents;
188
227
  registrars.push({ id, name, email, abuseEmail, events });
189
228
  }
190
- if ((((_z = ent.roles) === null || _z === void 0 ? void 0 : _z.includes("reseller")) || ent.role === "reseller") &&
229
+ if ((((_2 = ent.roles) === null || _2 === void 0 ? void 0 : _2.includes("reseller")) || ent.role === "reseller") &&
191
230
  ent.vcardArray) {
192
231
  // vcard objects can be unexpectedly and arbitrarily nested
193
232
  const name = findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], "");
@@ -240,57 +279,3 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
240
279
  parseIpResponse(domain, thinResponse, response);
241
280
  return response;
242
281
  }
243
- function findStatus(statuses, domain) {
244
- // console.warn({ domain, statuses });
245
- return (Array.isArray(statuses)
246
- ? statuses
247
- : statuses && typeof statuses === "object"
248
- ? Object.keys(statuses)
249
- : typeof statuses === "string"
250
- ? statuses.trim().split(/\s*,\s*/)
251
- : []).map((status) => normalizeWhoisStatus(status));
252
- }
253
- function findNameservers(values) {
254
- let nameservers = [];
255
- if (Array.isArray(values)) {
256
- nameservers = values;
257
- }
258
- else if (typeof values === "object") {
259
- nameservers = Object.values(values);
260
- }
261
- return nameservers
262
- .map((ns) => { var _a; return ns.ldhName || ns.ldnName || ((_a = ns.ipAddresses) === null || _a === void 0 ? void 0 : _a.v4); })
263
- .flat()
264
- .filter((ns) => ns)
265
- .map((ns) => (ns.stringValue || ns).toLocaleLowerCase())
266
- .sort();
267
- }
268
- function findTimestamps(values) {
269
- const ts = {
270
- created: null,
271
- updated: null,
272
- expires: null,
273
- };
274
- let events = [];
275
- if (Array.isArray(values)) {
276
- events = values;
277
- }
278
- else if (typeof values === "object") {
279
- events = Object.values(values);
280
- }
281
- for (const [event, field] of eventMap) {
282
- events.find((ev) => {
283
- var _a;
284
- const isMatch = ((_a = ev === null || ev === void 0 ? void 0 : ev.eventAction) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase()) === event && ev.eventDate;
285
- if (isMatch) {
286
- const d = new Date(ev.eventDate.toString().replace(/\+0000Z$/, "Z"));
287
- // console.log(field, ev.eventDate, d);
288
- if (!isNaN(d.valueOf())) {
289
- ts[field] = d;
290
- return true;
291
- }
292
- }
293
- });
294
- }
295
- return ts;
296
- }
@@ -0,0 +1 @@
1
+ import "./polyfills.js";