@cleandns/whois-rdap 1.0.51 → 1.0.55

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 +69 -80
  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 +65 -82
  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) {
@@ -73,7 +98,7 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
73
98
  link.type === "application/rdap+json")) === null || _d === void 0 ? void 0 : _d.href.replace("/domain/domain/", "/domain/")) || `${options.server}/domain/${domain}`;
74
99
  let thickResponse = null;
75
100
  if (!options.thinOnly && thickRdap) {
76
- // console.log(`fetching thick RDAP: ${thickRdap}`);
101
+ debug("fetching thick RDAP: %s", thickRdap);
77
102
  thickResponse = await _fetch(thickRdap)
78
103
  .then((r) => r.json())
79
104
  .catch(() => null);
@@ -81,7 +106,7 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
81
106
  }
82
107
  else {
83
108
  thickResponse = null;
84
- // console.warn(`thick RDAP failed for ${domain}`);
109
+ debug("thick RDAP failed for %s", domain);
85
110
  }
86
111
  }
87
112
  if ((_e = thickResponse === null || thickResponse === void 0 ? void 0 : thickResponse.rdapConformance) === null || _e === void 0 ? void 0 : _e["0"]) {
@@ -90,11 +115,14 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
90
115
  const registrars = [];
91
116
  const resellers = [];
92
117
  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 || []),
118
+ 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;
119
+ // Use toArray to safely handle entities that might not be iterable
120
+ const entities = toArray(response.entities);
121
+ const entityList = [
122
+ ...entities,
96
123
  response.entity ? { events: response.events, ...response.entity } : null,
97
- ].filter(Boolean)) {
124
+ ].filter(Boolean);
125
+ for (const ent of entityList) {
98
126
  if (((_a = ent.roles) === null || _a === void 0 ? void 0 : _a.includes("registrar")) || ent.role === "registrar") {
99
127
  const pubIds = [];
100
128
  if (ent.publicIds) {
@@ -116,53 +144,59 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
116
144
  || ((_h = pubIds.find((id) => id.type === "Registry Identifier")) === null || _h === void 0 ? void 0 : _h.identifier)
117
145
  || pubIds.find((id) => id.type === "IANA Registrar ID");
118
146
  if (reg) {
119
- // console.log(ent.vcardArray);
120
147
  const id = typeof reg === 'object' ? 0 : reg;
121
148
  const name = (parseInt(id) == id
122
149
  && ((_j = (await ianaIdToRegistrar(parseInt(id)))) === null || _j === void 0 ? void 0 : _j.name))
123
150
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], reg);
124
- const email = ((_k = [ent, ...(ent.entities || [])]
151
+ // Safely handle ent.entities
152
+ const entEntities = toArray(ent.entities);
153
+ const email = ((_k = [ent, ...entEntities]
125
154
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
126
155
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
127
156
  .filter(Boolean)) === null || _k === void 0 ? void 0 : _k[0]) || "";
128
- const abuseEmail = ((_l = [ent, ...(ent.entities || [])]
157
+ const abuseEmail = ((_l = [ent, ...entEntities]
129
158
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
130
159
  .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
160
  .filter(Boolean)) === null || _l === void 0 ? void 0 : _l[0]) || "";
132
161
  const events = ent.events || response.events || ent.enents || response.enents;
133
162
  registrars.push({ id, name, email, abuseEmail, events });
134
163
  }
135
- // handles .ca
164
+ // handles .ca - with safe optional chaining
136
165
  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 || [])]
166
+ const entEntities = toArray(ent.entities);
167
+ const email = ((_q = [ent, ...entEntities]
138
168
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
139
169
  .map((e) => findInObject(e.vcardArray, (el) => Array.isArray(el) && el[0] === "email", (el) => el[3], ""))
140
170
  .filter(Boolean)) === null || _q === void 0 ? void 0 : _q[0]) || "";
141
- const abuseEmail = ((_r = [ent, ...(ent.entities || [])]
171
+ const abuseEmail = ((_r = [ent, ...entEntities]
142
172
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
143
173
  .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
174
  .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 });
175
+ 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]) || '';
176
+ registrars.push({ id: 0, name: vcardName, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
146
177
  }
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 || [])]
178
+ // handles .si - with safe array access
179
+ else if (ent.vcardArray && Array.isArray(ent.vcardArray[1]) && ent.vcardArray[1].find((el) => el[0] === 'fn')) {
180
+ const entEntities = toArray(ent.entities);
181
+ const email = ((_v = [ent, ...entEntities]
150
182
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
151
183
  .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 || [])]
184
+ .filter(Boolean)) === null || _v === void 0 ? void 0 : _v[0]) || "";
185
+ const abuseEmail = ((_w = [ent, ...entEntities]
154
186
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
155
187
  .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]) || "";
188
+ .filter(Boolean)) === null || _w === void 0 ? void 0 : _w[0]) || "";
157
189
  if (ent.handle && ent.handle.toString().match(/^\d+$/)) {
158
190
  const id = ent.handle;
159
191
  const name = (parseInt(id) == id
160
- && ((_u = (await ianaIdToRegistrar(parseInt(id)))) === null || _u === void 0 ? void 0 : _u.name))
192
+ && ((_x = (await ianaIdToRegistrar(parseInt(id)))) === null || _x === void 0 ? void 0 : _x.name))
161
193
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], id);
162
194
  registrars.push({ id, name, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
163
195
  }
164
196
  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 });
197
+ const fnEntry = ent.vcardArray[1].find((el) => el[0] === 'fn');
198
+ const name = fnEntry ? fnEntry[3] : ent.handle || '';
199
+ registrars.push({ id: ent.handle || 0, name, email, abuseEmail, events: ent.events || response.events || ent.enents || response.enents });
166
200
  }
167
201
  }
168
202
  // handles .ar
@@ -171,23 +205,24 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
171
205
  }
172
206
  }
173
207
  if (domain.endsWith(".is") &&
174
- (((_v = ent.roles) === null || _v === void 0 ? void 0 : _v.includes("technical")) || ent.role === "technical")) {
208
+ (((_y = ent.roles) === null || _y === void 0 ? void 0 : _y.includes("technical")) || ent.role === "technical")) {
175
209
  const id = ent.handle;
176
210
  const name = (parseInt(id) == id
177
- && ((_w = (await ianaIdToRegistrar(parseInt(id)))) === null || _w === void 0 ? void 0 : _w.name))
211
+ && ((_z = (await ianaIdToRegistrar(parseInt(id)))) === null || _z === void 0 ? void 0 : _z.name))
178
212
  || findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], id);
179
- const email = ((_x = [ent, ...(ent.entities || [])]
213
+ const entEntities = toArray(ent.entities);
214
+ const email = ((_0 = [ent, ...entEntities]
180
215
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
181
216
  .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 || [])]
217
+ .filter(Boolean)) === null || _0 === void 0 ? void 0 : _0[0]) || "";
218
+ const abuseEmail = ((_1 = [ent, ...entEntities]
184
219
  .filter((e) => e === null || e === void 0 ? void 0 : e.vcardArray)
185
220
  .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]) || "";
221
+ .filter(Boolean)) === null || _1 === void 0 ? void 0 : _1[0]) || "";
187
222
  const events = ent.events || response.events || ent.enents || response.enents;
188
223
  registrars.push({ id, name, email, abuseEmail, events });
189
224
  }
190
- if ((((_z = ent.roles) === null || _z === void 0 ? void 0 : _z.includes("reseller")) || ent.role === "reseller") &&
225
+ if ((((_2 = ent.roles) === null || _2 === void 0 ? void 0 : _2.includes("reseller")) || ent.role === "reseller") &&
191
226
  ent.vcardArray) {
192
227
  // vcard objects can be unexpectedly and arbitrarily nested
193
228
  const name = findInObject(ent.vcardArray, (el) => Array.isArray(el) && (el[0] === "fn" || el[0] === "org"), (el) => el[3], "");
@@ -240,49 +275,3 @@ export async function whois(origDomain, options = { fetch: fetch, thinOnly: fals
240
275
  parseIpResponse(domain, thinResponse, response);
241
276
  return response;
242
277
  }
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
- const date = events.find((ev) => { var _a; return ((_a = ev === null || ev === void 0 ? void 0 : ev.eventAction) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase()) === event; });
283
- if (date === null || date === void 0 ? void 0 : date.eventDate) {
284
- ts[field] = new Date(date.eventDate.toString().replace(/\+0000Z$/, "Z"));
285
- }
286
- }
287
- return ts;
288
- }
@@ -0,0 +1 @@
1
+ import "./polyfills.js";