@fanboynz/network-scanner 2.0.28 → 2.0.30

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/.clauderc ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "description": "Network scanner that monitors website requests and generates blocking rules. Uses Puppeteer to load sites, intercepts network traffic, matches patterns, and outputs rules in various formats (adblock, dnsmasq, hosts file, etc.).",
3
+
4
+ "conventions": [
5
+ "Store modular functionality in ./lib/ directory with focused, single-purpose modules",
6
+ "Use messageColors and formatLogMessage from ./lib/colorize for consistent console output",
7
+ "Implement timeout protection for all Puppeteer operations using Promise.race patterns",
8
+ "Handle browser lifecycle with comprehensive cleanup in try-finally blocks",
9
+ "Validate all external tool availability before use (grep, curl, whois, dig)",
10
+ "Use forceDebug flag for detailed logging, silentMode for minimal output"
11
+ ],
12
+
13
+ "files": {
14
+ "important": [
15
+ "nwss.js",
16
+ "config.json",
17
+ "lib/*.js",
18
+ "*.md",
19
+ "nwss.1"
20
+ ],
21
+ "ignore": [
22
+ "node_modules/**",
23
+ "logs/**",
24
+ "sources/**",
25
+ ".cache/**",
26
+ "*.log",
27
+ "*.gz"
28
+ ]
29
+ }
30
+ }
@@ -59,7 +59,7 @@ const PRECOMPILED_MOCKS = Object.freeze({
59
59
  postMessage: () => {},
60
60
  disconnect: () => {}
61
61
  }),
62
- getManifest: () => Object.freeze({ name: "Chrome", version: "141.0.0.0" }),
62
+ getManifest: () => Object.freeze({ name: "Chrome", version: "142.0.0.0" }),
63
63
  getURL: (path) => `chrome-extension://invalid/${path}`,
64
64
  id: undefined
65
65
  }),
@@ -89,22 +89,15 @@ const BUILT_IN_PROPERTIES = new Set([
89
89
  ]);
90
90
 
91
91
  // User agent collections with latest versions
92
- const USER_AGENT_COLLECTIONS = {
93
- chrome: [
94
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
95
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
96
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
97
- ],
98
- firefox: [
99
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0",
100
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0",
101
- "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0"
102
- ],
103
- safari: [
104
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15",
105
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"
106
- ]
107
- };
92
+ const USER_AGENT_COLLECTIONS = Object.freeze(new Map([
93
+ ['chrome', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
94
+ ['chrome_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
95
+ ['chrome_linux', "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
96
+ ['firefox', "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0"],
97
+ ['firefox_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0"],
98
+ ['firefox_linux', "Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0"],
99
+ ['safari', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15"]
100
+ ]));
108
101
 
109
102
  // Timezone configuration with offsets
110
103
  const TIMEZONE_CONFIG = {
@@ -366,8 +359,7 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
366
359
  // Validate page state before injection
367
360
  if (!(await validatePageForInjection(page, currentUrl, forceDebug))) return;
368
361
 
369
- const selectedUserAgents = USER_AGENT_COLLECTIONS[siteConfig.userAgent.toLowerCase()];
370
- const ua = selectedUserAgents ? selectedUserAgents[Math.floor(Math.random() * selectedUserAgents.length)] : null;
362
+ const ua = USER_AGENT_COLLECTIONS.get(siteConfig.userAgent.toLowerCase());
371
363
 
372
364
  if (ua) {
373
365
  await page.setUserAgent(ua);
@@ -512,7 +504,10 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
512
504
  'phantomjs', '_Selenium_IDE_Recorder', 'callSelenium', '_selenium',
513
505
  '__phantomas', '__selenium_evaluate', '__driver_unwrapped',
514
506
  'webdriver-evaluate', '__webdriverFunc', 'driver-evaluate', '__driver-evaluate', '__selenium-evaluate',
515
- 'spawn', 'emit', 'Buffer', 'domAutomation', 'domAutomationController'
507
+ 'spawn', 'emit', 'Buffer', 'domAutomation', 'domAutomationController',
508
+ 'cdc_adoQpoasnfa76pfcZLmcfl_JSON', 'cdc_adoQpoasnfa76pfcZLmcfl_Object',
509
+ 'cdc_adoQpoasnfa76pfcZLmcfl_Proxy', 'cdc_adoQpoasnfa76pfcZLmcfl_Reflect',
510
+ '$cdc_asdjflasutopfhvcZLmcfl_', '$chrome_asyncScriptInfo', '__$webdriverAsyncExecutor'
516
511
  ];
517
512
 
518
513
  automationProps.forEach(prop => {
@@ -528,16 +523,45 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
528
523
  // Simulate Chrome runtime
529
524
  //
530
525
  safeExecute(() => {
531
- if (!window.chrome?.runtime) {
526
+ if (!window.chrome) {
532
527
  window.chrome = {
533
528
  runtime: {
534
529
  onConnect: { addListener: () => {}, removeListener: () => {} },
535
530
  onMessage: { addListener: () => {}, removeListener: () => {} },
536
531
  sendMessage: () => {},
537
- connect: () => ({ onMessage: { addListener: () => {}, removeListener: () => {} }, postMessage: () => {}, disconnect: () => {} }),
538
- getManifest: () => ({ name: "Chrome", version: "141.0.0.0" }),
532
+ connect: () => ({
533
+ onMessage: { addListener: () => {}, removeListener: () => {} },
534
+ postMessage: () => {},
535
+ disconnect: () => {}
536
+ }),
537
+ getManifest: () => ({
538
+ name: "Chrome",
539
+ version: "142.0.0.0",
540
+ manifest_version: 3,
541
+ description: "Chrome Browser"
542
+ }),
539
543
  getURL: (path) => `chrome-extension://invalid/${path}`,
540
- id: undefined
544
+ id: undefined,
545
+ getPlatformInfo: (callback) => callback({
546
+ os: navigator.platform.includes('Win') ? 'win' :
547
+ navigator.platform.includes('Mac') ? 'mac' : 'linux',
548
+ arch: 'x86-64',
549
+ nacl_arch: 'x86-64'
550
+ })
551
+ },
552
+ storage: {
553
+ local: {
554
+ get: (keys, callback) => callback && callback({}),
555
+ set: (items, callback) => callback && callback(),
556
+ remove: (keys, callback) => callback && callback(),
557
+ clear: (callback) => callback && callback()
558
+ },
559
+ sync: {
560
+ get: (keys, callback) => callback && callback({}),
561
+ set: (items, callback) => callback && callback(),
562
+ remove: (keys, callback) => callback && callback(),
563
+ clear: (callback) => callback && callback()
564
+ }
541
565
  },
542
566
  loadTimes: () => {
543
567
  const now = performance.now();
@@ -555,9 +579,56 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
555
579
  startE: Date.now() - Math.random() * 2000
556
580
  })
557
581
  };
582
+
583
+ // Make chrome object non-enumerable to match real Chrome
584
+ Object.defineProperty(window, 'chrome', {
585
+ value: window.chrome,
586
+ writable: false,
587
+ enumerable: false,
588
+ configurable: true
589
+ });
590
+
591
+ // Add Chrome-specific globals that Cloudflare might check
592
+ if (!window.external) {
593
+ window.external = {
594
+ AddSearchProvider: () => {},
595
+ IsSearchProviderInstalled: () => 0
596
+ };
597
+ }
598
+
599
+ // Ensure chrome.runtime appears as a native object
600
+ Object.defineProperty(window.chrome.runtime, 'toString', {
601
+ value: () => '[object Object]'
602
+ });
558
603
  }
559
604
  }, 'Chrome runtime simulation');
560
605
 
606
+ // Add realistic Chrome browser behavior
607
+ //
608
+ safeExecute(() => {
609
+ // Remove Puppeteer-specific properties not covered in main automation cleanup
610
+ delete window.__puppeteer_evaluation_script__;
611
+ delete window.__runtime;
612
+ delete window._asyncToGenerator;
613
+ delete window.__puppeteer;
614
+ delete window.__cdp;
615
+ delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
616
+
617
+ // Simulate Chrome's performance observer
618
+ if (window.PerformanceObserver) {
619
+ try {
620
+ const observer = new PerformanceObserver(() => {});
621
+ observer.observe({entryTypes: ['navigation']});
622
+ } catch(e) {
623
+ // Silently ignore if PerformanceObserver fails
624
+ }
625
+ }
626
+
627
+ // Ensure external object exists (don't overwrite chrome object)
628
+ window.external = window.external || {};
629
+
630
+ }, 'realistic Chrome behavior');
631
+
561
632
  // Spoof plugins based on user agent
562
633
  //
563
634
  safeExecute(() => {
@@ -874,29 +945,33 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
874
945
  if (navigator.permissions?.query) {
875
946
  const originalQuery = navigator.permissions.query;
876
947
  navigator.permissions.query = function(descriptor) {
877
- // More realistic permission states based on permission type
948
+
878
949
  const permissionName = descriptor.name || descriptor;
879
- let state = 'prompt'; // Default state
950
+ // Realistic Chrome permission defaults
951
+ const chromeDefaults = {
952
+ 'notifications': 'default',
953
+ 'geolocation': 'prompt',
954
+ 'camera': 'prompt',
955
+ 'microphone': 'prompt',
956
+ 'persistent-storage': 'granted',
957
+ 'background-sync': 'granted',
958
+ 'midi': 'prompt',
959
+ 'push': 'prompt',
960
+ 'accelerometer': 'granted',
961
+ 'gyroscope': 'granted',
962
+ 'magnetometer': 'granted'
963
+ };
880
964
 
881
- // Common permissions that are usually granted
882
- if (['notifications', 'persistent-storage'].includes(permissionName)) {
883
- state = Math.random() > 0.3 ? 'granted' : 'prompt';
884
- }
885
- // Privacy-sensitive permissions that are usually denied/prompt
886
- else if (['camera', 'microphone', 'geolocation'].includes(permissionName)) {
887
- state = Math.random() > 0.8 ? 'granted' : 'prompt';
888
- }
889
- // Other permissions random
890
- else {
891
- state = Math.random() > 0.5 ? 'granted' : 'prompt';
892
- }
965
+ const state = chromeDefaults[permissionName] || 'prompt';
893
966
 
894
967
  return Promise.resolve({
895
- state: state,
968
+ state,
896
969
  onchange: null
897
970
  });
898
- };
971
+ }
972
+
899
973
  }
974
+
900
975
  // Block permission prompts from actually appearing
901
976
  if (window.Notification && Notification.requestPermission) {
902
977
  Notification.requestPermission = () => Promise.resolve('default');
@@ -995,12 +1070,28 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
995
1070
  // Font fingerprinting protection
996
1071
  //
997
1072
  safeExecute(() => {
998
- // Standardize available fonts to common system fonts
999
- const standardFonts = [
1000
- 'Arial', 'Helvetica', 'Times New Roman', 'Times', 'Courier New', 'Courier',
1001
- 'Verdana', 'Georgia', 'Palatino', 'Garamond', 'Bookman', 'Comic Sans MS',
1002
- 'Trebuchet MS', 'Arial Black', 'Impact', 'Tahoma', 'Lucida Console'
1003
- ];
1073
+ // OS-specific font profiles for better realism
1074
+ const getOSFonts = (userAgent) => {
1075
+ if (userAgent.includes('Windows') || userAgent.includes('Win')) {
1076
+ return ['Arial', 'Times New Roman', 'Courier New', 'Verdana', 'Tahoma', 'Trebuchet MS', 'Georgia', 'Impact', 'Comic Sans MS', 'Segoe UI'];
1077
+ } else if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS X')) {
1078
+ return ['Arial', 'Times New Roman', 'Courier New', 'Helvetica', 'Times', 'Courier', 'Verdana', 'Georgia', 'Palatino', 'San Francisco'];
1079
+ } else {
1080
+ return ['Arial', 'Times New Roman', 'Courier New', 'Liberation Sans', 'Liberation Serif', 'DejaVu Sans', 'Ubuntu'];
1081
+ }
1082
+ };
1083
+ const standardFonts = getOSFonts(navigator.userAgent);
1084
+
1085
+ // CRITICAL: Block font enumeration
1086
+ if (document.fonts) {
1087
+ Object.defineProperty(document.fonts, 'values', {
1088
+ value: () => standardFonts.map(font => ({ family: font, style: 'normal', weight: '400' }))[Symbol.iterator]()
1089
+ });
1090
+ Object.defineProperty(document.fonts, 'forEach', {
1091
+ value: (callback) => standardFonts.forEach((font, i) => callback({ family: font, style: 'normal', weight: '400' }, i))
1092
+ });
1093
+ Object.defineProperty(document.fonts, 'size', { get: () => standardFonts.length });
1094
+ }
1004
1095
 
1005
1096
  // Intercept font availability detection
1006
1097
  if (document.fonts && document.fonts.check) {
@@ -1637,4 +1728,4 @@ module.exports = {
1637
1728
  applyTimezoneSpoofing,
1638
1729
  DEFAULT_PLATFORM,
1639
1730
  DEFAULT_TIMEZONE
1640
- };
1731
+ };
package/lib/nettools.js CHANGED
@@ -779,6 +779,8 @@ function createNetToolsHandler(config) {
779
779
  isDomainAlreadyDetected,
780
780
  getRootDomain,
781
781
  siteConfig,
782
+ processedWhoisDomains = new Set(), // Accept global sets, fallback to new for backward compatibility
783
+ processedDigDomains = new Set(),
782
784
  dumpUrls,
783
785
  matchedUrlsLogFile,
784
786
  forceDebug,
@@ -790,9 +792,22 @@ function createNetToolsHandler(config) {
790
792
  const hasDig = digTerms && Array.isArray(digTerms) && digTerms.length > 0;
791
793
  const hasDigOr = digOrTerms && Array.isArray(digOrTerms) && digOrTerms.length > 0;
792
794
 
793
- // Add separate deduplication caches for different lookup types
794
- const processedWhoisDomains = new Set();
795
- const processedDigDomains = new Set();
795
+ // Create config-aware cache keys for deduplication
796
+ // Whois: Only include search terms + server (domain registry data is consistent across subdomains)
797
+ const whoisConfigKey = JSON.stringify({
798
+ terms: whoisTerms || [],
799
+ orTerms: whoisOrTerms || [],
800
+ server: whoisServer || 'default',
801
+ serverMode: whoisServerMode || 'random'
802
+ });
803
+ // Dig: Include all config (DNS records can vary by specific subdomain)
804
+ const digConfigKey = JSON.stringify({
805
+ terms: digTerms || [],
806
+ orTerms: digOrTerms || [],
807
+ recordType: digRecordType,
808
+ subdomain: digSubdomain
809
+ });
810
+
796
811
  // Add whois resolution caching to avoid redundant whois lookups
797
812
  const whoisResultCache = new Map();
798
813
  const WHOIS_CACHE_TTL = 900000; // 15 minutes cache TTL (whois data changes less frequently)
@@ -853,15 +868,24 @@ function createNetToolsHandler(config) {
853
868
 
854
869
  // Determine which domain will be used for dig lookup
855
870
  const digDomain = digSubdomain && originalDomain ? originalDomain : domain;
871
+
872
+ // For whois: use root domain only (whois data is consistent for entire domain)
873
+ const whoisRootDomain = getRootDomain ? getRootDomain(`http://${domain}`) : domain;
856
874
 
857
- // Check if we need to perform any lookups
858
- const needsWhoisLookup = (hasWhois || hasWhoisOr) && !processedWhoisDomains.has(domain);
859
- const needsDigLookup = (hasDig || hasDigOr) && !processedDigDomains.has(digDomain);
875
+ // Check if we need to perform any lookups with appropriate deduplication
876
+ // Whois: root domain + config (whois data same for sub.example.com and example.com)
877
+ const whoisDedupeKey = `${whoisRootDomain}:${whoisConfigKey}`;
878
+ // Dig: specific subdomain + config (DNS records can differ between subdomains)
879
+ const digDedupeKey = `${digDomain}:${digConfigKey}`;
880
+ const needsWhoisLookup = (hasWhois || hasWhoisOr) && !processedWhoisDomains.has(whoisDedupeKey);
881
+ const needsDigLookup = (hasDig || hasDigOr) && !processedDigDomains.has(digDedupeKey);
860
882
 
861
883
  // Skip if we don't need to perform any lookups
862
884
  if (!needsWhoisLookup && !needsDigLookup) {
863
885
  if (forceDebug) {
864
- logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Skipping duplicate lookups for ${domain} (whois: ${!needsWhoisLookup}, dig: ${!needsDigLookup})`);
886
+ const whoisSkipped = (hasWhois || hasWhoisOr) ? `cached(${whoisRootDomain})` : 'n/a';
887
+ const digSkipped = (hasDig || hasDigOr) ? `cached(${digDomain})` : 'n/a';
888
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Skipping duplicate lookups for ${domain} (whois: ${whoisSkipped}, dig: ${digSkipped})`);
865
889
  }
866
890
  return;
867
891
  }
@@ -869,7 +893,7 @@ function createNetToolsHandler(config) {
869
893
 
870
894
  if (forceDebug) {
871
895
  const totalProcessed = processedWhoisDomains.size + processedDigDomains.size;
872
- logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Processing domain: ${domain} (whois: ${needsWhoisLookup}, dig: ${needsDigLookup}) (${totalProcessed} total processed)`);
896
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Processing domain: ${domain} (whois: ${needsWhoisLookup ? whoisRootDomain : 'skip'}, dig: ${needsDigLookup ? digDomain : 'skip'}) (${totalProcessed} total processed)`);
873
897
  }
874
898
 
875
899
  // Log site-specific whois delay if different from default
@@ -955,12 +979,12 @@ function createNetToolsHandler(config) {
955
979
 
956
980
  // Perform whois lookup if either whois or whois-or is configured
957
981
  if (needsWhoisLookup) {
958
- // Mark whois domain as being processed
959
- processedWhoisDomains.add(domain);
982
+ // Mark whois root domain+config as being processed
983
+ processedWhoisDomains.add(whoisDedupeKey);
960
984
 
961
985
  // Check whois cache first - cache key includes server for accuracy
962
986
  const selectedServer = selectWhoisServer(whoisServer, whoisServerMode);
963
- const whoisCacheKey = `${domain}-${(selectedServer && selectedServer !== '') ? selectedServer : 'default'}`;
987
+ const whoisCacheKey = `${whoisRootDomain}-${(selectedServer && selectedServer !== '') ? selectedServer : 'default'}`;
964
988
  const now = Date.now();
965
989
  let whoisResult = null;
966
990
 
@@ -970,7 +994,7 @@ function createNetToolsHandler(config) {
970
994
  if (forceDebug) {
971
995
  const age = Math.round((now - cachedEntry.timestamp) / 1000);
972
996
  const serverInfo = (selectedServer && selectedServer !== '') ? ` (server: ${selectedServer})` : ' (default server)';
973
- logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Using cached result for ${domain}${serverInfo} [age: ${age}s]`);
997
+ logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Using cached result for ${whoisRootDomain}${serverInfo} [age: ${age}s]`);
974
998
  }
975
999
  // V8 Optimized: Object.assign is faster than spread for object merging
976
1000
  whoisResult = Object.assign({}, cachedEntry.result, {
@@ -982,7 +1006,7 @@ function createNetToolsHandler(config) {
982
1006
  // Cache expired, remove it
983
1007
  whoisResultCache.delete(whoisCacheKey);
984
1008
  if (forceDebug) {
985
- logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cache expired for ${domain}, performing fresh lookup`);
1009
+ logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cache expired for ${whoisRootDomain}, performing fresh lookup`);
986
1010
  }
987
1011
  }
988
1012
  }
@@ -991,7 +1015,7 @@ function createNetToolsHandler(config) {
991
1015
  if (!whoisResult) {
992
1016
  if (forceDebug) {
993
1017
  const serverInfo = (selectedServer && selectedServer !== '') ? ` using server ${selectedServer}` : ' using default server';
994
- logToConsoleAndFile(`${messageColors.highlight('[whois]')} Performing fresh whois lookup for ${domain}${serverInfo}`);
1018
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Performing fresh whois lookup for ${whoisRootDomain}${serverInfo}`);
995
1019
  }
996
1020
 
997
1021
  // Configure retry options based on site config or use defaults
@@ -1004,7 +1028,7 @@ function createNetToolsHandler(config) {
1004
1028
  };
1005
1029
 
1006
1030
  try {
1007
- whoisResult = await whoisLookupWithRetry(domain, 8000, whoisServer, forceDebug, retryOptions, whoisDelay, logToConsoleAndFile);
1031
+ whoisResult = await whoisLookupWithRetry(whoisRootDomain, 8000, whoisServer, forceDebug, retryOptions, whoisDelay, logToConsoleAndFile);
1008
1032
 
1009
1033
  // Cache successful results (and certain types of failures)
1010
1034
  if (whoisResult.success ||
@@ -1020,13 +1044,13 @@ function createNetToolsHandler(config) {
1020
1044
  if (forceDebug) {
1021
1045
  const cacheType = whoisResult.success ? 'successful' : 'failed';
1022
1046
  const serverInfo = selectedServer ? ` (server: ${selectedServer})` : ' (default server)';
1023
- logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cached ${cacheType} result for ${domain}${serverInfo}`);
1047
+ logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cached ${cacheType} result for ${whoisRootDomain}${serverInfo}`);
1024
1048
  }
1025
1049
  }
1026
1050
  } catch (whoisError) {
1027
1051
  // Handle exceptions from whois lookup
1028
1052
  if (forceDebug) {
1029
- logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception during lookup for ${domain}: ${whoisError.message}`);
1053
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception during lookup for ${whoisRootDomain}: ${whoisError.message}`);
1030
1054
  logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception type: ${whoisError.constructor.name}`);
1031
1055
  if (whoisError.stack) {
1032
1056
  logToConsoleAndFile(`${messageColors.highlight('[whois]')} Stack trace: ${whoisError.stack.split('\n').slice(0, 3).join(' -> ')}`);
@@ -1035,7 +1059,7 @@ function createNetToolsHandler(config) {
1035
1059
 
1036
1060
  // Log whois exceptions in dry run mode
1037
1061
  if (dryRunCallback && forceDebug) {
1038
- logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Exception: ${whoisError.message}`);
1062
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Exception for ${whoisRootDomain}: ${whoisError.message}`);
1039
1063
  }
1040
1064
  // Continue with dig if configured
1041
1065
  whoisResult = null; // Ensure we don't process a null result
@@ -1086,7 +1110,7 @@ function createNetToolsHandler(config) {
1086
1110
  const retryInfo = whoisResult.retryInfo ? ` [${whoisResult.retryInfo.totalAttempts}/${whoisResult.retryInfo.maxAttempts} attempts]` : '';
1087
1111
  const cacheInfo = whoisResult.fromCache ? ` [CACHED - ${Math.round(whoisResult.cacheAge / 1000)}s old]` : '';
1088
1112
  const duration = whoisResult.fromCache ? `cached in 0ms` : `in ${whoisResult.duration}ms`;
1089
- logToConsoleAndFile(`${messageColors.highlight('[whois]')} Lookup completed for ${domain}${serverUsed} ${duration}${retryInfo}${cacheInfo}`);
1113
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Lookup completed for ${whoisRootDomain}${serverUsed} ${duration}${retryInfo}${cacheInfo}`);
1090
1114
 
1091
1115
  if (whoisResult.retryInfo && whoisResult.retryInfo.retriedAfterFailure) {
1092
1116
  logToConsoleAndFile(`${messageColors.highlight('[whois]')} Success after retry - servers attempted: [${whoisResult.retryInfo.serversAttempted.map(s => s || 'default').join(', ')}]`);
@@ -1099,7 +1123,7 @@ function createNetToolsHandler(config) {
1099
1123
  const errorContext = whoisResult.isTimeout ? 'TIMEOUT' : 'ERROR';
1100
1124
  const retryInfo = whoisResult.retryInfo ? ` [${whoisResult.retryInfo.totalAttempts}/${whoisResult.retryInfo.maxAttempts} attempts]` : '';
1101
1125
 
1102
- logToConsoleAndFile(`${messageColors.highlight('[whois]')} ${errorContext}: Lookup failed for ${domain}${serverUsed} after ${whoisResult.duration}ms${retryInfo}`);
1126
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} ${errorContext}: Lookup failed for ${whoisRootDomain}${serverUsed} after ${whoisResult.duration}ms${retryInfo}`);
1103
1127
  logToConsoleAndFile(`${messageColors.highlight('[whois]')} Command executed: ${whoisResult.command || 'unknown'}`);
1104
1128
  logToConsoleAndFile(`${messageColors.highlight('[whois]')} Error details: ${whoisResult.error}`);
1105
1129
 
@@ -1182,8 +1206,8 @@ function createNetToolsHandler(config) {
1182
1206
 
1183
1207
  // Perform dig lookup if configured
1184
1208
  if (needsDigLookup) {
1185
- // Mark dig domain as being processed
1186
- processedDigDomains.add(digDomain);
1209
+ // Mark dig domain+config as being processed (includes specific subdomain)
1210
+ processedDigDomains.add(digDedupeKey);
1187
1211
 
1188
1212
  if (forceDebug) {
1189
1213
  const digTypes = [];
package/nwss.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v2.0.27 ===
1
+ // === Network scanner script (nwss.js) v2.0.29 ===
2
2
 
3
3
  // puppeteer for browser automation, fs for file system operations, psl for domain parsing.
4
4
  // const pLimit = require('p-limit'); // Will be dynamically imported
@@ -89,12 +89,12 @@ const CONCURRENCY_LIMITS = Object.freeze({
89
89
 
90
90
  // V8 Optimization: Use Map for user agent lookups instead of object
91
91
  const USER_AGENTS = Object.freeze(new Map([
92
- ['chrome', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"],
93
- ['chrome_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"],
94
- ['chrome_linux', "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"],
95
- ['firefox', "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/143.0"],
96
- ['firefox_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/143.0"],
97
- ['firefox_linux', "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/143.0"],
92
+ ['chrome', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
93
+ ['chrome_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
94
+ ['chrome_linux', "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"],
95
+ ['firefox', "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0"],
96
+ ['firefox_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0"],
97
+ ['firefox_linux', "Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0"],
98
98
  ['safari', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15"]
99
99
  ]));
100
100
 
@@ -143,7 +143,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
143
143
  const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage, cleanupPageBeforeReload } = require('./lib/browserhealth');
144
144
 
145
145
  // --- Script Configuration & Constants ---
146
- const VERSION = '2.0.27'; // Script version
146
+ const VERSION = '2.0.29'; // Script version
147
147
 
148
148
  // get startTime
149
149
  const startTime = Date.now();
@@ -747,6 +747,12 @@ function safeMarkDomainProcessed(domain, context, metadata) {
747
747
  }
748
748
  }
749
749
 
750
+ // Global deduplication for nettools to avoid redundant lookups across all URLs
751
+ // Whois: keyed by root domain only (whois data is consistent for entire domain)
752
+ // Dig: keyed by specific subdomain+config (DNS records can vary by subdomain)
753
+ const globalProcessedWhoisDomains = new Set();
754
+ const globalProcessedDigDomains = new Set();
755
+
750
756
  // Handle --clean-rules after config is loaded (so we have access to sites)
751
757
  if (cleanRules || cleanRulesFile) {
752
758
  const filesToClean = cleanRulesFile ? [cleanRulesFile] : [outputFile, compareFile].filter(Boolean);
@@ -1245,6 +1251,16 @@ function setupFrameHandling(page, forceDebug) {
1245
1251
  // Auto-detect best headless mode based on Puppeteer version
1246
1252
  headless: headlessMode,
1247
1253
  args: [
1254
+ // CRITICAL: Remove automation detection markers
1255
+ '--disable-blink-features=AutomationControlled',
1256
+ '--no-first-run',
1257
+ '--disable-default-apps',
1258
+ '--disable-component-extensions-with-background-pages',
1259
+ // HIGH IMPACT: Normal Chrome behavior simulation
1260
+ '--password-store=basic',
1261
+ '--use-mock-keychain',
1262
+ '--disable-client-side-phishing-detection',
1263
+ '--enable-features=NetworkService',
1248
1264
  // Disk space controls - 50MB cache limits
1249
1265
  '--disable-features=VizDisplayCompositor',
1250
1266
  `--disk-cache-size=${CACHE_LIMITS.DISK_CACHE_SIZE}`, // 50MB disk cache
@@ -1260,10 +1276,7 @@ function setupFrameHandling(page, forceDebug) {
1260
1276
  '--aggressive-cache-discard',
1261
1277
  '--memory-pressure-off',
1262
1278
  '--max_old_space_size=2048',
1263
- '--no-first-run',
1264
1279
  '--disable-prompt-on-repost', // Fixes form popup on page reload
1265
- '--disable-default-apps',
1266
- '--disable-component-extensions-with-background-pages',
1267
1280
  '--disable-background-networking',
1268
1281
  '--no-sandbox',
1269
1282
  '--disable-setuid-sandbox',
@@ -1290,14 +1303,13 @@ function setupFrameHandling(page, forceDebug) {
1290
1303
  '--disable-backgrounding-occluded-windows',
1291
1304
  '--disable-background-timer-throttling',
1292
1305
  '--disable-features=site-per-process', // Better for single-site scanning
1293
- '--disable-blink-features=AutomationControlled', // Avoid detection
1294
1306
  '--no-zygote', // Better process isolation
1295
1307
  ],
1296
1308
  // Optimized timeouts for Puppeteer 23.x performance
1297
1309
  protocolTimeout: TIMEOUTS.PROTOCOL_TIMEOUT,
1298
1310
  slowMo: 0, // No artificial delays
1299
1311
  defaultViewport: null, // Use system default viewport
1300
- ignoreDefaultArgs: ['--enable-automation'] // Avoid automation detection
1312
+ ignoreDefaultArgs: ['--enable-automation', '--enable-blink-features=AutomationControlled'] // Avoid automation detection
1301
1313
  });
1302
1314
 
1303
1315
  // Store the user data directory on the browser object for cleanup
@@ -1953,14 +1965,14 @@ function setupFrameHandling(page, forceDebug) {
1953
1965
  }
1954
1966
 
1955
1967
  await page.setExtraHTTPHeaders({
1956
- 'Sec-CH-UA': '"Chromium";v="141", "Not=A?Brand";v="24", "Google Chrome";v="141"',
1968
+ 'Sec-CH-UA': '"Chromium";v="142", "Not=A?Brand";v="24", "Google Chrome";v="142"',
1957
1969
  'Sec-CH-UA-Platform': `"${platform}"`,
1958
1970
  'Sec-CH-UA-Platform-Version': `"${platformVersion}"`,
1959
1971
  'Sec-CH-UA-Mobile': '?0',
1960
1972
  'Sec-CH-UA-Arch': `"${arch}"`,
1961
1973
  'Sec-CH-UA-Bitness': '"64"',
1962
- 'Sec-CH-UA-Full-Version': '"141.0.7390.55"',
1963
- 'Sec-CH-UA-Full-Version-List': '"Chromium";v="141.0.7390.55", "Not=A?Brand";v="24.0.0.0", "Google Chrome";v="141.0.7390.55"'
1974
+ 'Sec-CH-UA-Full-Version': '"142.0.7444,69"',
1975
+ 'Sec-CH-UA-Full-Version-List': '"Chromium";v="142.0.7444,69", "Not=A?Brand";v="24.0.0.0", "Google Chrome";v="142.0.7444,69"'
1964
1976
  });
1965
1977
  }
1966
1978
  } catch (fingerprintErr) {
@@ -2485,6 +2497,8 @@ function setupFrameHandling(page, forceDebug) {
2485
2497
  const netToolsHandler = createNetToolsHandler({
2486
2498
  whoisTerms,
2487
2499
  whoisOrTerms,
2500
+ processedWhoisDomains: globalProcessedWhoisDomains,
2501
+ processedDigDomains: globalProcessedDigDomains,
2488
2502
  whoisDelay: siteConfig.whois_delay !== undefined ? siteConfig.whois_delay : whois_delay,
2489
2503
  whoisServer,
2490
2504
  whoisServerMode: siteConfig.whois_server_mode || whois_server_mode,
@@ -2591,6 +2605,8 @@ function setupFrameHandling(page, forceDebug) {
2591
2605
  const netToolsHandler = createNetToolsHandler({
2592
2606
  whoisTerms,
2593
2607
  whoisOrTerms,
2608
+ processedWhoisDomains: globalProcessedWhoisDomains,
2609
+ processedDigDomains: globalProcessedDigDomains,
2594
2610
  whoisDelay: siteConfig.whois_delay !== undefined ? siteConfig.whois_delay : whois_delay, // Site-specific or global fallback
2595
2611
  whoisServer, // Pass whois server configuration
2596
2612
  whoisServerMode: siteConfig.whois_server_mode || whois_server_mode,
@@ -3544,21 +3560,39 @@ function setupFrameHandling(page, forceDebug) {
3544
3560
  }
3545
3561
  }
3546
3562
 
3547
- // Hang detection for debugging concurrency issues
3548
- let currentBatchInfo = { batchStart: 0, batchSize: 0 };
3549
- const hangDetectionInterval = setInterval(() => {
3550
- // Only show hang detection messages in debug mode
3551
- if (forceDebug) {
3552
- const currentBatch = Math.floor(currentBatchInfo.batchStart / RESOURCE_CLEANUP_INTERVAL) + 1;
3553
- const totalBatches = Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL);
3554
- console.log(formatLogMessage('debug', `[HANG CHECK] Processed: ${processedUrlCount}/${totalUrls} URLs, Batch: ${currentBatch}/${totalBatches}, Current batch size: ${currentBatchInfo.batchSize}`));
3555
- console.log(formatLogMessage('debug', `[HANG CHECK] URLs since cleanup: ${urlsSinceLastCleanup}, Recent failures: ${results.slice(-3).filter(r => !r.success).length}/3`));
3556
- }
3557
- }, 30000); // Check every 30 seconds
3563
+ // Enhanced hang detection with browser restart recovery
3564
+ let currentBatchInfo = { batchStart: 0, batchSize: 0 };
3565
+ let lastProcessedCount = 0;
3566
+ let hangCheckCount = 0;
3567
+ let forceRestartFlag = false; // Flag to trigger restart on next iteration
3568
+
3569
+ const hangDetectionInterval = setInterval(() => {
3570
+ if (forceDebug) {
3571
+ const currentBatch = Math.floor(currentBatchInfo.batchStart / RESOURCE_CLEANUP_INTERVAL) + 1;
3572
+ const totalBatches = Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL);
3573
+ console.log(formatLogMessage('debug', `[HANG CHECK] Processed: ${processedUrlCount}/${totalUrls} URLs, Batch: ${currentBatch}/${totalBatches}, Current batch size: ${currentBatchInfo.batchSize}`));
3574
+ console.log(formatLogMessage('debug', `[HANG CHECK] URLs since cleanup: ${urlsSinceLastCleanup}, Recent failures: ${results.slice(-3).filter(r => !r.success).length}/3`));
3575
+
3576
+ // Check progress and trigger browser restart if hung
3577
+ if (processedUrlCount === lastProcessedCount) {
3578
+ hangCheckCount++;
3579
+ console.log(formatLogMessage('warn', `[HANG CHECK] No progress for ${hangCheckCount * 30}s`));
3580
+ if (hangCheckCount >= 5) {
3581
+ console.log(formatLogMessage('error', `[HANG CHECK] Hung for 2.5 minutes. Triggering emergency browser restart.`));
3582
+ forceRestartFlag = true; // Set flag instead of exiting
3583
+ hangCheckCount = 0; // Reset counter for next cycle
3584
+ }
3585
+ } else {
3586
+ hangCheckCount = 0;
3587
+ }
3588
+ lastProcessedCount = processedUrlCount;
3589
+ }
3590
+ }, 30000);
3558
3591
 
3559
- // Process URLs in batches to maintain concurrency while allowing browser restarts
3560
- let siteGroupIndex = 0;
3561
- for (let batchStart = 0; batchStart < totalUrls; batchStart += RESOURCE_CLEANUP_INTERVAL) {
3592
+ // Process URLs in batches with exception handling
3593
+ let siteGroupIndex = 0;
3594
+ try {
3595
+ for (let batchStart = 0; batchStart < totalUrls; batchStart += RESOURCE_CLEANUP_INTERVAL) {
3562
3596
  const batchEnd = Math.min(batchStart + RESOURCE_CLEANUP_INTERVAL, totalUrls);
3563
3597
  const currentBatch = allTasks.slice(batchStart, batchEnd);
3564
3598
 
@@ -3587,14 +3621,22 @@ function setupFrameHandling(page, forceDebug) {
3587
3621
  hasCriticalErrors ||
3588
3622
  urlsSinceLastCleanup > RESOURCE_CLEANUP_INTERVAL * 0.9 // Very close to cleanup limit
3589
3623
  )) {
3590
- healthCheck = await monitorBrowserHealth(browser, {}, {
3591
- siteIndex: Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL),
3592
- totalSites: Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL),
3593
- urlsSinceCleanup: urlsSinceLastCleanup,
3594
- cleanupInterval: RESOURCE_CLEANUP_INTERVAL,
3595
- forceDebug,
3596
- silentMode
3597
- });
3624
+ try {
3625
+ healthCheck = await Promise.race([
3626
+ monitorBrowserHealth(browser, {}, {
3627
+ siteIndex: Math.floor(batchStart / RESOURCE_CLEANUP_INTERVAL),
3628
+ totalSites: Math.ceil(totalUrls / RESOURCE_CLEANUP_INTERVAL),
3629
+ urlsSinceCleanup: urlsSinceLastCleanup,
3630
+ cleanupInterval: RESOURCE_CLEANUP_INTERVAL,
3631
+ forceDebug,
3632
+ silentMode
3633
+ }),
3634
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), 30000))
3635
+ ]);
3636
+ } catch (healthError) {
3637
+ console.log(formatLogMessage('warn', `[HEALTH CHECK] Timeout, assuming restart needed`));
3638
+ healthCheck = { shouldRestart: true, reason: 'Health check timeout' };
3639
+ }
3598
3640
  } else if (forceDebug && urlsSinceLastCleanup > 10) {
3599
3641
  console.log(formatLogMessage('debug', `Skipping health check: failure rate ${Math.round(recentFailureRate * 100)}%, critical errors: ${hasCriticalErrors ? 'yes' : 'no'}`));
3600
3642
  }
@@ -3612,11 +3654,13 @@ function setupFrameHandling(page, forceDebug) {
3612
3654
  !healthCheck.reason?.includes('Scheduled cleanup') &&
3613
3655
  (healthCheck.reason?.includes('Critical') || healthCheck.reason?.includes('disconnected'));
3614
3656
 
3615
- // Restart browser if we've processed enough URLs, health check suggests it, and this isn't the last site
3616
- if ((wouldExceedLimit || shouldRestartFromHealth || (hasHighFailureRate && recentResults.length >= 6)) && urlsSinceLastCleanup > 8 && isNotLastBatch) {
3617
-
3657
+ // Restart browser if we've processed enough URLs, health check suggests it, hang detected, and this isn't the last site
3658
+ if ((wouldExceedLimit || shouldRestartFromHealth || forceRestartFlag || (hasHighFailureRate && recentResults.length >= 6)) && urlsSinceLastCleanup > 8 && isNotLastBatch) {
3618
3659
  let restartReason = 'Unknown';
3619
- if (shouldRestartFromHealth) {
3660
+ if (forceRestartFlag) {
3661
+ restartReason = 'Emergency restart due to 2.5-minute hang detection';
3662
+ forceRestartFlag = false; // Reset the flag
3663
+ } else if (shouldRestartFromHealth) {
3620
3664
  restartReason = healthCheck.reason;
3621
3665
  } else if (hasHighFailureRate) {
3622
3666
  restartReason = `High failure rate: ${Math.round(recentFailureRate * 100)}% in recent batch`;
@@ -3685,9 +3729,34 @@ function setupFrameHandling(page, forceDebug) {
3685
3729
  console.log(formatLogMessage('debug', `[CONCURRENCY] Starting ${batchSize} concurrent tasks with limit ${MAX_CONCURRENT_SITES}`));
3686
3730
  }
3687
3731
 
3688
- // Create tasks with current browser instance and process them with TRUE concurrency
3689
- const batchTasks = currentBatch.map(task => originalLimit(() => processUrl(task.url, task.config, browser)));
3690
- const batchResults = await Promise.all(batchTasks);
3732
+ // Create tasks with timeout protection
3733
+ const batchTasks = currentBatch.map(task => originalLimit(() => processUrl(task.url, task.config, browser)));
3734
+
3735
+ let batchResults;
3736
+ try {
3737
+ batchResults = await Promise.race([
3738
+ Promise.all(batchTasks),
3739
+ new Promise((_, reject) =>
3740
+ setTimeout(() => reject(new Error('Batch timeout')), 600000) // 10 min timeout
3741
+ )
3742
+ ]);
3743
+ } catch (timeoutError) {
3744
+ if (timeoutError.message.includes('timeout')) {
3745
+ console.log(formatLogMessage('error', `[TIMEOUT] Batch hung. Restarting browser.`));
3746
+ try {
3747
+ await handleBrowserExit(browser, { forceDebug, timeout: 5000, exitOnFailure: false });
3748
+ browser = await createBrowser();
3749
+ urlsSinceLastCleanup = 0;
3750
+ } catch (restartErr) {
3751
+ throw restartErr;
3752
+ }
3753
+ batchResults = currentBatch.map(task => ({
3754
+ success: false, error: 'Batch timeout', needsImmediateRestart: true, url: task.url
3755
+ }));
3756
+ } else {
3757
+ throw timeoutError;
3758
+ }
3759
+ }
3691
3760
 
3692
3761
  // IMPROVED: Much more conservative emergency restart logic
3693
3762
  const criticalRestartCount = batchResults.filter(r => r.needsImmediateRestart).length;
@@ -3801,6 +3870,26 @@ function setupFrameHandling(page, forceDebug) {
3801
3870
  if (forceDebug) console.log(formatLogMessage('debug', `Emergency restart failed: ${emergencyRestartErr.message}`));
3802
3871
  }
3803
3872
  }
3873
+ // Handle hang detection flag if it's still set (e.g., on last batch where normal restart wouldn't trigger)
3874
+ if (forceRestartFlag && batchEnd < totalUrls) {
3875
+ console.log(`\n${messageColors.fileOp('🔄 Emergency hang detection restart:')} Browser appears hung, forcing restart`);
3876
+ try {
3877
+ await handleBrowserExit(browser, { forceDebug, timeout: 5000, exitOnFailure: false, cleanTempFiles: true });
3878
+ browser = await createBrowser();
3879
+ urlsSinceLastCleanup = 0;
3880
+ forceRestartFlag = false; // Reset flag
3881
+ await fastTimeout(TIMEOUTS.EMERGENCY_RESTART_DELAY);
3882
+ if (forceDebug) console.log(formatLogMessage('debug', `Emergency hang detection restart completed`));
3883
+ } catch (hangRestartErr) {
3884
+ if (forceDebug) console.log(formatLogMessage('debug', `Hang detection restart failed: ${hangRestartErr.message}`));
3885
+ // Continue anyway - better to try processing remaining URLs than exit
3886
+ }
3887
+ }
3888
+ }
3889
+ } catch (processingError) {
3890
+ console.log(formatLogMessage('error', `Critical error: ${processingError.message}`));
3891
+ clearInterval(hangDetectionInterval);
3892
+ throw processingError;
3804
3893
  }
3805
3894
 
3806
3895
  // Clear hang detection interval
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "2.0.28",
3
+ "version": "2.0.30",
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": {