@push.rocks/smartnetwork 4.5.1 → 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.
@@ -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
- let dnsClient: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns> | null = null;
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 = new plugins.smartdns.dnsClientMod.Smartdns({
394
- strategy: 'prefer-system',
395
- allowDohFallback: true,
396
- timeoutMs: this.timeout,
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
- if (dnsClient) {
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) — uses smartdns, no Rust needed.
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 = new plugins.smartdns.dnsClientMod.Smartdns({
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
  */