@fanboynz/network-scanner 2.0.8 → 2.0.10
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/README.md +1 -0
- package/lib/browserhealth.js +86 -1
- package/lib/cdp.js +48 -8
- package/lib/cloudflare.js +7 -1
- package/nwss.1 +24 -1
- package/nwss.js +169 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -248,6 +248,7 @@ When a page redirects to a new domain, first-party/third-party detection is base
|
|
|
248
248
|
| `isBrave` | Boolean | `false` | Spoof Brave browser detection |
|
|
249
249
|
| `evaluateOnNewDocument` | Boolean | `false` | Inject fetch/XHR interceptor in page |
|
|
250
250
|
| `cdp` | Boolean | `false` | Enable CDP logging for this site |
|
|
251
|
+
| `cdp_specific` | Array | - | Enable CDP logging only for specific domains in the URL list |
|
|
251
252
|
| `css_blocked` | Array | - | CSS selectors to hide elements |
|
|
252
253
|
| `source` | Boolean | `false` | Save page source HTML after load |
|
|
253
254
|
| `screenshot` | Boolean | `false` | Capture screenshot on load failure |
|
package/lib/browserhealth.js
CHANGED
|
@@ -970,6 +970,90 @@ async function isBrowserHealthy(browserInstance, includeNetworkTest = true) {
|
|
|
970
970
|
}
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Performs comprehensive cleanup of page resources before operations that might cause detached frames
|
|
975
|
+
* Also attempts to stop any pending navigations that might interfere
|
|
976
|
+
* Used before reloads, navigations, and other operations that can trigger frame detachment
|
|
977
|
+
* @param {import('puppeteer').Page} page - Page to clean up
|
|
978
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
979
|
+
* @returns {Promise<boolean>} True if cleanup succeeded
|
|
980
|
+
*/
|
|
981
|
+
async function cleanupPageBeforeReload(page, forceDebug = false) {
|
|
982
|
+
try {
|
|
983
|
+
if (page.isClosed()) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// First, try to stop any pending navigation
|
|
988
|
+
try {
|
|
989
|
+
await page.evaluate(() => {
|
|
990
|
+
// Stop any ongoing navigation
|
|
991
|
+
if (window.stop) {
|
|
992
|
+
window.stop();
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
} catch (e) {
|
|
996
|
+
// Page might be mid-navigation, that's ok
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Wait a bit for navigation to stop
|
|
1000
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1001
|
+
|
|
1002
|
+
// Now do the full cleanup
|
|
1003
|
+
await page.evaluate(() => {
|
|
1004
|
+
// Stop all media elements
|
|
1005
|
+
document.querySelectorAll('video, audio').forEach(media => {
|
|
1006
|
+
try {
|
|
1007
|
+
media.pause();
|
|
1008
|
+
media.src = '';
|
|
1009
|
+
media.load();
|
|
1010
|
+
} catch(e) {}
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Clear all timers and intervals
|
|
1014
|
+
const highestId = setTimeout(() => {}, 0);
|
|
1015
|
+
for (let i = highestId; i >= 0; i--) {
|
|
1016
|
+
clearTimeout(i);
|
|
1017
|
+
clearInterval(i);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Stop all animations
|
|
1021
|
+
if (typeof cancelAnimationFrame !== 'undefined') {
|
|
1022
|
+
const highestRAF = requestAnimationFrame(() => {});
|
|
1023
|
+
for (let i = highestRAF; i >= 0; i--) {
|
|
1024
|
+
cancelAnimationFrame(i);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Clear all iframes properly
|
|
1029
|
+
document.querySelectorAll('iframe').forEach(iframe => {
|
|
1030
|
+
try {
|
|
1031
|
+
// Stop iframe content first
|
|
1032
|
+
if (iframe.contentWindow) {
|
|
1033
|
+
iframe.contentWindow.stop();
|
|
1034
|
+
}
|
|
1035
|
+
iframe.src = 'about:blank';
|
|
1036
|
+
iframe.remove();
|
|
1037
|
+
} catch(e) {}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// Force garbage collection if available
|
|
1041
|
+
if (window.gc) window.gc();
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
if (forceDebug) {
|
|
1045
|
+
console.log(formatLogMessage('debug', 'Page resources cleaned before reload'));
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return true;
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
if (forceDebug) {
|
|
1051
|
+
console.log(formatLogMessage('debug', `Page cleanup error: ${err.message}`));
|
|
1052
|
+
}
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
973
1057
|
module.exports = {
|
|
974
1058
|
checkBrowserHealth,
|
|
975
1059
|
checkBrowserMemory,
|
|
@@ -983,7 +1067,8 @@ module.exports = {
|
|
|
983
1067
|
monitorBrowserHealth,
|
|
984
1068
|
isBrowserHealthy,
|
|
985
1069
|
isCriticalProtocolError,
|
|
986
|
-
updatePageUsage
|
|
1070
|
+
updatePageUsage,
|
|
1071
|
+
cleanupPageBeforeReload
|
|
987
1072
|
};
|
|
988
1073
|
|
|
989
1074
|
// Clean up tracking maps when pages are closed
|
package/lib/cdp.js
CHANGED
|
@@ -116,6 +116,7 @@ async function setRequestInterceptionWithTimeout(page, timeout = 15000) {
|
|
|
116
116
|
* @param {boolean} options.enableCDP - Global CDP flag (from --cdp command line)
|
|
117
117
|
* @param {boolean} options.siteSpecificCDP - Site-specific CDP flag (from config)
|
|
118
118
|
* @param {boolean} options.forceDebug - Debug logging flag
|
|
119
|
+
* @param {string} options.currentUrl - Current URL for domain-specific CDP decisions
|
|
119
120
|
* @returns {Promise<object>} CDP session object with cleanup method
|
|
120
121
|
*/
|
|
121
122
|
async function createCDPSession(page, currentUrl, options = {}) {
|
|
@@ -132,10 +133,14 @@ async function createCDPSession(page, currentUrl, options = {}) {
|
|
|
132
133
|
|
|
133
134
|
// Log which CDP mode is being used
|
|
134
135
|
if (forceDebug) {
|
|
136
|
+
const urlHostname = (() => {
|
|
137
|
+
try { return new URL(currentUrl).hostname; } catch { return 'unknown'; }
|
|
138
|
+
})();
|
|
139
|
+
|
|
135
140
|
if (enableCDP) {
|
|
136
|
-
console.log(formatLogMessage('debug', `
|
|
141
|
+
console.log(formatLogMessage('debug', `[cdp] Global CDP enabled by --cdp flag for ${urlHostname}`));
|
|
137
142
|
} else if (siteSpecificCDP === true) {
|
|
138
|
-
console.log(formatLogMessage('debug', `CDP
|
|
143
|
+
console.log(formatLogMessage('debug', `[cdp] Site-specific CDP enabled for ${urlHostname} (via cdp: true or cdp_specific domain match)`));
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
@@ -163,14 +168,22 @@ async function createCDPSession(page, currentUrl, options = {}) {
|
|
|
163
168
|
// Extract hostname for logging context (handles URL parsing errors gracefully)
|
|
164
169
|
let hostnameForLog = 'unknown-host';
|
|
165
170
|
try {
|
|
166
|
-
|
|
171
|
+
const currentHostname = new URL(currentUrl).hostname;
|
|
172
|
+
const requestHostname = new URL(requestUrl).hostname;
|
|
173
|
+
// Show both hostnames if different (cross-domain requests)
|
|
174
|
+
if (currentHostname !== requestHostname) {
|
|
175
|
+
hostnameForLog = `${currentHostname}?${requestHostname}`;
|
|
176
|
+
} else {
|
|
177
|
+
hostnameForLog = currentHostname;
|
|
178
|
+
}
|
|
167
179
|
} catch (_) {
|
|
168
180
|
// Ignore URL parsing errors for logging context
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
// Log the request with context only if debug mode is enabled
|
|
184
|
+
if (forceDebug) {
|
|
185
|
+
console.log(formatLogMessage('debug', `[cdp][${hostnameForLog}] ${method} ${requestUrl} (initiator: ${initiator})`));
|
|
186
|
+
}
|
|
174
187
|
});
|
|
175
188
|
|
|
176
189
|
if (forceDebug) {
|
|
@@ -200,6 +213,15 @@ async function createCDPSession(page, currentUrl, options = {}) {
|
|
|
200
213
|
} catch (cdpErr) {
|
|
201
214
|
cdpSession = null; // Reset on failure
|
|
202
215
|
|
|
216
|
+
// Enhanced error context for CDP domain-specific debugging
|
|
217
|
+
const urlContext = (() => {
|
|
218
|
+
try {
|
|
219
|
+
return new URL(currentUrl).hostname;
|
|
220
|
+
} catch {
|
|
221
|
+
return currentUrl.substring(0, 50) + '...';
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
224
|
+
|
|
203
225
|
// Categorize CDP errors for proper handling
|
|
204
226
|
// Enhanced error handling for Puppeteer 20+ error patterns
|
|
205
227
|
if (cdpErr.message.includes('Network.enable timed out') ||
|
|
@@ -236,9 +258,10 @@ async function createCDPSession(page, currentUrl, options = {}) {
|
|
|
236
258
|
*
|
|
237
259
|
* @param {object} siteConfig - Site configuration object
|
|
238
260
|
* @param {boolean} globalCDP - Global CDP flag
|
|
261
|
+
* @param {Array} cdpSpecificDomains - Array of domains for cdp_specific feature
|
|
239
262
|
* @returns {object} Validation result with recommendations
|
|
240
263
|
*/
|
|
241
|
-
function validateCDPConfig(siteConfig, globalCDP) {
|
|
264
|
+
function validateCDPConfig(siteConfig, globalCDP, cdpSpecificDomains = []) {
|
|
242
265
|
const warnings = [];
|
|
243
266
|
const recommendations = [];
|
|
244
267
|
|
|
@@ -247,8 +270,25 @@ function validateCDPConfig(siteConfig, globalCDP) {
|
|
|
247
270
|
warnings.push('Site-specific CDP disabled but global CDP is enabled - global setting will override');
|
|
248
271
|
}
|
|
249
272
|
|
|
273
|
+
// Validate cdp_specific configuration
|
|
274
|
+
if (siteConfig.cdp_specific) {
|
|
275
|
+
if (!Array.isArray(siteConfig.cdp_specific)) {
|
|
276
|
+
warnings.push('cdp_specific must be an array of domain strings');
|
|
277
|
+
} else if (siteConfig.cdp_specific.length === 0) {
|
|
278
|
+
warnings.push('cdp_specific is empty - no domains will have CDP enabled');
|
|
279
|
+
} else {
|
|
280
|
+
// Validate domain format
|
|
281
|
+
const invalidDomains = siteConfig.cdp_specific.filter(domain => {
|
|
282
|
+
return typeof domain !== 'string' || domain.trim() === '';
|
|
283
|
+
});
|
|
284
|
+
if (invalidDomains.length > 0) {
|
|
285
|
+
warnings.push(`cdp_specific contains invalid domains: ${invalidDomains.join(', ')}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
// Performance recommendations
|
|
251
|
-
if (globalCDP || siteConfig.cdp === true) {
|
|
291
|
+
if (globalCDP || siteConfig.cdp === true || (siteConfig.cdp_specific && siteConfig.cdp_specific.length > 0)) {
|
|
252
292
|
recommendations.push('CDP logging enabled - this may impact performance for high-traffic sites');
|
|
253
293
|
|
|
254
294
|
if (siteConfig.timeout && siteConfig.timeout < 30000) {
|
package/lib/cloudflare.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cloudflare bypass and challenge handling module - Optimized with smart detection and adaptive timeouts
|
|
3
|
+
* Version: 2.6.3 - Fixes Cannot read properties of undefined (reading 'hasIndicators')
|
|
3
4
|
* Version: 2.6.2 - Further detached Frame fixes
|
|
4
5
|
* Version: 2.6.1 - timeoutId is not defined & race condition fix
|
|
5
6
|
* Version: 2.6.0 - Memory leak fixes and timeout cleanup
|
|
@@ -19,7 +20,7 @@ const { formatLogMessage } = require('./colorize');
|
|
|
19
20
|
/**
|
|
20
21
|
* Module version information
|
|
21
22
|
*/
|
|
22
|
-
const CLOUDFLARE_MODULE_VERSION = '2.6.
|
|
23
|
+
const CLOUDFLARE_MODULE_VERSION = '2.6.3';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Timeout constants for various operations (in milliseconds)
|
|
@@ -1570,6 +1571,11 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
|
|
|
1570
1571
|
|
|
1571
1572
|
// Quick detection first - exit early if no Cloudflare detected and no explicit config
|
|
1572
1573
|
const quickDetection = await quickCloudflareDetection(page, forceDebug);
|
|
1574
|
+
|
|
1575
|
+
// Safety check: ensure quickDetection is valid
|
|
1576
|
+
if (!quickDetection) {
|
|
1577
|
+
return { phishingWarning: { attempted: false, success: true }, verificationChallenge: { attempted: false, success: true }, overallSuccess: true, errors: [], quickDetectionFailed: true };
|
|
1578
|
+
}
|
|
1573
1579
|
|
|
1574
1580
|
// Early return structure when no Cloudflare indicators found
|
|
1575
1581
|
// Sets attempted: false, success: true for both protection types
|
package/nwss.1
CHANGED
|
@@ -282,7 +282,6 @@ Array of CSS selectors to hide elements on the page.
|
|
|
282
282
|
.B userAgent
|
|
283
283
|
Spoof User-Agent: \fB"chrome"\fR, \fB"chrome_mac"\fR, \fB"chrome_linux"\fR, \fB"firefox"\fR, \fB"firefox_mac"\fR, \fB"firefox_linux"\fR, or \fB"safari"\fR.
|
|
284
284
|
|
|
285
|
-
|
|
286
285
|
.TP
|
|
287
286
|
.B interact
|
|
288
287
|
Boolean. Simulate mouse movements and clicks.
|
|
@@ -442,6 +441,10 @@ Boolean. Inject Fetch/XHR interceptor scripts into page context.
|
|
|
442
441
|
.B cdp
|
|
443
442
|
Boolean. Enable Chrome DevTools Protocol logging for this specific site.
|
|
444
443
|
|
|
444
|
+
.TP
|
|
445
|
+
.B cdp_specific
|
|
446
|
+
Array of domain names. Enable Chrome DevTools Protocol logging only for URLs matching these specific domains within a multi-URL site configuration. Takes precedence over \fBcdp: false\fR but is ignored if \fBcdp: true\fR is set. Supports exact hostname matching and subdomain matching (e.g., "example.com" matches both "example.com" and "subdomain.example.com"). Useful for selective debugging of network requests on specific domains while avoiding CDP overhead on others.
|
|
447
|
+
|
|
445
448
|
.TP
|
|
446
449
|
.B source
|
|
447
450
|
Boolean. Save page source HTML after loading.
|
|
@@ -880,6 +883,26 @@ node nwss.js --ignore-cache --debug -o rules.txt
|
|
|
880
883
|
}
|
|
881
884
|
.EE
|
|
882
885
|
|
|
886
|
+
.SS Selective CDP logging for specific domains:
|
|
887
|
+
.EX
|
|
888
|
+
{
|
|
889
|
+
"url": [
|
|
890
|
+
"https://site1.com/page1",
|
|
891
|
+
"https://debug-target.com/page2",
|
|
892
|
+
"https://site2.com/page3"
|
|
893
|
+
],
|
|
894
|
+
"filterRegex": "\\\\.(space|website)\\\\b",
|
|
895
|
+
"cdp_specific": ["debug-target.com"],
|
|
896
|
+
"resourceTypes": ["script", "fetch"],
|
|
897
|
+
"comments": [
|
|
898
|
+
"CDP enabled only for debug-target.com",
|
|
899
|
+
"Other URLs run without CDP overhead"
|
|
900
|
+
]
|
|
901
|
+
}
|
|
902
|
+
.EE
|
|
903
|
+
|
|
904
|
+
Note: If \fBcdp: true\fR is also set, \fBcdp_specific\fR is ignored and CDP is enabled for all URLs.
|
|
905
|
+
|
|
883
906
|
.SS FlowProxy protection handling:
|
|
884
907
|
.EX
|
|
885
908
|
{
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v2.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v2.0.10 ===
|
|
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
|
|
@@ -66,7 +66,7 @@ const TIMEOUTS = {
|
|
|
66
66
|
EMERGENCY_RESTART_DELAY: 2000,
|
|
67
67
|
BROWSER_STABILIZE_DELAY: 1000,
|
|
68
68
|
CURL_HANDLER_DELAY: 3000,
|
|
69
|
-
PROTOCOL_TIMEOUT:
|
|
69
|
+
PROTOCOL_TIMEOUT: 160000,
|
|
70
70
|
REDIRECT_JS_TIMEOUT: 5000
|
|
71
71
|
};
|
|
72
72
|
|
|
@@ -126,10 +126,10 @@ function detectPuppeteerVersion() {
|
|
|
126
126
|
// Enhanced redirect handling
|
|
127
127
|
const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/redirect');
|
|
128
128
|
// Ensure web browser is working correctly
|
|
129
|
-
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage } = require('./lib/browserhealth');
|
|
129
|
+
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage, cleanupPageBeforeReload } = require('./lib/browserhealth');
|
|
130
130
|
|
|
131
131
|
// --- Script Configuration & Constants ---
|
|
132
|
-
const VERSION = '2.0.
|
|
132
|
+
const VERSION = '2.0.10'; // Script version
|
|
133
133
|
|
|
134
134
|
// get startTime
|
|
135
135
|
const startTime = Date.now();
|
|
@@ -555,6 +555,7 @@ FlowProxy Protection Options:
|
|
|
555
555
|
Advanced Options:
|
|
556
556
|
evaluateOnNewDocument: true/false Inject fetch/XHR interceptor in page (for this site)
|
|
557
557
|
cdp: true/false Enable CDP logging for this site Inject fetch/XHR interceptor in page
|
|
558
|
+
cdp_specific: ["domain1.com", "domain2.com"] Enable CDP logging only for specific domains in the URL list
|
|
558
559
|
interact_duration: <milliseconds> Duration of interaction simulation (default: 2000)
|
|
559
560
|
interact_scrolling: true/false Enable scrolling simulation (default: true)
|
|
560
561
|
interact_clicks: true/false Enable element clicking simulation (default: false)
|
|
@@ -923,6 +924,30 @@ function safeGetDomain(url, getFullHostname = false) {
|
|
|
923
924
|
}
|
|
924
925
|
}
|
|
925
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Checks if a URL matches any domain in the cdp_specific list
|
|
929
|
+
* @param {string} url - The URL to check
|
|
930
|
+
* @param {Array} cdpSpecificList - Array of domains that should have CDP enabled
|
|
931
|
+
* @returns {boolean} True if URL matches a domain in the list
|
|
932
|
+
*/
|
|
933
|
+
function shouldEnableCDPForUrl(url, cdpSpecificList) {
|
|
934
|
+
if (!cdpSpecificList || !Array.isArray(cdpSpecificList) || cdpSpecificList.length === 0) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
try {
|
|
939
|
+
const urlHostname = new URL(url).hostname;
|
|
940
|
+
return cdpSpecificList.some(domain => {
|
|
941
|
+
// Remove protocol if present and clean domain
|
|
942
|
+
const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
|
|
943
|
+
// Match exact hostname or subdomain
|
|
944
|
+
return urlHostname === cleanDomain || urlHostname.endsWith('.' + cleanDomain);
|
|
945
|
+
});
|
|
946
|
+
} catch (urlErr) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
926
951
|
/**
|
|
927
952
|
* Outputs dry run results to console with formatted display
|
|
928
953
|
* If outputFile is specified, also captures output for file writing
|
|
@@ -1062,7 +1087,7 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
|
|
|
1062
1087
|
|
|
1063
1088
|
function setupFrameHandling(page, forceDebug) {
|
|
1064
1089
|
// Track active frames and clear on navigation to prevent detached frame access
|
|
1065
|
-
let activeFrames = new
|
|
1090
|
+
let activeFrames = new Map(); // Use Map to track frame state
|
|
1066
1091
|
|
|
1067
1092
|
// Clear frame tracking on navigation to prevent stale references
|
|
1068
1093
|
page.on('framenavigated', (frame) => {
|
|
@@ -1076,6 +1101,15 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1076
1101
|
page.on('frameattached', async (frame) => {
|
|
1077
1102
|
// Enhanced frame handling with detached frame protection
|
|
1078
1103
|
try {
|
|
1104
|
+
// Test frame accessibility first with safe method
|
|
1105
|
+
let isFrameValid = false;
|
|
1106
|
+
try {
|
|
1107
|
+
frame.url(); // This will throw if frame is detached
|
|
1108
|
+
isFrameValid = true;
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
return; // Frame is already detached, skip
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1079
1113
|
// Multiple checks for frame validity to prevent detached frame errors
|
|
1080
1114
|
if (!frame) {
|
|
1081
1115
|
if (forceDebug) {
|
|
@@ -1111,7 +1145,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1111
1145
|
return;
|
|
1112
1146
|
}
|
|
1113
1147
|
|
|
1114
|
-
|
|
1148
|
+
// Store frame with timestamp for tracking
|
|
1149
|
+
activeFrames.set(frame, Date.now());
|
|
1150
|
+
|
|
1151
|
+
if (frame !== page.mainFrame() && frame.parentFrame()) { // Only handle child frames
|
|
1115
1152
|
try {
|
|
1116
1153
|
if (forceDebug) {
|
|
1117
1154
|
console.log(formatLogMessage('debug', `New frame attached: ${frameUrl || 'about:blank'}`));
|
|
@@ -1475,6 +1512,24 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1475
1512
|
return { url: currentUrl, rules: [], success: false, skipped: true };
|
|
1476
1513
|
}
|
|
1477
1514
|
|
|
1515
|
+
// Determine CDP enablement based on cdp_specific or traditional cdp setting
|
|
1516
|
+
let shouldEnableCDPForThisUrl = false;
|
|
1517
|
+
if (siteConfig.cdp === true) {
|
|
1518
|
+
// If cdp: true is set, enable CDP for all URLs and ignore cdp_specific
|
|
1519
|
+
shouldEnableCDPForThisUrl = true;
|
|
1520
|
+
if (forceDebug && siteConfig.cdp_specific) {
|
|
1521
|
+
console.log(formatLogMessage('debug', `CDP enabled for all URLs via cdp: true - ignoring cdp_specific for ${currentUrl}`));
|
|
1522
|
+
}
|
|
1523
|
+
} else if (siteConfig.cdp_specific && Array.isArray(siteConfig.cdp_specific)) {
|
|
1524
|
+
// Only use cdp_specific if cdp is not explicitly set to true
|
|
1525
|
+
shouldEnableCDPForThisUrl = shouldEnableCDPForUrl(currentUrl, siteConfig.cdp_specific);
|
|
1526
|
+
if (forceDebug && shouldEnableCDPForThisUrl) {
|
|
1527
|
+
console.log(formatLogMessage('debug', `CDP enabled for ${currentUrl} via cdp_specific domain match`));
|
|
1528
|
+
}
|
|
1529
|
+
} else {
|
|
1530
|
+
shouldEnableCDPForThisUrl = false;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1478
1533
|
let page = null;
|
|
1479
1534
|
let cdpSession = null;
|
|
1480
1535
|
let cdpSessionManager = null;
|
|
@@ -1884,7 +1939,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1884
1939
|
try {
|
|
1885
1940
|
cdpSessionManager = await createCDPSession(page, currentUrl, {
|
|
1886
1941
|
enableCDP,
|
|
1887
|
-
siteSpecificCDP:
|
|
1942
|
+
siteSpecificCDP: shouldEnableCDPForThisUrl,
|
|
1888
1943
|
forceDebug
|
|
1889
1944
|
});
|
|
1890
1945
|
} catch (cdpErr) {
|
|
@@ -1936,7 +1991,17 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1936
1991
|
}
|
|
1937
1992
|
|
|
1938
1993
|
// --- Apply all fingerprint spoofing (user agent, Brave, fingerprint protection) ---
|
|
1939
|
-
|
|
1994
|
+
try {
|
|
1995
|
+
await applyAllFingerprintSpoofing(page, siteConfig, forceDebug, currentUrl);
|
|
1996
|
+
} catch (fingerprintErr) {
|
|
1997
|
+
if (fingerprintErr.message.includes('Session closed') ||
|
|
1998
|
+
fingerprintErr.message.includes('Protocol error') ||
|
|
1999
|
+
fingerprintErr.message.includes('addScriptToEvaluateOnNewDocument')) {
|
|
2000
|
+
console.warn(`[fingerprint protection failed] ${currentUrl}: ${fingerprintErr.message}`);
|
|
2001
|
+
} else {
|
|
2002
|
+
throw fingerprintErr;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1940
2005
|
|
|
1941
2006
|
const regexes = Array.isArray(siteConfig.filterRegex)
|
|
1942
2007
|
? siteConfig.filterRegex.map(r => new RegExp(r.replace(/^\/(.*)\/$/, '$1')))
|
|
@@ -3039,6 +3104,18 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3039
3104
|
// Page finished initial loading - mark as idle
|
|
3040
3105
|
updatePageUsage(page, false);
|
|
3041
3106
|
} catch (err) {
|
|
3107
|
+
// Handle detached frame errors during navigation
|
|
3108
|
+
if (err.message.includes('Navigating frame was detached') ||
|
|
3109
|
+
err.message.includes('Attempted to use detached')) {
|
|
3110
|
+
// Silent handling - this is expected for iframe-heavy sites
|
|
3111
|
+
if (forceDebug) {
|
|
3112
|
+
console.log(formatLogMessage('debug', `Frame detachment during navigation (expected): ${currentUrl}`));
|
|
3113
|
+
}
|
|
3114
|
+
// Continue with partial success - don't fail completely
|
|
3115
|
+
currentPageUrl = currentUrl;
|
|
3116
|
+
siteCounter++;
|
|
3117
|
+
// Skip to post-navigation processing
|
|
3118
|
+
} else {
|
|
3042
3119
|
// Enhanced error handling for redirect timeouts using redirect module
|
|
3043
3120
|
const timeoutResult = await handleRedirectTimeout(page, currentUrl, err, safeGetDomain, forceDebug, formatLogMessage);
|
|
3044
3121
|
|
|
@@ -3052,6 +3129,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3052
3129
|
throw err;
|
|
3053
3130
|
}
|
|
3054
3131
|
}
|
|
3132
|
+
}
|
|
3055
3133
|
|
|
3056
3134
|
if (interactEnabled && !disableInteract) {
|
|
3057
3135
|
if (forceDebug) console.log(formatLogMessage('debug', `interaction simulation enabled for ${currentUrl}`));
|
|
@@ -3098,13 +3176,41 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3098
3176
|
}
|
|
3099
3177
|
|
|
3100
3178
|
for (let i = 1; i <= totalReloads; i++) {
|
|
3101
|
-
|
|
3179
|
+
// Check browser health before attempting reload
|
|
3180
|
+
try {
|
|
3181
|
+
const browserHealthy = await isQuicklyResponsive(browser, 2000);
|
|
3182
|
+
if (!browserHealthy) {
|
|
3183
|
+
if (forceDebug) {
|
|
3184
|
+
console.log(formatLogMessage('debug', `Browser unresponsive before reload #${i}, skipping remaining reloads`));
|
|
3185
|
+
}
|
|
3186
|
+
console.warn(`Browser unresponsive before reload #${i}, skipping remaining reloads`);
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
} catch (healthErr) {
|
|
3190
|
+
console.warn(`Browser health check failed before reload #${i}: ${healthErr.message}`);
|
|
3191
|
+
break;
|
|
3192
|
+
}
|
|
3193
|
+
// Check if page is still valid before attempting reload
|
|
3194
|
+
let pageStillValid = false;
|
|
3102
3195
|
try {
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3196
|
+
// Add timeout to page validity check
|
|
3197
|
+
await Promise.race([
|
|
3198
|
+
page.evaluate(() => true),
|
|
3199
|
+
new Promise((_, reject) =>
|
|
3200
|
+
setTimeout(() => reject(new Error('Page validity check timeout')), 3000)
|
|
3201
|
+
)
|
|
3202
|
+
]);
|
|
3203
|
+
pageStillValid = true;
|
|
3204
|
+
} catch (validityCheck) {
|
|
3205
|
+
console.warn(`Page invalid before reload #${i}, skipping remaining reloads`);
|
|
3206
|
+
break;
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
// Use comprehensive cleanup from browserhealth module
|
|
3210
|
+
await cleanupPageBeforeReload(page, forceDebug);
|
|
3211
|
+
|
|
3212
|
+
// Add stabilization delay after cleanup
|
|
3213
|
+
await fastTimeout(1000);
|
|
3108
3214
|
|
|
3109
3215
|
if (siteConfig.clear_sitedata === true) {
|
|
3110
3216
|
try {
|
|
@@ -3116,32 +3222,36 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3116
3222
|
}
|
|
3117
3223
|
|
|
3118
3224
|
let reloadSuccess = false;
|
|
3225
|
+
|
|
3226
|
+
// Skip force reload if browser seems unhealthy
|
|
3227
|
+
const skipForceReload = i > 2; // After 2 attempts, skip force reload
|
|
3119
3228
|
|
|
3120
|
-
if (useForceReload && !reloadSuccess) {
|
|
3229
|
+
if (useForceReload && !reloadSuccess && !skipForceReload) {
|
|
3121
3230
|
// Attempt force reload: disable cache, reload, re-enable cache
|
|
3122
3231
|
try {
|
|
3123
|
-
//
|
|
3232
|
+
// Timeout-protected cache disable
|
|
3124
3233
|
await Promise.race([
|
|
3125
3234
|
page.setCacheEnabled(false),
|
|
3126
|
-
new Promise((_, reject) =>
|
|
3127
|
-
setTimeout(() => reject(new Error('setCacheEnabled(false) timeout')), 5000)
|
|
3128
|
-
)
|
|
3235
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Cache disable timeout')), 8000))
|
|
3129
3236
|
]);
|
|
3130
3237
|
|
|
3131
|
-
|
|
3238
|
+
// Use networkidle2 for force reload to better detect when page is actually loaded
|
|
3239
|
+
await page.reload({ waitUntil: 'networkidle2', timeout: Math.min(timeout, 15000) });
|
|
3132
3240
|
|
|
3241
|
+
// Timeout-protected cache enable
|
|
3133
3242
|
await Promise.race([
|
|
3134
3243
|
page.setCacheEnabled(true),
|
|
3135
|
-
new Promise((_, reject) =>
|
|
3136
|
-
setTimeout(() => reject(new Error('setCacheEnabled(true) timeout')), 5000)
|
|
3137
|
-
)
|
|
3244
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Cache enable timeout')), 8000))
|
|
3138
3245
|
]);
|
|
3139
|
-
|
|
3246
|
+
|
|
3140
3247
|
reloadSuccess = true;
|
|
3141
3248
|
if (forceDebug) console.log(formatLogMessage('debug', `Force reload #${i} completed for ${currentUrl}`));
|
|
3142
3249
|
|
|
3143
3250
|
} catch (forceReloadErr) {
|
|
3144
|
-
|
|
3251
|
+
// Don't warn for timeouts on problematic sites, just fall back silently
|
|
3252
|
+
if (forceDebug || !forceReloadErr.message.includes('timeout')) {
|
|
3253
|
+
console.warn(messageColors.warn(`[force reload #${i} failed] ${currentUrl}: ${forceReloadErr.message} - falling back to standard reload`));
|
|
3254
|
+
}
|
|
3145
3255
|
reloadSuccess = false; // Ensure we try standard reload
|
|
3146
3256
|
}
|
|
3147
3257
|
}
|
|
@@ -3149,24 +3259,39 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3149
3259
|
// Fallback to standard reload if force reload failed or wasn't attempted
|
|
3150
3260
|
if (!reloadSuccess) {
|
|
3151
3261
|
try {
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3262
|
+
const canReload = await page.evaluate(() => {
|
|
3263
|
+
return !!(document && document.body);
|
|
3264
|
+
}).catch(() => false);
|
|
3265
|
+
|
|
3266
|
+
if (!canReload) {
|
|
3267
|
+
throw new Error('Page document invalid for reload');
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// Use networkidle2 with reasonable timeout
|
|
3271
|
+
// Use simpler reload for problematic pages
|
|
3272
|
+
const reloadOptions = i > 1
|
|
3273
|
+
? { waitUntil: 'domcontentloaded', timeout: 10000 } // Simpler after failures
|
|
3274
|
+
: { waitUntil: 'networkidle2', timeout: 15000 }; // Full wait first time
|
|
3275
|
+
|
|
3276
|
+
await page.reload(reloadOptions);
|
|
3277
|
+
|
|
3155
3278
|
if (forceDebug) console.log(formatLogMessage('debug', `Standard reload #${i} completed for ${currentUrl}`));
|
|
3156
3279
|
} catch (standardReloadErr) {
|
|
3157
|
-
|
|
3280
|
+
// Only warn for non-timeout errors
|
|
3281
|
+
if (!standardReloadErr.message.includes('timeout')) {
|
|
3282
|
+
console.warn(messageColors.warn(`[standard reload #${i} failed] ${currentUrl}: ${standardReloadErr.message}`));
|
|
3283
|
+
} else if (forceDebug) {
|
|
3284
|
+
console.log(formatLogMessage('debug', `Reload #${i} timed out for ${currentUrl}, continuing anyway`));
|
|
3285
|
+
}
|
|
3158
3286
|
|
|
3159
3287
|
// Check if this is a persistent failure that should skip remaining reloads
|
|
3160
|
-
|
|
3288
|
+
const isPersistentFailure = standardReloadErr.message.includes('detached Frame') ||
|
|
3289
|
+
standardReloadErr.message.includes('Attempted to use detached') ||
|
|
3290
|
+
standardReloadErr.message.includes('Navigating frame was detached') ||
|
|
3291
|
+
standardReloadErr.message.includes('document invalid') ||
|
|
3161
3292
|
standardReloadErr.message.includes('net::ERR_') ||
|
|
3162
3293
|
standardReloadErr.message.includes('Protocol error') ||
|
|
3163
|
-
|
|
3164
|
-
// CDP and injection failures
|
|
3165
|
-
standardReloadErr.constructor.name === 'ProtocolError' ||
|
|
3166
|
-
standardReloadErr.name === 'ProtocolError' ||
|
|
3167
|
-
standardReloadErr.message.includes('addScriptToEvaluateOnNewDocument timed out') ||
|
|
3168
|
-
standardReloadErr.message.includes('Runtime.callFunctionOn timed out') ||
|
|
3169
|
-
standardReloadErr.message.includes('CDP injection timeout');
|
|
3294
|
+
standardReloadErr.message.includes('Page crashed');
|
|
3170
3295
|
|
|
3171
3296
|
if (isPersistentFailure) {
|
|
3172
3297
|
const remainingReloads = totalReloads - i;
|
|
@@ -3176,16 +3301,16 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3176
3301
|
// Break out of reload loop to move to next URL faster
|
|
3177
3302
|
break;
|
|
3178
3303
|
}
|
|
3304
|
+
// For navigation timeouts, we can continue - the page might still be partially loaded
|
|
3305
|
+
// Don't break the loop for simple timeouts
|
|
3179
3306
|
}
|
|
3180
|
-
} else {
|
|
3181
|
-
// Regular reload
|
|
3182
|
-
await page.reload({ waitUntil: 'domcontentloaded', timeout: Math.min(timeout, 15000) });
|
|
3183
3307
|
}
|
|
3184
3308
|
|
|
3185
3309
|
// Only add delay if we're continuing with more reloads
|
|
3186
3310
|
if (i < totalReloads) {
|
|
3187
|
-
|
|
3188
|
-
|
|
3311
|
+
// Reduce delay for problematic sites
|
|
3312
|
+
const adjustedDelay = i > 1 ? Math.min(delayMs, 2000) : delayMs;
|
|
3313
|
+
await fastTimeout(adjustedDelay);
|
|
3189
3314
|
}
|
|
3190
3315
|
}
|
|
3191
3316
|
|
|
@@ -3349,7 +3474,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3349
3474
|
urlsToProcess.forEach(url => {
|
|
3350
3475
|
allTasks.push({
|
|
3351
3476
|
url,
|
|
3352
|
-
config: site,
|
|
3477
|
+
config: { ...site, _originalUrl: url }, // Preserve original URL for CDP domain checking
|
|
3353
3478
|
taskId: allTasks.length // For tracking
|
|
3354
3479
|
});
|
|
3355
3480
|
});
|
|
@@ -3867,4 +3992,4 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3867
3992
|
if (forceDebug) console.log(formatLogMessage('debug', `About to exit process...`));
|
|
3868
3993
|
process.exit(0);
|
|
3869
3994
|
|
|
3870
|
-
})();
|
|
3995
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
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": {
|