@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 +30 -0
- package/lib/fingerprint.js +138 -47
- package/lib/nettools.js +46 -22
- package/nwss.js +134 -45
- package/package.json +1 -1
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
|
+
}
|
package/lib/fingerprint.js
CHANGED
|
@@ -59,7 +59,7 @@ const PRECOMPILED_MOCKS = Object.freeze({
|
|
|
59
59
|
postMessage: () => {},
|
|
60
60
|
disconnect: () => {}
|
|
61
61
|
}),
|
|
62
|
-
getManifest: () => Object.freeze({ name: "Chrome", version: "
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
],
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
|
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: () => ({
|
|
538
|
-
|
|
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
|
-
|
|
948
|
+
|
|
878
949
|
const permissionName = descriptor.name || descriptor;
|
|
879
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
//
|
|
794
|
-
|
|
795
|
-
const
|
|
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
|
-
|
|
859
|
-
const
|
|
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
|
-
|
|
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(
|
|
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 = `${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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.
|
|
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/
|
|
93
|
-
['chrome_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
94
|
-
['chrome_linux', "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
95
|
-
['firefox', "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
96
|
-
['firefox_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
97
|
-
['firefox_linux', "Mozilla/5.0 (X11; Linux x86_64; rv:
|
|
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.
|
|
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="
|
|
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': '"
|
|
1963
|
-
'Sec-CH-UA-Full-Version-List': '"Chromium";v="
|
|
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
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
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
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
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
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
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 (
|
|
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
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
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.
|
|
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": {
|