@fanboynz/network-scanner 2.0.54 → 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, '')
@@ -1635,6 +1640,55 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
1635
1640
  });
1636
1641
  }, 'location URL masking');
1637
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
+
1638
1692
  }, ua, forceDebug, selectedGpu);
1639
1693
  } catch (stealthErr) {
1640
1694
  if (stealthErr.message.includes('Session closed') ||
@@ -2030,9 +2084,6 @@ async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, current
2030
2084
  }
2031
2085
 
2032
2086
  // Legacy compatibility function - maintained for backwards compatibility
2033
- function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
2034
- return safeSpoofingExecution(spoofFunction, description, { debug: forceDebug });
2035
- }
2036
2087
 
2037
2088
 
2038
2089
  module.exports = {
@@ -2044,7 +2095,6 @@ module.exports = {
2044
2095
  applyAllFingerprintSpoofing,
2045
2096
  simulateHumanBehavior,
2046
2097
  safeDefineProperty,
2047
- safeExecuteSpoofing, // Legacy compatibility
2048
2098
  safeSpoofingExecution,
2049
2099
  DEFAULT_PLATFORM,
2050
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/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
- '--disable-gpu', // WebGL null-context handled by fingerprint.js Proxy mock
1451
1450
  '--mute-audio',
1452
1451
  '--disable-translate',
1453
1452
  '--window-size=1920,1080',
@@ -3660,6 +3659,32 @@ function setupFrameHandling(page, forceDebug) {
3660
3659
  }
3661
3660
  }
3662
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
+
3663
3688
  // Only add delay if we're continuing with more reloads
3664
3689
  if (i < totalReloads) {
3665
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.54",
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": {