@push.rocks/smartnetwork 4.5.2 → 4.6.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 +9 -0
- package/dist_ts/smartnetwork.classes.ipintelligence.js +13 -8
- 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 +112 -7
- 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 +22 -7
- package/ts/smartnetwork.classes.smartnetwork.ts +56 -7
|
@@ -50,6 +50,13 @@ export interface IIpIntelligenceOptions {
|
|
|
50
50
|
dbMaxAge?: number;
|
|
51
51
|
/** Timeout (ms) for RDAP/DNS/CDN requests. Default: 5000 */
|
|
52
52
|
timeout?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Optional injected smartdns client. When provided, IpIntelligence will
|
|
55
|
+
* not create or destroy its own client (the owner — typically SmartNetwork —
|
|
56
|
+
* manages lifecycle). When omitted, a short-lived client is created per
|
|
57
|
+
* Team Cymru lookup and destroyed in finally.
|
|
58
|
+
*/
|
|
59
|
+
dnsClient?: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
// CDN URLs for GeoLite2 MMDB files (served via jsDelivr from npm packages)
|
|
@@ -93,9 +100,13 @@ export class IpIntelligence {
|
|
|
93
100
|
private bootstrapEntries: IBootstrapEntry[] | null = null;
|
|
94
101
|
private bootstrapPromise: Promise<void> | null = null;
|
|
95
102
|
|
|
103
|
+
// Optional injected smartdns client (shared by SmartNetwork)
|
|
104
|
+
private readonly sharedDnsClient: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns> | null;
|
|
105
|
+
|
|
96
106
|
constructor(options?: IIpIntelligenceOptions) {
|
|
97
107
|
this.dbMaxAge = options?.dbMaxAge ?? DEFAULT_DB_MAX_AGE;
|
|
98
108
|
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
109
|
+
this.sharedDnsClient = options?.dnsClient ?? null;
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
/**
|
|
@@ -385,16 +396,19 @@ export class IpIntelligence {
|
|
|
385
396
|
* Response: "ASN | prefix | CC | rir | date"
|
|
386
397
|
*/
|
|
387
398
|
private async queryTeamCymru(ip: string): Promise<{ asn: number; prefix: string; country: string } | null> {
|
|
388
|
-
|
|
399
|
+
const external = this.sharedDnsClient !== null;
|
|
400
|
+
let dnsClient = this.sharedDnsClient;
|
|
389
401
|
try {
|
|
390
402
|
const reversed = ip.split('.').reverse().join('.');
|
|
391
403
|
const queryName = `${reversed}.origin.asn.cymru.com`;
|
|
392
404
|
|
|
393
|
-
dnsClient
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
405
|
+
if (!dnsClient) {
|
|
406
|
+
dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
407
|
+
strategy: 'prefer-system',
|
|
408
|
+
allowDohFallback: true,
|
|
409
|
+
timeoutMs: this.timeout,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
398
412
|
|
|
399
413
|
const records = await dnsClient.getRecordsTxt(queryName);
|
|
400
414
|
if (!records || records.length === 0) return null;
|
|
@@ -418,7 +432,8 @@ export class IpIntelligence {
|
|
|
418
432
|
this.logger.debug?.(`Team Cymru DNS query failed for ${ip}: ${err.message}`);
|
|
419
433
|
return null;
|
|
420
434
|
} finally {
|
|
421
|
-
|
|
435
|
+
// Only destroy clients we created ourselves; leave injected ones alone.
|
|
436
|
+
if (!external && dnsClient) {
|
|
422
437
|
dnsClient.destroy();
|
|
423
438
|
}
|
|
424
439
|
}
|
|
@@ -2,10 +2,14 @@ import * as plugins from './smartnetwork.plugins.js';
|
|
|
2
2
|
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed.js';
|
|
3
3
|
import { PublicIp } from './smartnetwork.classes.publicip.js';
|
|
4
4
|
import { IpIntelligence, type IIpIntelligenceResult } from './smartnetwork.classes.ipintelligence.js';
|
|
5
|
+
import { DomainIntelligence, type IDomainIntelligenceResult } from './smartnetwork.classes.domainintelligence.js';
|
|
5
6
|
import { getLogger } from './logging.js';
|
|
6
7
|
import { NetworkError } from './errors.js';
|
|
7
8
|
import { RustNetworkBridge } from './smartnetwork.classes.rustbridge.js';
|
|
8
9
|
|
|
10
|
+
/** Type alias for the shared Smartdns client instance */
|
|
11
|
+
type TSmartdnsClient = InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Configuration options for SmartNetwork
|
|
11
15
|
*/
|
|
@@ -54,6 +58,8 @@ export class SmartNetwork {
|
|
|
54
58
|
private rustBridge: RustNetworkBridge;
|
|
55
59
|
private bridgeStarted = false;
|
|
56
60
|
private ipIntelligence: IpIntelligence | null = null;
|
|
61
|
+
private domainIntelligence: DomainIntelligence | null = null;
|
|
62
|
+
private dnsClient: TSmartdnsClient | null = null;
|
|
57
63
|
|
|
58
64
|
constructor(options?: SmartNetworkOptions) {
|
|
59
65
|
this.options = options || {};
|
|
@@ -73,13 +79,24 @@ export class SmartNetwork {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
* Stop the Rust binary bridge.
|
|
82
|
+
* Stop the Rust binary bridge and tear down the shared Smartdns client.
|
|
83
|
+
* Call this before your Node process exits if you've used any DNS or
|
|
84
|
+
* Rust-backed features, otherwise the smartdns Rust backend may keep
|
|
85
|
+
* the event loop alive.
|
|
77
86
|
*/
|
|
78
87
|
public async stop(): Promise<void> {
|
|
79
88
|
if (this.bridgeStarted) {
|
|
80
89
|
await this.rustBridge.stop();
|
|
81
90
|
this.bridgeStarted = false;
|
|
82
91
|
}
|
|
92
|
+
if (this.dnsClient) {
|
|
93
|
+
this.dnsClient.destroy();
|
|
94
|
+
this.dnsClient = null;
|
|
95
|
+
// Intelligence instances hold a stale reference to the destroyed
|
|
96
|
+
// client; drop them so the next call rebuilds with a fresh one.
|
|
97
|
+
this.ipIntelligence = null;
|
|
98
|
+
this.domainIntelligence = null;
|
|
99
|
+
}
|
|
83
100
|
}
|
|
84
101
|
|
|
85
102
|
/**
|
|
@@ -91,6 +108,23 @@ export class SmartNetwork {
|
|
|
91
108
|
}
|
|
92
109
|
}
|
|
93
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Lazily create the shared Smartdns client. The Rust backend inside
|
|
113
|
+
* Smartdns is only spawned on first query that requires it (NS/MX/SOA
|
|
114
|
+
* with prefer-system strategy, or any query with doh/udp strategy).
|
|
115
|
+
* The client is destroyed by stop().
|
|
116
|
+
*/
|
|
117
|
+
private ensureDnsClient(): TSmartdnsClient {
|
|
118
|
+
if (!this.dnsClient) {
|
|
119
|
+
this.dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
120
|
+
strategy: 'prefer-system',
|
|
121
|
+
allowDohFallback: true,
|
|
122
|
+
timeoutMs: 5000,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return this.dnsClient;
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
/**
|
|
95
129
|
* Get network speed via Cloudflare speed test (pure TS, no Rust needed).
|
|
96
130
|
*/
|
|
@@ -310,16 +344,15 @@ export class SmartNetwork {
|
|
|
310
344
|
}
|
|
311
345
|
|
|
312
346
|
/**
|
|
313
|
-
* Resolve DNS records (A, AAAA, MX)
|
|
347
|
+
* Resolve DNS records (A, AAAA, MX) via the shared smartdns client.
|
|
348
|
+
* The client is lifecycle-managed by start()/stop() — MX queries spawn
|
|
349
|
+
* the smartdns Rust bridge, which is torn down by stop().
|
|
314
350
|
*/
|
|
315
351
|
public async resolveDns(
|
|
316
352
|
host: string,
|
|
317
353
|
): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
|
|
318
354
|
try {
|
|
319
|
-
const dnsClient =
|
|
320
|
-
strategy: 'prefer-system',
|
|
321
|
-
allowDohFallback: true,
|
|
322
|
-
});
|
|
355
|
+
const dnsClient = this.ensureDnsClient();
|
|
323
356
|
|
|
324
357
|
const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
|
|
325
358
|
dnsClient.getRecordsA(host).catch((): any[] => []),
|
|
@@ -404,7 +437,7 @@ export class SmartNetwork {
|
|
|
404
437
|
*/
|
|
405
438
|
public async getIpIntelligence(ip: string): Promise<IIpIntelligenceResult> {
|
|
406
439
|
if (!this.ipIntelligence) {
|
|
407
|
-
this.ipIntelligence = new IpIntelligence();
|
|
440
|
+
this.ipIntelligence = new IpIntelligence({ dnsClient: this.ensureDnsClient() });
|
|
408
441
|
}
|
|
409
442
|
const fetcher = () => this.ipIntelligence!.getIntelligence(ip);
|
|
410
443
|
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
@@ -413,6 +446,22 @@ export class SmartNetwork {
|
|
|
413
446
|
return fetcher();
|
|
414
447
|
}
|
|
415
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Get domain intelligence: registrar, registrant, nameservers, registration
|
|
451
|
+
* events, status flags, DNSSEC, and abuse contact via RDAP. Pure TS, no
|
|
452
|
+
* Rust needed.
|
|
453
|
+
*/
|
|
454
|
+
public async getDomainIntelligence(domain: string): Promise<IDomainIntelligenceResult> {
|
|
455
|
+
if (!this.domainIntelligence) {
|
|
456
|
+
this.domainIntelligence = new DomainIntelligence({ dnsClient: this.ensureDnsClient() });
|
|
457
|
+
}
|
|
458
|
+
const fetcher = () => this.domainIntelligence!.getIntelligence(domain);
|
|
459
|
+
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
460
|
+
return this.getCached(`domainIntelligence:${domain}`, fetcher);
|
|
461
|
+
}
|
|
462
|
+
return fetcher();
|
|
463
|
+
}
|
|
464
|
+
|
|
416
465
|
/**
|
|
417
466
|
* Internal caching helper
|
|
418
467
|
*/
|