@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 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 |
@@ -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', `CDP logging globally enabled by --cdp, applying to page: ${currentUrl}`));
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 logging enabled for page ${currentUrl} via site-specific 'cdp: true' config.`));
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
- hostnameForLog = new URL(currentUrl).hostname;
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
- // Log the request with context - customize this for your needs
172
- // Format: [cdp][hostname] METHOD url (initiator: type)
173
- console.log(formatLogMessage('debug', `[cdp][${hostnameForLog}] ${method} ${requestUrl} (initiator: ${initiator})`));
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.2';
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.8 ===
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: 120000,
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.8'; // Script version
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 Set();
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
- if (frame.parentFrame()) { // Only handle child frames, not main frame
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: siteConfig.cdp,
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
- await applyAllFingerprintSpoofing(page, siteConfig, forceDebug, currentUrl);
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
- // Clear any stale frame references before reload
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
- await page.evaluate(() => {
3104
- // Force cleanup of any pending frame operations
3105
- if (window.requestIdleCallback) window.requestIdleCallback(() => {});
3106
- });
3107
- } catch (cleanupErr) { /* ignore */ }
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
- // Add timeout protection for setCacheEnabled operations
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
- await page.reload({ waitUntil: 'domcontentloaded', timeout: Math.min(timeout, 12000) });
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
- console.warn(messageColors.warn(`[force reload #${i} failed] ${currentUrl}: ${forceReloadErr.message} - falling back to standard reload`));
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
- // Reduced timeout for faster failure detection
3153
- const standardReloadTimeout = Math.min(timeout, 8000); // Reduced from 15000ms
3154
- await page.reload({ waitUntil: 'domcontentloaded', timeout: standardReloadTimeout });
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
- console.warn(messageColors.warn(`[standard reload #${i} failed] ${currentUrl}: ${standardReloadErr.message}`));
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
- const isPersistentFailure = standardReloadErr.message.includes('Navigation timeout') ||
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
- standardReloadErr.message.includes('Page crashed') ||
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
- await fastTimeout(delayMs);
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.8",
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": {