@push.rocks/smartnetwork 4.5.2 → 4.7.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartnetwork.classes.domainintelligence.d.ts +170 -0
- package/dist_ts/smartnetwork.classes.domainintelligence.js +542 -0
- package/dist_ts/smartnetwork.classes.ipintelligence.d.ts +16 -0
- package/dist_ts/smartnetwork.classes.ipintelligence.js +100 -18
- package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +23 -2
- package/dist_ts/smartnetwork.classes.smartnetwork.js +52 -8
- package/package.json +1 -1
- package/readme.md +115 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -0
- package/ts/smartnetwork.classes.domainintelligence.ts +659 -0
- package/ts/smartnetwork.classes.ipintelligence.ts +119 -16
- package/ts/smartnetwork.classes.smartnetwork.ts +56 -7
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { domainToASCII } from 'node:url';
|
|
2
|
+
import * as plugins from './smartnetwork.plugins.js';
|
|
3
|
+
import { getLogger } from './logging.js';
|
|
4
|
+
// IANA bootstrap for domain RDAP
|
|
5
|
+
const IANA_BOOTSTRAP_DNS_URL = 'https://data.iana.org/rdap/dns.json';
|
|
6
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
7
|
+
/**
|
|
8
|
+
* Build an empty result object with all fields nulled.
|
|
9
|
+
*/
|
|
10
|
+
function emptyResult(domain = null) {
|
|
11
|
+
return {
|
|
12
|
+
domain,
|
|
13
|
+
handle: null,
|
|
14
|
+
status: null,
|
|
15
|
+
registrationDate: null,
|
|
16
|
+
expirationDate: null,
|
|
17
|
+
lastChangedDate: null,
|
|
18
|
+
registrarName: null,
|
|
19
|
+
registrarIanaId: null,
|
|
20
|
+
registrantOrg: null,
|
|
21
|
+
registrantCountry: null,
|
|
22
|
+
abuseEmail: null,
|
|
23
|
+
abusePhone: null,
|
|
24
|
+
nameservers: null,
|
|
25
|
+
dnssec: null,
|
|
26
|
+
nameserversSource: null,
|
|
27
|
+
resolvedIpv4: null,
|
|
28
|
+
resolvedIpv6: null,
|
|
29
|
+
mxRecords: null,
|
|
30
|
+
txtRecords: null,
|
|
31
|
+
soaRecord: null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* DomainIntelligence performs RDAP lookups for domain names using the
|
|
36
|
+
* IANA DNS bootstrap to discover the correct registry RDAP endpoint per TLD.
|
|
37
|
+
*/
|
|
38
|
+
export class DomainIntelligence {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.logger = getLogger();
|
|
41
|
+
// Bootstrap cache: tld (lowercased) -> RDAP base URL (without trailing slash)
|
|
42
|
+
this.bootstrapEntries = null;
|
|
43
|
+
this.bootstrapPromise = null;
|
|
44
|
+
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
45
|
+
this.sharedDnsClient = options?.dnsClient ?? null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get comprehensive domain intelligence. Runs RDAP and DNS lookups in
|
|
49
|
+
* parallel, then merges the results. Returns an all-null result (rather
|
|
50
|
+
* than throwing) for malformed input, unknown TLDs, or total failure.
|
|
51
|
+
*
|
|
52
|
+
* - RDAP provides: registrar, registrant, events (registration/expiration),
|
|
53
|
+
* nameservers (registry/parent), status, DNSSEC, abuse contact
|
|
54
|
+
* - DNS provides: A/AAAA records, MX, TXT, SOA, and a nameservers fallback
|
|
55
|
+
* when RDAP is unavailable (closes the ccTLD gap)
|
|
56
|
+
*/
|
|
57
|
+
async getIntelligence(domain) {
|
|
58
|
+
const normalized = this.normalizeDomain(domain);
|
|
59
|
+
if (!normalized)
|
|
60
|
+
return emptyResult(null);
|
|
61
|
+
const [rdapSettled, dnsSettled] = await Promise.allSettled([
|
|
62
|
+
this.queryRdapLayer(normalized),
|
|
63
|
+
this.queryDnsLayer(normalized),
|
|
64
|
+
]);
|
|
65
|
+
const result = emptyResult(normalized);
|
|
66
|
+
// Merge RDAP fields (if any) — start with the parsed RDAP result as the base
|
|
67
|
+
if (rdapSettled.status === 'fulfilled' && rdapSettled.value) {
|
|
68
|
+
Object.assign(result, rdapSettled.value);
|
|
69
|
+
if (result.nameservers && result.nameservers.length > 0) {
|
|
70
|
+
result.nameserversSource = 'rdap';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Merge DNS fields (if any)
|
|
74
|
+
if (dnsSettled.status === 'fulfilled' && dnsSettled.value) {
|
|
75
|
+
const dns = dnsSettled.value;
|
|
76
|
+
result.resolvedIpv4 = dns.resolvedIpv4;
|
|
77
|
+
result.resolvedIpv6 = dns.resolvedIpv6;
|
|
78
|
+
result.mxRecords = dns.mxRecords;
|
|
79
|
+
result.txtRecords = dns.txtRecords;
|
|
80
|
+
result.soaRecord = dns.soaRecord;
|
|
81
|
+
// Nameserver fallback: only from DNS when RDAP didn't populate it
|
|
82
|
+
if ((!result.nameservers || result.nameservers.length === 0) && dns.nameservers) {
|
|
83
|
+
result.nameservers = dns.nameservers;
|
|
84
|
+
result.nameserversSource = 'dns';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
// ─── RDAP Layer (existing logic, wrapped) ───────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Run the full RDAP lookup flow for a pre-normalized domain: extract TLD,
|
|
92
|
+
* load bootstrap, match registry, query, and parse. Returns the parsed
|
|
93
|
+
* RDAP fields (as a full IDomainIntelligenceResult) or null if any step
|
|
94
|
+
* fails or the TLD has no RDAP support.
|
|
95
|
+
*/
|
|
96
|
+
async queryRdapLayer(domain) {
|
|
97
|
+
const tld = this.extractTld(domain);
|
|
98
|
+
if (!tld)
|
|
99
|
+
return null;
|
|
100
|
+
await this.ensureBootstrap();
|
|
101
|
+
const baseUrl = this.matchTld(tld);
|
|
102
|
+
if (!baseUrl)
|
|
103
|
+
return null;
|
|
104
|
+
const rdapData = await this.queryRdap(domain, baseUrl);
|
|
105
|
+
if (!rdapData)
|
|
106
|
+
return null;
|
|
107
|
+
return this.parseRdapResponse(domain, rdapData);
|
|
108
|
+
}
|
|
109
|
+
// ─── Normalization & TLD extraction ─────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Normalize a domain to lowercased ASCII punycode form. Returns null for
|
|
112
|
+
* obviously invalid input.
|
|
113
|
+
*/
|
|
114
|
+
normalizeDomain(input) {
|
|
115
|
+
if (typeof input !== 'string')
|
|
116
|
+
return null;
|
|
117
|
+
let trimmed = input.trim().toLowerCase();
|
|
118
|
+
if (!trimmed)
|
|
119
|
+
return null;
|
|
120
|
+
// Strip a single trailing dot (FQDN form)
|
|
121
|
+
if (trimmed.endsWith('.'))
|
|
122
|
+
trimmed = trimmed.slice(0, -1);
|
|
123
|
+
if (!trimmed)
|
|
124
|
+
return null;
|
|
125
|
+
// Reject inputs that contain whitespace, slashes, or other URL noise
|
|
126
|
+
if (/[\s/\\?#]/.test(trimmed))
|
|
127
|
+
return null;
|
|
128
|
+
// Convert IDN to ASCII (punycode). Returns '' for invalid input.
|
|
129
|
+
const ascii = domainToASCII(trimmed);
|
|
130
|
+
if (!ascii)
|
|
131
|
+
return null;
|
|
132
|
+
// Must contain at least one dot to have a TLD
|
|
133
|
+
if (!ascii.includes('.'))
|
|
134
|
+
return null;
|
|
135
|
+
return ascii;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract the TLD as the last dot-separated label.
|
|
139
|
+
*/
|
|
140
|
+
extractTld(domain) {
|
|
141
|
+
const idx = domain.lastIndexOf('.');
|
|
142
|
+
if (idx < 0 || idx === domain.length - 1)
|
|
143
|
+
return null;
|
|
144
|
+
return domain.slice(idx + 1);
|
|
145
|
+
}
|
|
146
|
+
// ─── Bootstrap Subsystem ────────────────────────────────────────────
|
|
147
|
+
/**
|
|
148
|
+
* Load and cache the IANA DNS bootstrap file.
|
|
149
|
+
*/
|
|
150
|
+
async ensureBootstrap() {
|
|
151
|
+
if (this.bootstrapEntries)
|
|
152
|
+
return;
|
|
153
|
+
if (this.bootstrapPromise) {
|
|
154
|
+
await this.bootstrapPromise;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.bootstrapPromise = (async () => {
|
|
158
|
+
try {
|
|
159
|
+
const controller = new AbortController();
|
|
160
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
161
|
+
try {
|
|
162
|
+
const response = await fetch(IANA_BOOTSTRAP_DNS_URL, {
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
headers: { 'User-Agent': '@push.rocks/smartnetwork' },
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok)
|
|
167
|
+
throw new Error(`HTTP ${response.status}`);
|
|
168
|
+
const data = (await response.json());
|
|
169
|
+
const entries = new Map();
|
|
170
|
+
for (const [tlds, urls] of data.services) {
|
|
171
|
+
const baseUrl = urls[0]; // first URL is preferred
|
|
172
|
+
if (!baseUrl)
|
|
173
|
+
continue;
|
|
174
|
+
const cleanBase = baseUrl.replace(/\/$/, ''); // strip trailing slash
|
|
175
|
+
for (const tld of tlds) {
|
|
176
|
+
entries.set(tld.toLowerCase(), cleanBase);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.bootstrapEntries = entries;
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
clearTimeout(timeoutId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
this.logger.debug?.(`Failed to load DNS RDAP bootstrap: ${err.message}`);
|
|
187
|
+
this.bootstrapEntries = new Map(); // empty = all RDAP lookups will skip
|
|
188
|
+
}
|
|
189
|
+
})();
|
|
190
|
+
await this.bootstrapPromise;
|
|
191
|
+
this.bootstrapPromise = null;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Find the RDAP base URL for a given TLD via direct lookup.
|
|
195
|
+
*/
|
|
196
|
+
matchTld(tld) {
|
|
197
|
+
if (!this.bootstrapEntries || this.bootstrapEntries.size === 0)
|
|
198
|
+
return null;
|
|
199
|
+
return this.bootstrapEntries.get(tld.toLowerCase()) ?? null;
|
|
200
|
+
}
|
|
201
|
+
// ─── RDAP Query ─────────────────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Perform the RDAP HTTP query for a domain.
|
|
204
|
+
*/
|
|
205
|
+
async queryRdap(domain, baseUrl) {
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
208
|
+
try {
|
|
209
|
+
const response = await fetch(`${baseUrl}/domain/${encodeURIComponent(domain)}`, {
|
|
210
|
+
signal: controller.signal,
|
|
211
|
+
headers: {
|
|
212
|
+
'Accept': 'application/rdap+json',
|
|
213
|
+
'User-Agent': '@push.rocks/smartnetwork',
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok)
|
|
217
|
+
return null;
|
|
218
|
+
return await response.json();
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
this.logger.debug?.(`RDAP query failed for ${domain}: ${err.message}`);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
clearTimeout(timeoutId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ─── RDAP Response Parsing ──────────────────────────────────────────
|
|
229
|
+
parseRdapResponse(domain, data) {
|
|
230
|
+
const result = emptyResult(domain);
|
|
231
|
+
if (typeof data.handle === 'string')
|
|
232
|
+
result.handle = data.handle;
|
|
233
|
+
if (Array.isArray(data.status) && data.status.length > 0) {
|
|
234
|
+
result.status = data.status.filter((s) => typeof s === 'string');
|
|
235
|
+
}
|
|
236
|
+
// Events: registration / expiration / last changed
|
|
237
|
+
if (Array.isArray(data.events)) {
|
|
238
|
+
const events = this.extractEvents(data.events);
|
|
239
|
+
result.registrationDate = events.registration;
|
|
240
|
+
result.expirationDate = events.expiration;
|
|
241
|
+
result.lastChangedDate = events.lastChanged;
|
|
242
|
+
}
|
|
243
|
+
// Registrar (sponsor) and registrant from entities
|
|
244
|
+
if (Array.isArray(data.entities)) {
|
|
245
|
+
const registrar = this.extractRegistrar(data.entities);
|
|
246
|
+
result.registrarName = registrar.name;
|
|
247
|
+
result.registrarIanaId = registrar.ianaId;
|
|
248
|
+
result.abuseEmail = registrar.abuseEmail;
|
|
249
|
+
result.abusePhone = registrar.abusePhone;
|
|
250
|
+
const registrant = this.extractRegistrant(data.entities);
|
|
251
|
+
result.registrantOrg = registrant.org;
|
|
252
|
+
result.registrantCountry = registrant.country;
|
|
253
|
+
}
|
|
254
|
+
// Nameservers
|
|
255
|
+
if (Array.isArray(data.nameservers)) {
|
|
256
|
+
result.nameservers = this.extractNameservers(data.nameservers);
|
|
257
|
+
}
|
|
258
|
+
// DNSSEC: secureDNS.delegationSigned
|
|
259
|
+
if (data.secureDNS && typeof data.secureDNS.delegationSigned === 'boolean') {
|
|
260
|
+
result.dnssec = data.secureDNS.delegationSigned;
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Pull registration / expiration / last changed timestamps from an
|
|
266
|
+
* RDAP `events` array.
|
|
267
|
+
*/
|
|
268
|
+
extractEvents(events) {
|
|
269
|
+
let registration = null;
|
|
270
|
+
let expiration = null;
|
|
271
|
+
let lastChanged = null;
|
|
272
|
+
for (const ev of events) {
|
|
273
|
+
const action = typeof ev?.eventAction === 'string' ? ev.eventAction.toLowerCase() : '';
|
|
274
|
+
const date = typeof ev?.eventDate === 'string' ? ev.eventDate : null;
|
|
275
|
+
if (!date)
|
|
276
|
+
continue;
|
|
277
|
+
if (action === 'registration')
|
|
278
|
+
registration = date;
|
|
279
|
+
else if (action === 'expiration')
|
|
280
|
+
expiration = date;
|
|
281
|
+
else if (action === 'last changed')
|
|
282
|
+
lastChanged = date;
|
|
283
|
+
}
|
|
284
|
+
return { registration, expiration, lastChanged };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Extract registrar identity (name, IANA ID) and a nested abuse contact.
|
|
288
|
+
*/
|
|
289
|
+
extractRegistrar(entities) {
|
|
290
|
+
let name = null;
|
|
291
|
+
let ianaId = null;
|
|
292
|
+
let abuseEmail = null;
|
|
293
|
+
let abusePhone = null;
|
|
294
|
+
for (const entity of entities) {
|
|
295
|
+
const roles = Array.isArray(entity?.roles) ? entity.roles : [];
|
|
296
|
+
if (!roles.includes('registrar'))
|
|
297
|
+
continue;
|
|
298
|
+
name = this.extractVcardFn(entity);
|
|
299
|
+
// IANA Registrar ID lives in publicIds[]
|
|
300
|
+
if (Array.isArray(entity.publicIds)) {
|
|
301
|
+
for (const pid of entity.publicIds) {
|
|
302
|
+
if (pid && typeof pid === 'object' && pid.type === 'IANA Registrar ID') {
|
|
303
|
+
const parsed = parseInt(String(pid.identifier), 10);
|
|
304
|
+
if (!isNaN(parsed))
|
|
305
|
+
ianaId = parsed;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Abuse contact: nested entity with role "abuse"
|
|
310
|
+
if (Array.isArray(entity.entities)) {
|
|
311
|
+
for (const sub of entity.entities) {
|
|
312
|
+
const subRoles = Array.isArray(sub?.roles) ? sub.roles : [];
|
|
313
|
+
if (subRoles.includes('abuse')) {
|
|
314
|
+
if (!abuseEmail)
|
|
315
|
+
abuseEmail = this.extractVcardEmail(sub);
|
|
316
|
+
if (!abusePhone)
|
|
317
|
+
abusePhone = this.extractVcardTel(sub);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
break; // first registrar wins
|
|
322
|
+
}
|
|
323
|
+
return { name, ianaId, abuseEmail, abusePhone };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Extract registrant org/country from entities array.
|
|
327
|
+
*/
|
|
328
|
+
extractRegistrant(entities) {
|
|
329
|
+
for (const entity of entities) {
|
|
330
|
+
const roles = Array.isArray(entity?.roles) ? entity.roles : [];
|
|
331
|
+
if (!roles.includes('registrant'))
|
|
332
|
+
continue;
|
|
333
|
+
const org = this.extractVcardFn(entity);
|
|
334
|
+
const country = this.extractVcardCountry(entity);
|
|
335
|
+
return { org, country };
|
|
336
|
+
}
|
|
337
|
+
return { org: null, country: null };
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Map an RDAP nameservers[] array to a list of lowercased ldhName strings.
|
|
341
|
+
*/
|
|
342
|
+
extractNameservers(nameservers) {
|
|
343
|
+
const out = [];
|
|
344
|
+
for (const ns of nameservers) {
|
|
345
|
+
const ldh = ns?.ldhName;
|
|
346
|
+
if (typeof ldh === 'string' && ldh.length > 0) {
|
|
347
|
+
out.push(ldh.toLowerCase());
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return out.length > 0 ? out : null;
|
|
351
|
+
}
|
|
352
|
+
// ─── DNS Layer ──────────────────────────────────────────────────────
|
|
353
|
+
/**
|
|
354
|
+
* Run DNS record lookups for the given domain using smartdns. Queries
|
|
355
|
+
* NS/A/AAAA/MX/TXT/SOA in parallel via Promise.allSettled — failures in
|
|
356
|
+
* one record type do not affect the others. Returns an object with each
|
|
357
|
+
* field either populated or null.
|
|
358
|
+
*
|
|
359
|
+
* If a shared dnsClient was injected via constructor options, it is
|
|
360
|
+
* reused and NOT destroyed (ownership stays with the injector). Otherwise
|
|
361
|
+
* a short-lived client is created and destroyed in finally.
|
|
362
|
+
*/
|
|
363
|
+
async queryDnsLayer(domain) {
|
|
364
|
+
const external = this.sharedDnsClient !== null;
|
|
365
|
+
let dnsClient = this.sharedDnsClient;
|
|
366
|
+
try {
|
|
367
|
+
if (!dnsClient) {
|
|
368
|
+
dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
369
|
+
strategy: 'prefer-system',
|
|
370
|
+
allowDohFallback: true,
|
|
371
|
+
timeoutMs: this.timeout,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const [nsRes, aRes, aaaaRes, mxRes, txtRes, soaRes] = await Promise.allSettled([
|
|
375
|
+
dnsClient.getNameServers(domain),
|
|
376
|
+
dnsClient.getRecordsA(domain),
|
|
377
|
+
dnsClient.getRecordsAAAA(domain),
|
|
378
|
+
dnsClient.getRecords(domain, 'MX'),
|
|
379
|
+
dnsClient.getRecordsTxt(domain),
|
|
380
|
+
// 'SOA' is in smartdns's runtime dnsTypeMap but missing from the
|
|
381
|
+
// TDnsRecordType union in @tsclass/tsclass — cast to bypass the
|
|
382
|
+
// stale type definition.
|
|
383
|
+
dnsClient.getRecords(domain, 'SOA'),
|
|
384
|
+
]);
|
|
385
|
+
return {
|
|
386
|
+
nameservers: this.parseNsResult(nsRes),
|
|
387
|
+
resolvedIpv4: this.dnsValuesOrNull(aRes),
|
|
388
|
+
resolvedIpv6: this.dnsValuesOrNull(aaaaRes),
|
|
389
|
+
mxRecords: this.parseMxRecords(mxRes),
|
|
390
|
+
txtRecords: this.dnsValuesOrNull(txtRes),
|
|
391
|
+
soaRecord: this.dnsFirstValueOrNull(soaRes),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
this.logger.debug?.(`DNS layer failed for ${domain}: ${err.message}`);
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
// Only destroy clients we created ourselves; leave injected ones alone.
|
|
400
|
+
if (!external && dnsClient) {
|
|
401
|
+
dnsClient.destroy();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Extract normalized nameserver hostnames from a getNameServers() result.
|
|
407
|
+
*/
|
|
408
|
+
parseNsResult(res) {
|
|
409
|
+
if (res.status !== 'fulfilled' || !Array.isArray(res.value) || res.value.length === 0) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
const out = res.value
|
|
413
|
+
.map((ns) => (typeof ns === 'string' ? ns.toLowerCase().replace(/\.$/, '') : ''))
|
|
414
|
+
.filter((ns) => ns.length > 0);
|
|
415
|
+
return out.length > 0 ? out : null;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Extract `value` strings from a settled DNS lookup result.
|
|
419
|
+
*/
|
|
420
|
+
dnsValuesOrNull(res) {
|
|
421
|
+
if (res.status !== 'fulfilled' || !Array.isArray(res.value) || res.value.length === 0) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const values = res.value
|
|
425
|
+
.map((r) => r.value)
|
|
426
|
+
.filter((v) => typeof v === 'string' && v.length > 0);
|
|
427
|
+
return values.length > 0 ? values : null;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* First non-empty value from a settled DNS lookup result.
|
|
431
|
+
*/
|
|
432
|
+
dnsFirstValueOrNull(res) {
|
|
433
|
+
const values = this.dnsValuesOrNull(res);
|
|
434
|
+
return values?.[0] ?? null;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Parse MX records into {priority, exchange} pairs. smartdns returns MX
|
|
438
|
+
* values as serialized strings (typically "10 mail.example.com"). We
|
|
439
|
+
* best-effort parse the priority; if parsing fails we store the whole
|
|
440
|
+
* value as the exchange with priority=null so the result is still useful.
|
|
441
|
+
*/
|
|
442
|
+
parseMxRecords(res) {
|
|
443
|
+
if (res.status !== 'fulfilled' || !Array.isArray(res.value) || res.value.length === 0) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const out = [];
|
|
447
|
+
for (const r of res.value) {
|
|
448
|
+
if (typeof r.value !== 'string' || !r.value.trim())
|
|
449
|
+
continue;
|
|
450
|
+
const match = r.value.trim().match(/^(\d+)\s+(.+?)\.?$/);
|
|
451
|
+
if (match) {
|
|
452
|
+
out.push({ priority: parseInt(match[1], 10), exchange: match[2].toLowerCase() });
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
out.push({ priority: null, exchange: r.value.toLowerCase().replace(/\.$/, '') });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return out.length > 0 ? out : null;
|
|
459
|
+
}
|
|
460
|
+
// ─── vCard helpers (duplicated from IpIntelligence) ─────────────────
|
|
461
|
+
/**
|
|
462
|
+
* Extract the 'fn' (formatted name) from an entity's vcardArray
|
|
463
|
+
*/
|
|
464
|
+
extractVcardFn(entity) {
|
|
465
|
+
if (!entity?.vcardArray || !Array.isArray(entity.vcardArray))
|
|
466
|
+
return null;
|
|
467
|
+
const properties = entity.vcardArray[1];
|
|
468
|
+
if (!Array.isArray(properties))
|
|
469
|
+
return null;
|
|
470
|
+
for (const prop of properties) {
|
|
471
|
+
if (Array.isArray(prop) && prop[0] === 'fn') {
|
|
472
|
+
return prop[3] || null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Extract email from an entity's vcardArray
|
|
479
|
+
*/
|
|
480
|
+
extractVcardEmail(entity) {
|
|
481
|
+
if (!entity?.vcardArray || !Array.isArray(entity.vcardArray))
|
|
482
|
+
return null;
|
|
483
|
+
const properties = entity.vcardArray[1];
|
|
484
|
+
if (!Array.isArray(properties))
|
|
485
|
+
return null;
|
|
486
|
+
for (const prop of properties) {
|
|
487
|
+
if (Array.isArray(prop) && prop[0] === 'email') {
|
|
488
|
+
return prop[3] || null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Extract telephone number from an entity's vcardArray
|
|
495
|
+
*/
|
|
496
|
+
extractVcardTel(entity) {
|
|
497
|
+
if (!entity?.vcardArray || !Array.isArray(entity.vcardArray))
|
|
498
|
+
return null;
|
|
499
|
+
const properties = entity.vcardArray[1];
|
|
500
|
+
if (!Array.isArray(properties))
|
|
501
|
+
return null;
|
|
502
|
+
for (const prop of properties) {
|
|
503
|
+
if (Array.isArray(prop) && prop[0] === 'tel') {
|
|
504
|
+
// tel value can be a string or a uri like "tel:+1.5555555555"
|
|
505
|
+
const value = prop[3];
|
|
506
|
+
if (typeof value === 'string') {
|
|
507
|
+
return value.startsWith('tel:') ? value.slice(4) : value;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Extract country from an entity's vcardArray address field
|
|
515
|
+
*/
|
|
516
|
+
extractVcardCountry(entity) {
|
|
517
|
+
if (!entity?.vcardArray || !Array.isArray(entity.vcardArray))
|
|
518
|
+
return null;
|
|
519
|
+
const properties = entity.vcardArray[1];
|
|
520
|
+
if (!Array.isArray(properties))
|
|
521
|
+
return null;
|
|
522
|
+
for (const prop of properties) {
|
|
523
|
+
if (Array.isArray(prop) && prop[0] === 'adr') {
|
|
524
|
+
// The label parameter often contains the full address with country at the end
|
|
525
|
+
const label = prop[1]?.label;
|
|
526
|
+
if (typeof label === 'string') {
|
|
527
|
+
const lines = label.split('\n');
|
|
528
|
+
const lastLine = lines[lines.length - 1]?.trim();
|
|
529
|
+
if (lastLine && lastLine.length > 1)
|
|
530
|
+
return lastLine;
|
|
531
|
+
}
|
|
532
|
+
// Also check the structured value (7-element array, last element is country)
|
|
533
|
+
const value = prop[3];
|
|
534
|
+
if (Array.isArray(value) && value.length >= 7 && value[6]) {
|
|
535
|
+
return value[6];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRuZXR3b3JrLmNsYXNzZXMuZG9tYWluaW50ZWxsaWdlbmNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRuZXR3b3JrLmNsYXNzZXMuZG9tYWluaW50ZWxsaWdlbmNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDekMsT0FBTyxLQUFLLE9BQU8sTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBc0V6QyxpQ0FBaUM7QUFDakMsTUFBTSxzQkFBc0IsR0FBRyxxQ0FBcUMsQ0FBQztBQUNyRSxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUM7QUFFN0I7O0dBRUc7QUFDSCxTQUFTLFdBQVcsQ0FBQyxTQUF3QixJQUFJO0lBQy9DLE9BQU87UUFDTCxNQUFNO1FBQ04sTUFBTSxFQUFFLElBQUk7UUFDWixNQUFNLEVBQUUsSUFBSTtRQUNaLGdCQUFnQixFQUFFLElBQUk7UUFDdEIsY0FBYyxFQUFFLElBQUk7UUFDcEIsZUFBZSxFQUFFLElBQUk7UUFDckIsYUFBYSxFQUFFLElBQUk7UUFDbkIsZUFBZSxFQUFFLElBQUk7UUFDckIsYUFBYSxFQUFFLElBQUk7UUFDbkIsaUJBQWlCLEVBQUUsSUFBSTtRQUN2QixVQUFVLEVBQUUsSUFBSTtRQUNoQixVQUFVLEVBQUUsSUFBSTtRQUNoQixXQUFXLEVBQUUsSUFBSTtRQUNqQixNQUFNLEVBQUUsSUFBSTtRQUNaLGlCQUFpQixFQUFFLElBQUk7UUFDdkIsWUFBWSxFQUFFLElBQUk7UUFDbEIsWUFBWSxFQUFFLElBQUk7UUFDbEIsU0FBUyxFQUFFLElBQUk7UUFDZixVQUFVLEVBQUUsSUFBSTtRQUNoQixTQUFTLEVBQUUsSUFBSTtLQUNoQixDQUFDO0FBQ0osQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7SUFXN0IsWUFBWSxPQUFv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
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as plugins from './smartnetwork.plugins.js';
|
|
1
2
|
/**
|
|
2
3
|
* Unified result from all IP intelligence layers
|
|
3
4
|
*/
|
|
@@ -7,6 +8,7 @@ export interface IIpIntelligenceResult {
|
|
|
7
8
|
registrantOrg: string | null;
|
|
8
9
|
registrantCountry: string | null;
|
|
9
10
|
networkRange: string | null;
|
|
11
|
+
networkCidrs: string[] | null;
|
|
10
12
|
abuseContact: string | null;
|
|
11
13
|
country: string | null;
|
|
12
14
|
countryCode: string | null;
|
|
@@ -24,6 +26,13 @@ export interface IIpIntelligenceOptions {
|
|
|
24
26
|
dbMaxAge?: number;
|
|
25
27
|
/** Timeout (ms) for RDAP/DNS/CDN requests. Default: 5000 */
|
|
26
28
|
timeout?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Optional injected smartdns client. When provided, IpIntelligence will
|
|
31
|
+
* not create or destroy its own client (the owner — typically SmartNetwork —
|
|
32
|
+
* manages lifecycle). When omitted, a short-lived client is created per
|
|
33
|
+
* Team Cymru lookup and destroyed in finally.
|
|
34
|
+
*/
|
|
35
|
+
dnsClient?: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
|
|
27
36
|
}
|
|
28
37
|
/**
|
|
29
38
|
* IpIntelligence provides IP address intelligence by combining three data sources:
|
|
@@ -41,6 +50,7 @@ export declare class IpIntelligence {
|
|
|
41
50
|
private refreshPromise;
|
|
42
51
|
private bootstrapEntries;
|
|
43
52
|
private bootstrapPromise;
|
|
53
|
+
private readonly sharedDnsClient;
|
|
44
54
|
constructor(options?: IIpIntelligenceOptions);
|
|
45
55
|
/**
|
|
46
56
|
* Get comprehensive IP intelligence for the given IP address.
|
|
@@ -59,6 +69,8 @@ export declare class IpIntelligence {
|
|
|
59
69
|
* Query RDAP for registration data
|
|
60
70
|
*/
|
|
61
71
|
private queryRdap;
|
|
72
|
+
private parseRdapNetworkInfo;
|
|
73
|
+
private extractRdapCidrs;
|
|
62
74
|
/**
|
|
63
75
|
* Extract the 'fn' (formatted name) from an entity's vcardArray
|
|
64
76
|
*/
|
|
@@ -98,4 +110,8 @@ export declare class IpIntelligence {
|
|
|
98
110
|
* Convert an IPv4 address string to a 32-bit unsigned number
|
|
99
111
|
*/
|
|
100
112
|
private ipToNumber;
|
|
113
|
+
private ipv4RangeToCidrs;
|
|
114
|
+
private ipv4ToBigInt;
|
|
115
|
+
private numberToIpv4;
|
|
116
|
+
private powerOfTwoExponent;
|
|
101
117
|
}
|