@fanboynz/network-scanner 2.0.53 → 2.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.
@@ -15,7 +15,7 @@ const CHROME_TEMP_PATHS = [
15
15
  ];
16
16
 
17
17
  const CHROME_TEMP_PATTERNS = [
18
- '.com.google.Chrome.*', // Google Chrome temp files
18
+ 'com.google.Chrome.*', // Google Chrome temp files (no leading dot)
19
19
  '.org.chromium.Chromium.*',
20
20
  'puppeteer-*'
21
21
  ];
@@ -39,6 +39,7 @@ async function cleanupChromeTempFiles(options = {}) {
39
39
 
40
40
  // Base cleanup commands for standard temp directories
41
41
  const cleanupCommands = [
42
+ 'rm -rf /tmp/com.google.Chrome.* 2>/dev/null || true',
42
43
  'rm -rf /tmp/.com.google.Chrome.* 2>/dev/null || true',
43
44
  'rm -rf /tmp/.org.chromium.Chromium.* 2>/dev/null || true',
44
45
  'rm -rf /tmp/puppeteer-* 2>/dev/null || true',
@@ -48,6 +49,7 @@ async function cleanupChromeTempFiles(options = {}) {
48
49
 
49
50
  // Add snap-specific cleanup if requested
50
51
  if (includeSnapTemp || comprehensive) {
52
+ cleanupCommands.push('rm -rf /dev/shm/com.google.Chrome.* 2>/dev/null || true');
51
53
  cleanupCommands.push(
52
54
  'rm -rf /tmp/snap-private-tmp/snap.chromium/tmp/.org.chromium.Chromium.* 2>/dev/null || true',
53
55
  'rm -rf /tmp/snap-private-tmp/snap.chromium/tmp/puppeteer-* 2>/dev/null || true'
@@ -24,28 +24,6 @@ function seededRandom(seed) {
24
24
  const _fingerprintCache = new Map();
25
25
 
26
26
  // Type-specific property spoofing functions for monomorphic optimization
27
- function spoofNavigatorProperties(navigator, properties, options = {}) {
28
- if (!navigator || typeof navigator !== 'object') return false;
29
-
30
- for (const [prop, descriptor] of Object.entries(properties)) {
31
- if (!safeDefineProperty(navigator, prop, descriptor, options)) {
32
- if (options.debug) console.log(`[fingerprint] Failed to spoof navigator.${prop}`);
33
- }
34
- }
35
- return true;
36
- }
37
-
38
- function spoofScreenProperties(screen, properties, options = {}) {
39
- if (!screen || typeof screen !== 'object') return false;
40
-
41
- for (const [prop, descriptor] of Object.entries(properties)) {
42
- if (!safeDefineProperty(screen, prop, descriptor, options)) {
43
- if (options.debug) console.log(`[fingerprint] Failed to spoof screen.${prop}`);
44
- }
45
- }
46
- return true;
47
- }
48
-
49
27
  // Built-in properties that should not be modified
50
28
  const BUILT_IN_PROPERTIES = new Set([
51
29
  'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash',
@@ -379,6 +357,27 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
379
357
  return false;
380
358
  };
381
359
  })();
360
+
361
+ // Function.prototype.toString protection — make spoofed functions appear native
362
+ // Must be installed BEFORE any property overrides so all spoofs are protected
363
+ const nativeFunctionStore = new WeakMap();
364
+ const originalToString = Function.prototype.toString;
365
+
366
+ function maskAsNative(fn, nativeName) {
367
+ if (typeof fn === 'function') {
368
+ nativeFunctionStore.set(fn, nativeName || fn.name || '');
369
+ }
370
+ return fn;
371
+ }
372
+
373
+ Function.prototype.toString = function() {
374
+ if (nativeFunctionStore.has(this)) {
375
+ return `function ${nativeFunctionStore.get(this)}() { [native code] }`;
376
+ }
377
+ return originalToString.call(this);
378
+ };
379
+ // Protect the toString override itself
380
+ nativeFunctionStore.set(Function.prototype.toString, 'toString');
382
381
 
383
382
  // Create safe property definition helper
384
383
  function safeDefinePropertyLocal(target, property, descriptor) {
@@ -439,10 +438,14 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
439
438
  // Remove webdriver properties
440
439
  //
441
440
  safeExecute(() => {
442
- try {
443
- delete navigator.webdriver;
444
- } catch (e) {}
445
- safeDefinePropertyLocal(navigator, 'webdriver', { get: () => false });
441
+ // In real Chrome, webdriver lives on Navigator.prototype, not the instance.
442
+ // Override it there so Object.getOwnPropertyDescriptor(navigator, 'webdriver') returns undefined.
443
+ try { delete navigator.webdriver; } catch (e) {}
444
+ Object.defineProperty(Navigator.prototype, 'webdriver', {
445
+ get: () => false,
446
+ configurable: true,
447
+ enumerable: true
448
+ });
446
449
  }, 'webdriver removal');
447
450
 
448
451
  // Remove automation properties
@@ -861,6 +864,8 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
861
864
  if (typeof originalStack === 'string') {
862
865
  return originalStack
863
866
  .replace(/.*puppeteer.*\n?/gi, '')
867
+ .replace(/.*__puppeteer_evaluation_script__.*\n?/gi, '')
868
+ .replace(/.*evaluateOnNewDocument.*\n?/gi, '')
864
869
  .replace(/.*chrome-devtools.*\n?/gi, '')
865
870
  .replace(/.*webdriver.*\n?/gi, '')
866
871
  .replace(/.*automation.*\n?/gi, '')
@@ -1023,8 +1028,9 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
1023
1028
 
1024
1029
  // Null context — return mock to prevent crashes
1025
1030
  if (ctx === null) {
1026
- return new Proxy({}, {
1027
- get(_, prop) {
1031
+ const canvasEl = this; // capture the actual canvas element
1032
+ const mock = new Proxy({}, {
1033
+ get(target, prop) {
1028
1034
  if (prop === 'getShaderPrecisionFormat') return () => ({ rangeMin: 127, rangeMax: 127, precision: 23 });
1029
1035
  if (prop === 'getParameter') return (p) => webglSpoofParams[p] || 0;
1030
1036
  if (prop === 'getSupportedExtensions') return () => [];
@@ -1032,12 +1038,28 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
1032
1038
  if (name === 'WEBGL_debug_renderer_info') return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };
1033
1039
  return null;
1034
1040
  };
1035
- if (prop === 'canvas') return this;
1036
- if (prop === 'drawingBufferWidth') return 1920;
1037
- if (prop === 'drawingBufferHeight') return 1080;
1041
+ if (prop === 'getContextAttributes') return () => ({
1042
+ alpha: true, antialias: true, depth: true, failIfMajorPerformanceCaveat: false,
1043
+ desynchronized: false, premultipliedAlpha: true, preserveDrawingBuffer: false,
1044
+ powerPreference: 'default', stencil: false, xrCompatible: false
1045
+ });
1046
+ if (prop === 'isContextLost') return () => false;
1047
+ if (prop === 'canvas') return canvasEl;
1048
+ if (prop === 'drawingBufferWidth') return canvasEl.width || 1920;
1049
+ if (prop === 'drawingBufferHeight') return canvasEl.height || 1080;
1050
+ if (prop === 'drawingBufferColorSpace') return 'srgb';
1051
+ // Identity — let prototype chain handle constructor/toString/Symbol.toStringTag
1052
+ if (prop === 'constructor') return WebGLRenderingContext;
1053
+ if (prop === Symbol.toStringTag) return 'WebGLRenderingContext';
1054
+ // Common draw/state methods — return noop to prevent crashes
1038
1055
  return noop;
1039
1056
  }
1040
1057
  });
1058
+ // Make mock pass instanceof checks
1059
+ if (window.WebGLRenderingContext) {
1060
+ Object.setPrototypeOf(mock, WebGLRenderingContext.prototype);
1061
+ }
1062
+ return mock;
1041
1063
  }
1042
1064
 
1043
1065
  // Real context — wrap in Proxy to intercept getParameter/getExtension
@@ -1618,6 +1640,55 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
1618
1640
  });
1619
1641
  }, 'location URL masking');
1620
1642
 
1643
+ // Bulk-mask all spoofed prototype methods so toString() returns "[native code]"
1644
+ // Must run AFTER all overrides are applied
1645
+ safeExecute(() => {
1646
+ const protoMasks = [
1647
+ [WebGLRenderingContext.prototype, ['getParameter', 'getExtension', 'getSupportedExtensions']],
1648
+ [HTMLCanvasElement.prototype, ['getContext', 'toDataURL', 'toBlob']],
1649
+ [CanvasRenderingContext2D.prototype, ['getImageData', 'fillText', 'strokeText', 'measureText']],
1650
+ [EventTarget.prototype, ['addEventListener', 'removeEventListener']],
1651
+ [Date.prototype, ['getTimezoneOffset']],
1652
+ ];
1653
+ if (typeof WebGL2RenderingContext !== 'undefined') {
1654
+ protoMasks.push([WebGL2RenderingContext.prototype, ['getParameter', 'getExtension']]);
1655
+ }
1656
+ protoMasks.forEach(([proto, methods]) => {
1657
+ methods.forEach(name => {
1658
+ if (typeof proto[name] === 'function') maskAsNative(proto[name], name);
1659
+ });
1660
+ });
1661
+
1662
+ // Mask navigator/window method overrides
1663
+ if (typeof navigator.permissions?.query === 'function') maskAsNative(navigator.permissions.query, 'query');
1664
+ if (typeof navigator.getBattery === 'function') maskAsNative(navigator.getBattery, 'getBattery');
1665
+ if (typeof speechSynthesis?.getVoices === 'function') maskAsNative(speechSynthesis.getVoices, 'getVoices');
1666
+ if (typeof performance.now === 'function') maskAsNative(performance.now, 'now');
1667
+ if (typeof Notification?.requestPermission === 'function') maskAsNative(Notification.requestPermission, 'requestPermission');
1668
+ if (typeof window.RTCPeerConnection === 'function') maskAsNative(window.RTCPeerConnection, 'RTCPeerConnection');
1669
+ if (typeof window.Image === 'function') maskAsNative(window.Image, 'Image');
1670
+ if (typeof window.fetch === 'function') maskAsNative(window.fetch, 'fetch');
1671
+ if (typeof window.PointerEvent === 'function') maskAsNative(window.PointerEvent, 'PointerEvent');
1672
+
1673
+ // Mask property getters on navigator
1674
+ const navProps = ['userAgentData', 'connection', 'pdfViewerEnabled', 'webdriver',
1675
+ 'hardwareConcurrency', 'deviceMemory', 'platform', 'maxTouchPoints'];
1676
+ navProps.forEach(prop => {
1677
+ // Check both instance and prototype (webdriver lives on prototype)
1678
+ const desc = Object.getOwnPropertyDescriptor(navigator, prop)
1679
+ || Object.getOwnPropertyDescriptor(Navigator.prototype, prop);
1680
+ if (desc?.get) maskAsNative(desc.get, 'get ' + prop);
1681
+ });
1682
+
1683
+ // Mask window property getters
1684
+ ['screenX', 'screenY', 'outerWidth', 'outerHeight'].forEach(prop => {
1685
+ const desc = Object.getOwnPropertyDescriptor(window, prop);
1686
+ if (desc?.get) maskAsNative(desc.get, 'get ' + prop);
1687
+ });
1688
+
1689
+ if (debugEnabled) console.log('[fingerprint] toString protection applied to all spoofed functions');
1690
+ }, 'Function.prototype.toString bulk masking');
1691
+
1621
1692
  }, ua, forceDebug, selectedGpu);
1622
1693
  } catch (stealthErr) {
1623
1694
  if (stealthErr.message.includes('Session closed') ||
@@ -2013,9 +2084,6 @@ async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, current
2013
2084
  }
2014
2085
 
2015
2086
  // Legacy compatibility function - maintained for backwards compatibility
2016
- function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
2017
- return safeSpoofingExecution(spoofFunction, description, { debug: forceDebug });
2018
- }
2019
2087
 
2020
2088
 
2021
2089
  module.exports = {
@@ -2027,7 +2095,6 @@ module.exports = {
2027
2095
  applyAllFingerprintSpoofing,
2028
2096
  simulateHumanBehavior,
2029
2097
  safeDefineProperty,
2030
- safeExecuteSpoofing, // Legacy compatibility
2031
2098
  safeSpoofingExecution,
2032
2099
  DEFAULT_PLATFORM,
2033
2100
  DEFAULT_TIMEZONE
@@ -123,6 +123,20 @@ const ELEMENT_INTERACTION = {
123
123
  MISTAKE_RATE: 0.02 // Probability of typing mistakes (0.02 = 2% chance)
124
124
  };
125
125
 
126
+ // === CONTENT CLICK CONSTANTS ===
127
+ // For triggering document-level onclick handlers (e.g., Monetag onclick_static)
128
+ // These clicks target the page content area, not specific UI elements
129
+ // NOTE: No preDelay needed — mouse movements + scrolling already provide ~1s
130
+ // of activity before clicks fire, which is enough for async ad script registration
131
+ const CONTENT_CLICK = {
132
+ CLICK_COUNT: 2, // Two attempts (primary + backup if first suppressed)
133
+ INTER_CLICK_MIN: 300, // Minimum ms between clicks (above Monetag 250ms cooldown)
134
+ INTER_CLICK_MAX: 500, // Maximum ms between clicks
135
+ PRE_CLICK_DELAY: 300, // Small buffer for late-loading async ad scripts
136
+ VIEWPORT_INSET: 0.2, // Avoid outer 20% of viewport (menus, overlays)
137
+ MOUSE_APPROACH_STEPS: 3 // Minimal steps — just enough for non-instant movement
138
+ };
139
+
126
140
  // === INTENSITY SETTINGS ===
127
141
  // Pre-configured intensity levels - modify these to change overall behavior
128
142
  const INTENSITY_SETTINGS = {
@@ -606,6 +620,97 @@ async function interactWithElements(page, options = {}) {
606
620
  }
607
621
  }
608
622
 
623
+ /**
624
+ * Clicks random spots in the page content area to trigger document-level
625
+ * onclick handlers (Monetag onclick_static, similar popunder SDKs).
626
+ *
627
+ * WHY THIS EXISTS:
628
+ * Ad onclick SDKs attach a single listener on `document` (capture phase)
629
+ * that fires on ANY click with `isTrusted: true`. They don't care which
630
+ * element was clicked — just that a real input event reached the document.
631
+ * `interactWithElements()` hunts for <button>/<a> which may not exist or
632
+ * may be excluded by the SDK's own filter. This function simply clicks
633
+ * the content area of the page where the SDK will always accept the event.
634
+ *
635
+ * TIMING:
636
+ * - 300ms preDelay: small buffer after mouse/scroll activity (~1.2s) for
637
+ * any late-loading async ad scripts to finish registering listeners.
638
+ * - Spaces clicks 300-500ms apart (above Monetag's 250ms cooldown).
639
+ * - Total time: ~1.1s for 2 clicks (preDelay + move + pause + click + gap).
640
+ *
641
+ * TARGETING:
642
+ * - Clicks within the inner 60% of the viewport to avoid sticky headers,
643
+ * footers, sidebars, cookie banners, and overlay close buttons.
644
+ * - Each click gets a fresh random position with natural mouse approach.
645
+ *
646
+ * @param {import('puppeteer').Page} page
647
+ * @param {object} [options]
648
+ * @param {number} [options.clicks] Number of click attempts
649
+ * @param {number} [options.preDelay] Ms to wait before first click
650
+ * @param {number} [options.interClickMin] Min ms between clicks
651
+ * @param {number} [options.interClickMax] Max ms between clicks
652
+ * @param {boolean} [options.forceDebug] Log click coordinates
653
+ */
654
+ async function performContentClicks(page, options = {}) {
655
+ const {
656
+ clicks = CONTENT_CLICK.CLICK_COUNT,
657
+ preDelay = CONTENT_CLICK.PRE_CLICK_DELAY,
658
+ interClickMin = CONTENT_CLICK.INTER_CLICK_MIN,
659
+ interClickMax = CONTENT_CLICK.INTER_CLICK_MAX,
660
+ forceDebug = false
661
+ } = options;
662
+
663
+ try {
664
+ if (page.isClosed()) return;
665
+
666
+ const viewport = await getCachedViewport(page);
667
+ const inset = CONTENT_CLICK.VIEWPORT_INSET;
668
+ const minX = Math.floor(viewport.width * inset);
669
+ const maxX = Math.floor(viewport.width * (1 - inset));
670
+ const minY = Math.floor(viewport.height * inset);
671
+ const maxY = Math.floor(viewport.height * (1 - inset));
672
+
673
+ // Wait for ad scripts to register their listeners
674
+ await fastTimeout(preDelay);
675
+
676
+ let lastX = minX + Math.floor(Math.random() * (maxX - minX));
677
+ let lastY = minY + Math.floor(Math.random() * (maxY - minY));
678
+
679
+ for (let i = 0; i < clicks; i++) {
680
+ if (page.isClosed()) break;
681
+
682
+ // Random position in content zone
683
+ const targetX = minX + Math.floor(Math.random() * (maxX - minX));
684
+ const targetY = minY + Math.floor(Math.random() * (maxY - minY));
685
+
686
+ // Natural mouse approach (few steps, no need for elaborate curves)
687
+ await humanLikeMouseMove(page, lastX, lastY, targetX, targetY, {
688
+ steps: CONTENT_CLICK.MOUSE_APPROACH_STEPS,
689
+ curve: 0.03 + Math.random() * 0.04,
690
+ jitter: 1
691
+ });
692
+
693
+ // Brief human-like pause, then click
694
+ await fastTimeout(TIMING.CLICK_PAUSE_MIN + Math.random() * (TIMING.CLICK_PAUSE_MAX - TIMING.CLICK_PAUSE_MIN));
695
+ await page.mouse.click(targetX, targetY);
696
+
697
+ if (forceDebug) {
698
+ console.log(`[interaction] Content click ${i + 1}/${clicks} at (${targetX}, ${targetY})`);
699
+ }
700
+
701
+ lastX = targetX;
702
+ lastY = targetY;
703
+
704
+ // Inter-click gap (skip after last click)
705
+ if (i < clicks - 1) {
706
+ await fastTimeout(interClickMin + Math.random() * (interClickMax - interClickMin));
707
+ }
708
+ }
709
+ } catch (err) {
710
+ // Content clicks are supplementary — never break the scan
711
+ }
712
+ }
713
+
609
714
  /**
610
715
  * Simulates realistic typing behavior with human characteristics
611
716
  *
@@ -876,12 +981,22 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
876
981
  }
877
982
  }
878
983
 
879
- // Element interaction
984
+ // Click interaction — two strategies for maximum ad script coverage
985
+ // 1. Content area clicks: triggers document-level onclick handlers
986
+ // (Monetag, similar popunder SDKs that listen on document)
987
+ // 2. Element clicks: interacts with specific UI elements
988
+ // (ad scripts that attach to specific clickable elements)
880
989
  if (includeElementClicks) {
881
- await interactWithElements(page, {
882
- maxAttempts: 2,
883
- avoidDestructive: true
884
- });
990
+ if (checkTimeout()) return; // Emergency timeout check
991
+ // Primary: content area clicks for document-level onclick handlers
992
+ await performContentClicks(page, { forceDebug });
993
+ // Secondary: targeted element clicks (fast, 1 attempt only)
994
+ if (!checkTimeout()) {
995
+ await interactWithElements(page, {
996
+ maxAttempts: 1,
997
+ avoidDestructive: true
998
+ });
999
+ }
885
1000
  }
886
1001
 
887
1002
  // Periodic memory cleanup during interaction
@@ -1105,6 +1220,7 @@ module.exports = {
1105
1220
  humanLikeMouseMove,
1106
1221
  simulateScrolling,
1107
1222
  interactWithElements,
1223
+ performContentClicks,
1108
1224
  simulateTyping,
1109
1225
  generateRandomCoordinates
1110
1226
  };
package/lib/nettools.js CHANGED
@@ -11,6 +11,20 @@ const execPromise = util.promisify(exec);
11
11
  // Cycling index for whois server rotation
12
12
  let whoisServerCycleIndex = 0;
13
13
 
14
+ // Global dig result cache — shared across ALL handler instances and processUrl calls
15
+ // Key: `${domain}-${recordType}`, Value: { result, timestamp }
16
+ // DNS records don't change based on what terms you're searching for,
17
+ // so we cache the raw dig output and let each handler check its own terms against it
18
+ const globalDigResultCache = new Map();
19
+ const GLOBAL_DIG_CACHE_TTL = 300000; // 5 minutes
20
+ const GLOBAL_DIG_CACHE_MAX = 500;
21
+
22
+ // Global whois result cache — shared across ALL handler instances and processUrl calls
23
+ // Whois data is per root domain and doesn't change based on search terms
24
+ const globalWhoisResultCache = new Map();
25
+ const GLOBAL_WHOIS_CACHE_TTL = 900000; // 15 minutes (whois data changes less frequently)
26
+ const GLOBAL_WHOIS_CACHE_MAX = 500;
27
+
14
28
  /**
15
29
  * Strips ANSI color codes from a string for clean file logging
16
30
  * @param {string} text - Text that may contain ANSI codes
@@ -26,27 +40,28 @@ function stripAnsiColors(text) {
26
40
  * @returns {Object} Object with isAvailable boolean and version/error info
27
41
  */
28
42
  function validateWhoisAvailability() {
43
+ if (validateWhoisAvailability._cached) return validateWhoisAvailability._cached;
29
44
  try {
30
45
  const result = execSync('whois --version 2>&1', { encoding: 'utf8' });
31
- return {
46
+ validateWhoisAvailability._cached = {
32
47
  isAvailable: true,
33
48
  version: result.trim()
34
49
  };
35
50
  } catch (error) {
36
- // Some systems don't have --version, try just whois
37
51
  try {
38
52
  execSync('which whois', { encoding: 'utf8' });
39
- return {
53
+ validateWhoisAvailability._cached = {
40
54
  isAvailable: true,
41
55
  version: 'whois (version unknown)'
42
56
  };
43
57
  } catch (e) {
44
- return {
58
+ validateWhoisAvailability._cached = {
45
59
  isAvailable: false,
46
60
  error: 'whois command not found'
47
61
  };
48
62
  }
49
63
  }
64
+ return validateWhoisAvailability._cached;
50
65
  }
51
66
 
52
67
  /**
@@ -54,18 +69,20 @@ function validateWhoisAvailability() {
54
69
  * @returns {Object} Object with isAvailable boolean and version/error info
55
70
  */
56
71
  function validateDigAvailability() {
72
+ if (validateDigAvailability._cached) return validateDigAvailability._cached;
57
73
  try {
58
74
  const result = execSync('dig -v 2>&1', { encoding: 'utf8' });
59
- return {
75
+ validateDigAvailability._cached = {
60
76
  isAvailable: true,
61
77
  version: result.split('\n')[0].trim()
62
78
  };
63
79
  } catch (error) {
64
- return {
80
+ validateDigAvailability._cached = {
65
81
  isAvailable: false,
66
82
  error: 'dig command not found'
67
83
  };
68
84
  }
85
+ return validateDigAvailability._cached;
69
86
  }
70
87
 
71
88
  /**
@@ -635,8 +652,8 @@ async function digLookup(domain = '', recordType = 'A', timeout = 5000) {
635
652
  // Clean domain
636
653
  const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:\d+$/, '');
637
654
 
638
- // Get short output first
639
- const { stdout, stderr } = await execWithTimeout(`dig +short "${cleanDomain}" ${recordType}`, timeout);
655
+ // Single dig command — full output contains everything including the short answers
656
+ const { stdout: fullOutput, stderr } = await execWithTimeout(`dig "${cleanDomain}" ${recordType}`, timeout);
640
657
 
641
658
  if (stderr && stderr.trim()) {
642
659
  return {
@@ -647,13 +664,21 @@ async function digLookup(domain = '', recordType = 'A', timeout = 5000) {
647
664
  };
648
665
  }
649
666
 
650
- // Also get full dig output for detailed analysis
651
- const { stdout: fullOutput } = await execWithTimeout(`dig "${cleanDomain}" ${recordType}`, timeout);
667
+ // Extract short output from ANSWER SECTION of full dig output
668
+ const answerMatch = fullOutput.match(/;; ANSWER SECTION:\n([\s\S]*?)(?:\n;;|\n*$)/);
669
+ let shortOutput = '';
670
+ if (answerMatch) {
671
+ shortOutput = answerMatch[1]
672
+ .split('\n')
673
+ .map(line => line.split(/\s+/).pop())
674
+ .filter(Boolean)
675
+ .join('\n');
676
+ }
652
677
 
653
678
  return {
654
679
  success: true,
655
680
  output: fullOutput,
656
- shortOutput: stdout.trim(),
681
+ shortOutput,
657
682
  domain: cleanDomain,
658
683
  recordType
659
684
  };
@@ -766,7 +791,6 @@ function createNetToolsHandler(config) {
766
791
  whoisDelay = 4000,
767
792
  whoisServer,
768
793
  whoisServerMode = 'random',
769
- bufferedLogWrite = null,
770
794
  debugLogFile = null,
771
795
  digTerms,
772
796
  digOrTerms,
@@ -807,20 +831,10 @@ function createNetToolsHandler(config) {
807
831
  subdomain: digSubdomain
808
832
  });
809
833
 
810
- // Add whois resolution caching to avoid redundant whois lookups
811
- const whoisResultCache = new Map();
812
- const WHOIS_CACHE_TTL = 900000; // 15 minutes cache TTL (whois data changes less frequently)
813
- const MAX_CACHE_SIZE = 400; // Larger cache for whois due to longer TTL
814
- // Size Memory
815
- // 100 ~900KB
816
- // 200 1.8MB
817
- // 300 2.6MB
818
- // 400 3.4MB
819
- // 500 4.2MB
820
- // Add DNS resolution caching to avoid redundant dig lookups
821
- const digResultCache = new Map();
822
- const DIG_CACHE_TTL = 300000; // 5 minutes cache TTL
823
- const DIG_MAX_CACHE_SIZE = 400; // Smaller cache for dig due to shorter TTL
834
+ // Whois cache is global (globalWhoisResultCache) shared across all handler instances
835
+ // Whois data is per root domain and doesn't change based on search terms
836
+ // Dig cache is global (globalDigResultCache) shared across all handler instances
837
+ // DNS results are the same regardless of search terms
824
838
 
825
839
  return async function handleNetToolsCheck(domain, fullSubdomain) {
826
840
  // Use fullSubdomain parameter instead of originalDomain to maintain consistency
@@ -844,19 +858,17 @@ function createNetToolsHandler(config) {
844
858
 
845
859
  // Move the logToConsoleAndFile function declaration from later in the file to here:
846
860
  function logToConsoleAndFile(message) {
861
+ // Note: This function needs access to forceDebug, debugLogFile, and fs from the parent scope
862
+ // These are passed in via the config object to createNetToolsHandler
863
+ // forceDebug, debugLogFile, and fs are available in this closure
864
+
865
+ // Always log to console when in debug mode
847
866
  if (forceDebug) {
848
867
  console.log(formatLogMessage('debug', message));
849
868
  }
850
869
 
851
- if (debugLogFile && bufferedLogWrite) {
852
- try {
853
- const timestamp = new Date().toISOString();
854
- const cleanMessage = stripAnsiColors(message);
855
- bufferedLogWrite(debugLogFile, `${timestamp} [debug nettools] ${cleanMessage}\n`);
856
- } catch (logErr) {
857
- // Silently fail file logging to avoid disrupting whois operations
858
- }
859
- } else if (debugLogFile && fs) {
870
+ // Also log to file if debug file logging is enabled
871
+ if (debugLogFile && fs) {
860
872
  try {
861
873
  const timestamp = new Date().toISOString();
862
874
  const cleanMessage = stripAnsiColors(message);
@@ -982,9 +994,9 @@ function createNetToolsHandler(config) {
982
994
  const now = Date.now();
983
995
  let whoisResult = null;
984
996
 
985
- if (whoisResultCache.has(whoisCacheKey)) {
986
- const cachedEntry = whoisResultCache.get(whoisCacheKey);
987
- if (now - cachedEntry.timestamp < WHOIS_CACHE_TTL) {
997
+ if (globalWhoisResultCache.has(whoisCacheKey)) {
998
+ const cachedEntry = globalWhoisResultCache.get(whoisCacheKey);
999
+ if (now - cachedEntry.timestamp < GLOBAL_WHOIS_CACHE_TTL) {
988
1000
  if (forceDebug) {
989
1001
  const age = Math.round((now - cachedEntry.timestamp) / 1000);
990
1002
  const serverInfo = (selectedServer && selectedServer !== '') ? ` (server: ${selectedServer})` : ' (default server)';
@@ -998,7 +1010,7 @@ function createNetToolsHandler(config) {
998
1010
  });
999
1011
  } else {
1000
1012
  // Cache expired, remove it
1001
- whoisResultCache.delete(whoisCacheKey);
1013
+ globalWhoisResultCache.delete(whoisCacheKey);
1002
1014
  if (forceDebug) {
1003
1015
  logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cache expired for ${whoisRootDomain}, performing fresh lookup`);
1004
1016
  }
@@ -1030,7 +1042,7 @@ function createNetToolsHandler(config) {
1030
1042
  !whoisResult.error.toLowerCase().includes('connection') &&
1031
1043
  !whoisResult.error.toLowerCase().includes('network'))) {
1032
1044
 
1033
- whoisResultCache.set(whoisCacheKey, {
1045
+ globalWhoisResultCache.set(whoisCacheKey, {
1034
1046
  result: whoisResult,
1035
1047
  timestamp: now
1036
1048
  });
@@ -1183,17 +1195,17 @@ function createNetToolsHandler(config) {
1183
1195
  }
1184
1196
 
1185
1197
  // Periodic whois cache cleanup to prevent memory leaks
1186
- if (whoisResultCache.size > MAX_CACHE_SIZE) {
1198
+ if (globalWhoisResultCache.size > GLOBAL_WHOIS_CACHE_MAX) {
1187
1199
  const now = Date.now();
1188
1200
  let cleanedCount = 0;
1189
- for (const [key, entry] of whoisResultCache.entries()) {
1190
- if (now - entry.timestamp > WHOIS_CACHE_TTL) {
1191
- whoisResultCache.delete(key);
1201
+ for (const [key, entry] of globalWhoisResultCache.entries()) {
1202
+ if (now - entry.timestamp > GLOBAL_WHOIS_CACHE_TTL) {
1203
+ globalWhoisResultCache.delete(key);
1192
1204
  cleanedCount++;
1193
1205
  }
1194
1206
  }
1195
1207
  if (forceDebug && cleanedCount > 0) {
1196
- logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${whoisResultCache.size}`);
1208
+ logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${globalWhoisResultCache.size}`);
1197
1209
  }
1198
1210
  }
1199
1211
  }
@@ -1216,16 +1228,16 @@ function createNetToolsHandler(config) {
1216
1228
  const now = Date.now();
1217
1229
  let digResult = null;
1218
1230
 
1219
- if (digResultCache.has(digCacheKey)) {
1220
- const cachedEntry = digResultCache.get(digCacheKey);
1221
- if (now - cachedEntry.timestamp < DIG_CACHE_TTL) {
1231
+ if (globalDigResultCache.has(digCacheKey)) {
1232
+ const cachedEntry = globalDigResultCache.get(digCacheKey);
1233
+ if (now - cachedEntry.timestamp < GLOBAL_DIG_CACHE_TTL) {
1222
1234
  if (forceDebug) {
1223
1235
  logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Using cached result for ${digDomain} (${digRecordType}) [age: ${Math.round((now - cachedEntry.timestamp) / 1000)}s]`);
1224
1236
  }
1225
1237
  digResult = cachedEntry.result;
1226
1238
  } else {
1227
1239
  // Cache expired, remove it
1228
- digResultCache.delete(digCacheKey);
1240
+ globalDigResultCache.delete(digCacheKey);
1229
1241
  }
1230
1242
  }
1231
1243
 
@@ -1233,7 +1245,7 @@ function createNetToolsHandler(config) {
1233
1245
  digResult = await digLookup(digDomain, digRecordType, 5000); // 5 second timeout for dig
1234
1246
 
1235
1247
  // Cache the result for future use
1236
- digResultCache.set(digCacheKey, {
1248
+ globalDigResultCache.set(digCacheKey, {
1237
1249
  result: digResult,
1238
1250
  timestamp: now
1239
1251
  });
@@ -1303,17 +1315,17 @@ function createNetToolsHandler(config) {
1303
1315
  }
1304
1316
 
1305
1317
  // Periodic dig cache cleanup to prevent memory leaks
1306
- if (digResultCache.size > DIG_MAX_CACHE_SIZE) {
1318
+ if (globalDigResultCache.size > GLOBAL_DIG_CACHE_MAX) {
1307
1319
  const now = Date.now();
1308
1320
  let cleanedCount = 0;
1309
- for (const [key, entry] of digResultCache.entries()) {
1310
- if (now - entry.timestamp > DIG_CACHE_TTL) {
1311
- digResultCache.delete(key);
1321
+ for (const [key, entry] of globalDigResultCache.entries()) {
1322
+ if (now - entry.timestamp > GLOBAL_DIG_CACHE_TTL) {
1323
+ globalDigResultCache.delete(key);
1312
1324
  cleanedCount++;
1313
1325
  }
1314
1326
  }
1315
1327
  if (forceDebug && cleanedCount > 0) {
1316
- logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${digResultCache.size}`);
1328
+ logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${globalDigResultCache.size}`);
1317
1329
  }
1318
1330
  }
1319
1331
  }
@@ -1387,12 +1399,7 @@ function createNetToolsHandler(config) {
1387
1399
 
1388
1400
  // Add whois server info to log if custom server was used
1389
1401
  const serverInfo = whoisServer ? ` (whois-server: ${selectWhoisServer(whoisServer)})` : '';
1390
- const logLine = `${timestamp} [match][${simplifiedUrl}] ${domain} (${matchType.join(' + ')})${serverInfo}\n`;
1391
- if (bufferedLogWrite) {
1392
- bufferedLogWrite(matchedUrlsLogFile, logLine);
1393
- } else {
1394
- fs.appendFileSync(matchedUrlsLogFile, logLine);
1395
- }
1402
+ fs.appendFileSync(matchedUrlsLogFile, `${timestamp} [match][${simplifiedUrl}] ${domain} (${matchType.join(' + ')})${serverInfo}\n`);
1396
1403
  }
1397
1404
  }
1398
1405
 
package/nwss.js CHANGED
@@ -39,7 +39,7 @@ const { processResults } = require('./lib/post-processing');
39
39
  // Colorize various text when used
40
40
  const { colorize, colors, messageColors, tags, formatLogMessage } = require('./lib/colorize');
41
41
  // Enhanced mouse interaction and page simulation
42
- const { performPageInteraction, createInteractionConfig } = require('./lib/interaction');
42
+ const { performPageInteraction, createInteractionConfig, performContentClicks, humanLikeMouseMove } = require('./lib/interaction');
43
43
  // Domain detection cache for performance optimization
44
44
  const { createGlobalHelpers, getTotalDomainsSkipped, getDetectedDomainsCount } = require('./lib/domain-cache');
45
45
  const { createSmartCache } = require('./lib/smart-cache'); // Smart cache system
@@ -1447,7 +1447,6 @@ function setupFrameHandling(page, forceDebug) {
1447
1447
  '--disable-features=SafeBrowsing',
1448
1448
  '--disable-dev-shm-usage',
1449
1449
  '--disable-sync',
1450
- '--use-gl=swiftshader', // Software WebGL — prevents ad script crashes in headless
1451
1450
  '--mute-audio',
1452
1451
  '--disable-translate',
1453
1452
  '--window-size=1920,1080',
@@ -2730,9 +2729,7 @@ function setupFrameHandling(page, forceDebug) {
2730
2729
  whoisDelay: siteConfig.whois_delay !== undefined ? siteConfig.whois_delay : whois_delay,
2731
2730
  whoisServer,
2732
2731
  whoisServerMode: siteConfig.whois_server_mode || whois_server_mode,
2733
- bufferedLogWrite,
2734
2732
  debugLogFile,
2735
- fs,
2736
2733
  digTerms,
2737
2734
  digOrTerms,
2738
2735
  digRecordType,
@@ -2838,9 +2835,7 @@ function setupFrameHandling(page, forceDebug) {
2838
2835
  whoisDelay: siteConfig.whois_delay !== undefined ? siteConfig.whois_delay : whois_delay, // Site-specific or global fallback
2839
2836
  whoisServer, // Pass whois server configuration
2840
2837
  whoisServerMode: siteConfig.whois_server_mode || whois_server_mode,
2841
- bufferedLogWrite,
2842
- debugLogFile, // Pass debug log file for whois error logging
2843
- fs, // Pass fs module for file operations
2838
+ debugLogFile,
2844
2839
  digTerms,
2845
2840
  digOrTerms,
2846
2841
  digRecordType,
@@ -3664,6 +3659,32 @@ function setupFrameHandling(page, forceDebug) {
3664
3659
  }
3665
3660
  }
3666
3661
 
3662
+ // Post-reload interaction: trigger onclick ad scripts (Monetag etc.)
3663
+ // Each reload gives a fresh session with a new random ad domain —
3664
+ // without clicks the SDK never fires and we miss those domains.
3665
+ if (interactEnabled && !page.isClosed()) {
3666
+ try {
3667
+ // Brief wait for ad scripts to re-register after reload
3668
+ await fastTimeout(800);
3669
+ // Quick mouse moves to build movement score (Monetag tracks this)
3670
+ const vp = page.viewport() || { width: 1920, height: 1080 };
3671
+ const startX = 200 + Math.floor(Math.random() * (vp.width - 400));
3672
+ const startY = 200 + Math.floor(Math.random() * (vp.height - 400));
3673
+ await page.mouse.move(startX, startY);
3674
+ for (let m = 0; m < 2; m++) {
3675
+ const endX = 200 + Math.floor(Math.random() * (vp.width - 400));
3676
+ const endY = 200 + Math.floor(Math.random() * (vp.height - 400));
3677
+ await humanLikeMouseMove(page, startX, startY, endX, endY, { steps: 3, curve: 0.04, jitter: 1 });
3678
+ }
3679
+ // Content clicks to trigger document-level onclick handlers
3680
+ await performContentClicks(page, { clicks: 2, preDelay: 200, forceDebug });
3681
+ if (forceDebug) console.log(formatLogMessage('debug', `Post-reload interaction completed for reload #${i}`));
3682
+ } catch (postReloadInteractErr) {
3683
+ // Non-critical — continue with remaining reloads
3684
+ if (forceDebug) console.log(formatLogMessage('debug', `Post-reload interaction failed: ${postReloadInteractErr.message}`));
3685
+ }
3686
+ }
3687
+
3667
3688
  // Only add delay if we're continuing with more reloads
3668
3689
  if (i < totalReloads) {
3669
3690
  // Reduce delay for problematic sites
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "2.0.53",
3
+ "version": "2.0.55",
4
4
  "description": "A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.",
5
5
  "main": "nwss.js",
6
6
  "scripts": {