@fanboynz/network-scanner 2.0.66 → 3.0.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.
@@ -12,24 +12,33 @@ class DomainCache {
12
12
  constructor(options = {}) {
13
13
  // V8 Optimization: Initialize all properties in constructor for stable hidden class
14
14
  this.cache = new Set();
15
-
15
+
16
16
  // V8 Optimization: Use consistent object shape (no dynamic property addition)
17
- this.stats = {
18
- totalDetected: 0,
19
- totalSkipped: 0,
20
- cacheHits: 0,
21
- cacheMisses: 0
22
- };
23
-
17
+ this.stats = this._freshStats();
18
+
24
19
  // V8 Optimization: Store options directly instead of nested object for faster property access
25
20
  this.enableLogging = options.enableLogging || false;
26
21
  this.logPrefix = options.logPrefix || '[domain-cache]';
27
22
  this.maxCacheSize = options.maxCacheSize || 10000; // Prevent memory leaks
28
-
23
+
29
24
  // V8 Optimization: Pre-calculate 90% target to avoid repeated Math.floor
30
25
  this.targetCacheSize = Math.floor(this.maxCacheSize * 0.9);
31
26
  }
32
27
 
28
+ /**
29
+ * Canonical stats shape. Centralized so the constructor and clear() can't
30
+ * drift if a new counter is added later.
31
+ * @private
32
+ */
33
+ _freshStats() {
34
+ return {
35
+ totalDetected: 0,
36
+ totalSkipped: 0,
37
+ cacheHits: 0,
38
+ cacheMisses: 0
39
+ };
40
+ }
41
+
33
42
  /**
34
43
  * Check if a domain was already detected in a previous scan
35
44
  * @param {string} domain - Domain to check
@@ -61,8 +70,10 @@ class DomainCache {
61
70
  }
62
71
 
63
72
  /**
64
- * Mark a domain as detected for future reference
73
+ * Mark a domain as detected for future reference.
65
74
  * @param {string} domain - Domain to mark as detected
75
+ * @returns {boolean} True if the domain was newly added; false if it was
76
+ * already present or the input was invalid (not a non-empty string)
66
77
  */
67
78
  markDomainAsDetected(domain) {
68
79
  if (!domain || typeof domain !== 'string') {
@@ -80,22 +91,23 @@ class DomainCache {
80
91
  }
81
92
  }
82
93
 
83
- // Check size AFTER adding to prevent race where multiple threads see same size
84
- // and all trigger cleanup before any adds complete
85
- // V8 Optimization: Use pre-calculated targetCacheSize
94
+ // Check size after the add so an overflow only fires eviction once per
95
+ // overflowing call (using targetCacheSize precomputed in the constructor).
86
96
  if (this.cache.size > this.maxCacheSize) {
87
97
  const toRemove = this.cache.size - this.targetCacheSize;
88
98
  if (toRemove > 0) {
89
99
  this.clearOldestEntries(toRemove);
90
100
  }
91
101
  }
92
-
102
+
93
103
  return wasNew;
94
104
  }
95
105
 
96
106
  /**
97
- * Atomically check if domain was detected and mark it if new (race-condition free)
98
- * This method combines isDomainAlreadyDetected + markDomainAsDetected in one atomic operation
107
+ * Combined check-and-mark in one pass. Functionally equivalent to
108
+ * isDomainAlreadyDetected() followed by markDomainAsDetected(), but with
109
+ * one Set.has() call instead of two. (JS is single-threaded so all three
110
+ * variants are individually atomic; this one is just cheaper.)
99
111
  * @param {string} domain - Domain to check and potentially mark
100
112
  * @returns {boolean} True if domain was ALREADY detected (should skip), false if NEW (should process)
101
113
  */
@@ -127,23 +139,22 @@ class DomainCache {
127
139
  console.log(formatLogMessage('debug', `${this.logPrefix} Cache MISS: ${domain} (processing and marked, cache size: ${this.cache.size})`));
128
140
  }
129
141
 
130
- // Check size AFTER adding to prevent race where multiple threads see same size
131
- // and all trigger cleanup before any adds complete
132
- // V8 Optimization: Use pre-calculated targetCacheSize
142
+ // Check size after the add so an overflow only fires eviction once per
143
+ // overflowing call (using targetCacheSize precomputed in the constructor).
133
144
  if (this.cache.size > this.maxCacheSize) {
134
145
  const toRemove = this.cache.size - this.targetCacheSize;
135
146
  if (toRemove > 0) {
136
147
  this.clearOldestEntries(toRemove);
137
148
  }
138
149
  }
139
-
150
+
140
151
  return false; // New domain, should process
141
152
  }
142
153
 
143
154
  /**
144
- * Clear oldest entries from cache (basic LRU simulation)
145
- * Note: Set doesn't maintain insertion order in all Node.js versions,
146
- * so this is a simple implementation that clears a portion of the cache
155
+ * Clear oldest entries from cache (FIFO eviction). Set iteration order is
156
+ * guaranteed insertion order per ES2015, so this genuinely evicts oldest-
157
+ * first on every supported Node version.
147
158
  * @param {number} count - Number of entries to remove
148
159
  */
149
160
  clearOldestEntries(count) {
@@ -181,13 +192,8 @@ class DomainCache {
181
192
  clear() {
182
193
  const previousSize = this.cache.size;
183
194
  this.cache.clear();
184
- this.stats = {
185
- totalDetected: 0,
186
- totalSkipped: 0,
187
- cacheHits: 0,
188
- cacheMisses: 0
189
- };
190
-
195
+ this.stats = this._freshStats();
196
+
191
197
  if (this.enableLogging) {
192
198
  console.log(formatLogMessage('debug', `${this.logPrefix} Cache cleared (${previousSize} entries removed)`));
193
199
  }
@@ -226,21 +232,40 @@ class DomainCache {
226
232
  }
227
233
 
228
234
  /**
229
- * Add multiple domains to cache at once
235
+ * Add multiple domains to cache at once. Uses a single .size delta to
236
+ * count actually-new entries (skipping per-domain .has() calls), and
237
+ * runs the size-overflow eviction check once after the batch instead of
238
+ * per-domain. For a batch of N domains this is N .has() calls saved and
239
+ * up to N redundant cap checks collapsed to one.
230
240
  * @param {Array<string>} domains - Array of domains to add
231
241
  * @returns {number} Number of domains actually added (excludes duplicates)
232
242
  */
233
243
  markMultipleDomainsAsDetected(domains) {
234
- if (!Array.isArray(domains)) {
244
+ if (!Array.isArray(domains) || domains.length === 0) {
235
245
  return 0;
236
246
  }
237
247
 
238
- let addedCount = 0;
239
- domains.forEach(domain => {
240
- if (this.markDomainAsDetected(domain)) {
241
- addedCount++;
248
+ const startSize = this.cache.size;
249
+ for (let i = 0; i < domains.length; i++) {
250
+ const d = domains[i];
251
+ if (d && typeof d === 'string') {
252
+ this.cache.add(d);
253
+ }
254
+ }
255
+ const addedCount = this.cache.size - startSize;
256
+ this.stats.totalDetected += addedCount;
257
+
258
+ if (this.enableLogging && addedCount > 0) {
259
+ console.log(formatLogMessage('debug', `${this.logPrefix} Batch added ${addedCount} new domains (cache size: ${this.cache.size})`));
260
+ }
261
+
262
+ // One eviction sweep at the end, mirroring the single-add overflow check.
263
+ if (this.cache.size > this.maxCacheSize) {
264
+ const toRemove = this.cache.size - this.targetCacheSize;
265
+ if (toRemove > 0) {
266
+ this.clearOldestEntries(toRemove);
242
267
  }
243
- });
268
+ }
244
269
 
245
270
  return addedCount;
246
271
  }
@@ -267,13 +292,34 @@ class DomainCache {
267
292
  let globalDomainCache = null;
268
293
 
269
294
  /**
270
- * Get or create the global domain cache instance
271
- * @param {object} options - Cache options
295
+ * Get or create the global domain cache instance.
296
+ *
297
+ * NOTE: `options` is honored ONLY on the first call (the call that actually
298
+ * constructs the singleton). Subsequent calls return the existing instance
299
+ * regardless of what's passed. If you need different settings, call
300
+ * resetGlobalCache() first or use `new DomainCache(options)` directly.
301
+ *
302
+ * Under debug logging, a warning fires if a later caller passes options
303
+ * that don't match the live instance — silent drift is a recurring source
304
+ * of "why isn't my maxCacheSize taking effect?" confusion.
305
+ *
306
+ * @param {object} options - Cache options (first-call-only)
272
307
  * @returns {DomainCache} Global cache instance
273
308
  */
274
309
  function getGlobalDomainCache(options = {}) {
275
310
  if (!globalDomainCache) {
276
311
  globalDomainCache = new DomainCache(options);
312
+ return globalDomainCache;
313
+ }
314
+ // Singleton already exists — warn if the caller is trying to reconfigure it.
315
+ if (globalDomainCache.enableLogging) {
316
+ const drifted =
317
+ (options.maxCacheSize !== undefined && options.maxCacheSize !== globalDomainCache.maxCacheSize) ||
318
+ (options.enableLogging !== undefined && options.enableLogging !== globalDomainCache.enableLogging) ||
319
+ (options.logPrefix !== undefined && options.logPrefix !== globalDomainCache.logPrefix);
320
+ if (drifted) {
321
+ console.log(formatLogMessage('debug', `${globalDomainCache.logPrefix} getGlobalDomainCache called with options that differ from the live singleton; ignored (call resetGlobalCache() first to apply new options)`));
322
+ }
277
323
  }
278
324
  return globalDomainCache;
279
325
  }
@@ -323,7 +369,8 @@ function markDomainAsDetected(domain) {
323
369
  }
324
370
 
325
371
  /**
326
- * Atomically check and mark a domain (race-condition free)
372
+ * Combined check-and-mark in one pass — one Set.has() call instead of the
373
+ * two you'd pay for isDomainAlreadyDetected() + markDomainAsDetected().
327
374
  * @param {string} domain - Domain to check and mark
328
375
  * @returns {boolean} True if already detected (skip), false if new (process)
329
376
  */