@fanboynz/network-scanner 2.0.9 → 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.
@@ -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/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.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v2.0.9 ===
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.9'; // Script version
132
+ const VERSION = '2.0.10'; // Script version
133
133
 
134
134
  // get startTime
135
135
  const startTime = Date.now();
@@ -1087,7 +1087,7 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
1087
1087
 
1088
1088
  function setupFrameHandling(page, forceDebug) {
1089
1089
  // Track active frames and clear on navigation to prevent detached frame access
1090
- let activeFrames = new Set();
1090
+ let activeFrames = new Map(); // Use Map to track frame state
1091
1091
 
1092
1092
  // Clear frame tracking on navigation to prevent stale references
1093
1093
  page.on('framenavigated', (frame) => {
@@ -1101,6 +1101,15 @@ function setupFrameHandling(page, forceDebug) {
1101
1101
  page.on('frameattached', async (frame) => {
1102
1102
  // Enhanced frame handling with detached frame protection
1103
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
+
1104
1113
  // Multiple checks for frame validity to prevent detached frame errors
1105
1114
  if (!frame) {
1106
1115
  if (forceDebug) {
@@ -1136,7 +1145,10 @@ function setupFrameHandling(page, forceDebug) {
1136
1145
  return;
1137
1146
  }
1138
1147
 
1139
- 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
1140
1152
  try {
1141
1153
  if (forceDebug) {
1142
1154
  console.log(formatLogMessage('debug', `New frame attached: ${frameUrl || 'about:blank'}`));
@@ -1979,7 +1991,17 @@ function setupFrameHandling(page, forceDebug) {
1979
1991
  }
1980
1992
 
1981
1993
  // --- Apply all fingerprint spoofing (user agent, Brave, fingerprint protection) ---
1982
- 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
+ }
1983
2005
 
1984
2006
  const regexes = Array.isArray(siteConfig.filterRegex)
1985
2007
  ? siteConfig.filterRegex.map(r => new RegExp(r.replace(/^\/(.*)\/$/, '$1')))
@@ -3082,6 +3104,18 @@ function setupFrameHandling(page, forceDebug) {
3082
3104
  // Page finished initial loading - mark as idle
3083
3105
  updatePageUsage(page, false);
3084
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 {
3085
3119
  // Enhanced error handling for redirect timeouts using redirect module
3086
3120
  const timeoutResult = await handleRedirectTimeout(page, currentUrl, err, safeGetDomain, forceDebug, formatLogMessage);
3087
3121
 
@@ -3095,6 +3129,7 @@ function setupFrameHandling(page, forceDebug) {
3095
3129
  throw err;
3096
3130
  }
3097
3131
  }
3132
+ }
3098
3133
 
3099
3134
  if (interactEnabled && !disableInteract) {
3100
3135
  if (forceDebug) console.log(formatLogMessage('debug', `interaction simulation enabled for ${currentUrl}`));
@@ -3141,13 +3176,41 @@ function setupFrameHandling(page, forceDebug) {
3141
3176
  }
3142
3177
 
3143
3178
  for (let i = 1; i <= totalReloads; i++) {
3144
- // 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;
3145
3195
  try {
3146
- await page.evaluate(() => {
3147
- // Force cleanup of any pending frame operations
3148
- if (window.requestIdleCallback) window.requestIdleCallback(() => {});
3149
- });
3150
- } 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);
3151
3214
 
3152
3215
  if (siteConfig.clear_sitedata === true) {
3153
3216
  try {
@@ -3159,8 +3222,11 @@ function setupFrameHandling(page, forceDebug) {
3159
3222
  }
3160
3223
 
3161
3224
  let reloadSuccess = false;
3225
+
3226
+ // Skip force reload if browser seems unhealthy
3227
+ const skipForceReload = i > 2; // After 2 attempts, skip force reload
3162
3228
 
3163
- if (useForceReload && !reloadSuccess) {
3229
+ if (useForceReload && !reloadSuccess && !skipForceReload) {
3164
3230
  // Attempt force reload: disable cache, reload, re-enable cache
3165
3231
  try {
3166
3232
  // Timeout-protected cache disable
@@ -3169,28 +3235,23 @@ function setupFrameHandling(page, forceDebug) {
3169
3235
  new Promise((_, reject) => setTimeout(() => reject(new Error('Cache disable timeout')), 8000))
3170
3236
  ]);
3171
3237
 
3172
- 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) });
3173
3240
 
3174
3241
  // Timeout-protected cache enable
3175
3242
  await Promise.race([
3176
3243
  page.setCacheEnabled(true),
3177
3244
  new Promise((_, reject) => setTimeout(() => reject(new Error('Cache enable timeout')), 8000))
3178
3245
  ]);
3179
-
3180
- await page.reload({ waitUntil: 'domcontentloaded', timeout: Math.min(timeout, 12000) });
3181
-
3182
- await Promise.race([
3183
- page.setCacheEnabled(true),
3184
- new Promise((_, reject) =>
3185
- setTimeout(() => reject(new Error('setCacheEnabled(true) timeout')), 5000)
3186
- )
3187
- ]);
3188
-
3246
+
3189
3247
  reloadSuccess = true;
3190
3248
  if (forceDebug) console.log(formatLogMessage('debug', `Force reload #${i} completed for ${currentUrl}`));
3191
3249
 
3192
3250
  } catch (forceReloadErr) {
3193
- 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
+ }
3194
3255
  reloadSuccess = false; // Ensure we try standard reload
3195
3256
  }
3196
3257
  }
@@ -3198,24 +3259,39 @@ function setupFrameHandling(page, forceDebug) {
3198
3259
  // Fallback to standard reload if force reload failed or wasn't attempted
3199
3260
  if (!reloadSuccess) {
3200
3261
  try {
3201
- // Reduced timeout for faster failure detection
3202
- const standardReloadTimeout = Math.min(timeout, 8000); // Reduced from 15000ms
3203
- 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
+
3204
3278
  if (forceDebug) console.log(formatLogMessage('debug', `Standard reload #${i} completed for ${currentUrl}`));
3205
3279
  } catch (standardReloadErr) {
3206
- 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
+ }
3207
3286
 
3208
3287
  // Check if this is a persistent failure that should skip remaining reloads
3209
- 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') ||
3210
3292
  standardReloadErr.message.includes('net::ERR_') ||
3211
3293
  standardReloadErr.message.includes('Protocol error') ||
3212
- standardReloadErr.message.includes('Page crashed') ||
3213
- // CDP and injection failures
3214
- standardReloadErr.constructor.name === 'ProtocolError' ||
3215
- standardReloadErr.name === 'ProtocolError' ||
3216
- standardReloadErr.message.includes('addScriptToEvaluateOnNewDocument timed out') ||
3217
- standardReloadErr.message.includes('Runtime.callFunctionOn timed out') ||
3218
- standardReloadErr.message.includes('CDP injection timeout');
3294
+ standardReloadErr.message.includes('Page crashed');
3219
3295
 
3220
3296
  if (isPersistentFailure) {
3221
3297
  const remainingReloads = totalReloads - i;
@@ -3225,16 +3301,16 @@ function setupFrameHandling(page, forceDebug) {
3225
3301
  // Break out of reload loop to move to next URL faster
3226
3302
  break;
3227
3303
  }
3304
+ // For navigation timeouts, we can continue - the page might still be partially loaded
3305
+ // Don't break the loop for simple timeouts
3228
3306
  }
3229
- } else {
3230
- // Regular reload
3231
- await page.reload({ waitUntil: 'domcontentloaded', timeout: Math.min(timeout, 15000) });
3232
3307
  }
3233
3308
 
3234
3309
  // Only add delay if we're continuing with more reloads
3235
3310
  if (i < totalReloads) {
3236
-
3237
- await fastTimeout(delayMs);
3311
+ // Reduce delay for problematic sites
3312
+ const adjustedDelay = i > 1 ? Math.min(delayMs, 2000) : delayMs;
3313
+ await fastTimeout(adjustedDelay);
3238
3314
  }
3239
3315
  }
3240
3316
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "2.0.9",
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": {