@fanboynz/network-scanner 1.0.95 → 1.0.97

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
@@ -151,7 +151,7 @@ Example:
151
151
  | `blocked` | Array | - | Domains or regexes to block during scanning |
152
152
  | `even_blocked` | Boolean | `false` | Add matching rules even if requests are blocked |
153
153
  | `bypass_cache` | Boolean | `false` | Skip all caching for this site's URLs |
154
-
154
+ | `window_cleanup` | Boolean | `false` | Close extra browser windows/tabs after entire URL group completes with 16s delay |
155
155
 
156
156
  ### Redirect Handling Options
157
157
 
@@ -5,6 +5,136 @@
5
5
 
6
6
  const { formatLogMessage, messageColors } = require('./colorize');
7
7
 
8
+
9
+ // Window cleanup delay constant
10
+ const WINDOW_CLEANUP_DELAY_MS = 16000;
11
+
12
+ /**
13
+ * Performs group-level window cleanup after all URLs in a site group complete
14
+ * Closes all extra windows except the main browser window
15
+ * @param {import('puppeteer').Browser} browserInstance - Browser instance
16
+ * @param {string} groupDescription - Description of the group for logging
17
+ * @param {boolean} forceDebug - Debug logging flag
18
+ * @returns {Promise<Object>} Cleanup results
19
+ */
20
+ async function performGroupWindowCleanup(browserInstance, groupDescription, forceDebug) {
21
+ try {
22
+ // Wait before cleanup to allow any final operations to complete
23
+ if (forceDebug) {
24
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Waiting ${WINDOW_CLEANUP_DELAY_MS}ms before cleanup for group: ${groupDescription}`));
25
+ }
26
+ await new Promise(resolve => setTimeout(resolve, WINDOW_CLEANUP_DELAY_MS));
27
+
28
+ const allPages = await browserInstance.pages();
29
+ const mainPage = allPages[0]; // Always keep the first page as main
30
+ const extraPages = allPages.slice(1); // All other pages can be closed
31
+
32
+ if (extraPages.length === 0) {
33
+ if (forceDebug) {
34
+ console.log(formatLogMessage('debug', `[group_window_cleanup] No extra windows to close for group: ${groupDescription}`));
35
+ }
36
+ return { success: true, closedCount: 0, totalPages: allPages.length, estimatedMemoryFreed: 0 };
37
+ }
38
+
39
+ // Estimate memory usage before closing
40
+ let totalEstimatedMemory = 0;
41
+ const pageMemoryEstimates = [];
42
+
43
+ for (let i = 0; i < extraPages.length; i++) {
44
+ const page = extraPages[i];
45
+ let pageMemoryEstimate = 0;
46
+
47
+ try {
48
+ if (!page.isClosed()) {
49
+ // Get page metrics if available
50
+ const metrics = await Promise.race([
51
+ page.metrics(),
52
+ new Promise((_, reject) => setTimeout(() => reject(new Error('metrics timeout')), 1000))
53
+ ]);
54
+
55
+ // Calculate memory estimate based on page metrics
56
+ if (metrics) {
57
+ // Puppeteer metrics provide various memory-related values
58
+ pageMemoryEstimate = (
59
+ (metrics.JSHeapUsedSize || 0) + // JavaScript heap
60
+ (metrics.JSHeapTotalSize || 0) * 0.1 + // Estimated overhead
61
+ (metrics.Nodes || 0) * 100 + // DOM nodes (rough estimate)
62
+ (metrics.JSEventListeners || 0) * 50 // Event listeners
63
+ );
64
+ } else {
65
+ // Fallback: rough estimate based on page complexity
66
+ pageMemoryEstimate = 8 * 1024 * 1024; // 8MB default estimate per page
67
+ }
68
+ }
69
+ } catch (metricsErr) {
70
+ // Fallback estimate if metrics fail
71
+ pageMemoryEstimate = 8 * 1024 * 1024; // 8MB default
72
+ if (forceDebug) {
73
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Could not get metrics for page ${i + 1}, using default estimate: ${metricsErr.message}`));
74
+ }
75
+ }
76
+
77
+ pageMemoryEstimates.push(pageMemoryEstimate);
78
+ totalEstimatedMemory += pageMemoryEstimate;
79
+ }
80
+
81
+ // Close all extra pages since the entire group is complete
82
+ const closePromises = extraPages.map(async (page, index) => {
83
+ try {
84
+ if (!page.isClosed()) {
85
+ await page.close();
86
+ return { success: true, url: page.url() || `page-${index}`, estimatedMemory: pageMemoryEstimates[index] };
87
+ }
88
+ return { success: false, reason: 'already_closed', estimatedMemory: 0 };
89
+ } catch (closeErr) {
90
+ if (forceDebug) {
91
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Failed to close page ${index + 1}: ${closeErr.message}`));
92
+ }
93
+ return { success: false, error: closeErr.message, estimatedMemory: 0 };
94
+ }
95
+ });
96
+
97
+ const closeResults = await Promise.all(closePromises);
98
+ const successfulCloses = closeResults.filter(result => result.success === true).length;
99
+ const actualMemoryFreed = closeResults
100
+ .filter(result => result.success === true)
101
+ .reduce((sum, result) => sum + (result.estimatedMemory || 0), 0);
102
+
103
+ // Format memory for human readability
104
+ const formatMemory = (bytes) => {
105
+ if (bytes >= 1024 * 1024 * 1024) {
106
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
107
+ } else if (bytes >= 1024 * 1024) {
108
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
109
+ } else if (bytes >= 1024) {
110
+ return `${(bytes / 1024).toFixed(1)}KB`;
111
+ } else {
112
+ return `${bytes}B`;
113
+ }
114
+ };
115
+
116
+ if (forceDebug) {
117
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Closed ${successfulCloses}/${extraPages.length} windows for completed group: ${groupDescription} after ${WINDOW_CLEANUP_DELAY_MS}ms delay`));
118
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Estimated memory freed: ${formatMemory(actualMemoryFreed)}`));
119
+ }
120
+
121
+ return {
122
+ success: true,
123
+ closedCount: successfulCloses,
124
+ totalPages: allPages.length,
125
+ mainPageKept: !mainPage.isClosed(),
126
+ delayUsed: WINDOW_CLEANUP_DELAY_MS,
127
+ estimatedMemoryFreed: actualMemoryFreed,
128
+ estimatedMemoryFreedFormatted: formatMemory(actualMemoryFreed)
129
+ };
130
+ } catch (cleanupErr) {
131
+ if (forceDebug) {
132
+ console.log(formatLogMessage('debug', `[group_window_cleanup] Group cleanup failed for ${groupDescription}: ${cleanupErr.message}`));
133
+ }
134
+ return { success: false, error: cleanupErr.message, estimatedMemoryFreed: 0 };
135
+ }
136
+ }
137
+
8
138
  /**
9
139
  * Quick browser responsiveness test for use during page setup
10
140
  * Designed to catch browser degradation between operations
@@ -541,6 +671,7 @@ module.exports = {
541
671
  checkBrowserHealth,
542
672
  checkBrowserMemory,
543
673
  testBrowserConnectivity,
674
+ performGroupWindowCleanup,
544
675
  testNetworkCapability,
545
676
  isQuicklyResponsive,
546
677
  performHealthAssessment,
package/lib/cdp.js CHANGED
@@ -27,6 +27,36 @@
27
27
 
28
28
  const { formatLogMessage } = require('./colorize');
29
29
 
30
+ /**
31
+ * Creates a new page with timeout protection to prevent CDP hangs
32
+ * @param {import('puppeteer').Browser} browser - Browser instance
33
+ * @param {number} timeout - Timeout in milliseconds (default: 30000)
34
+ * @returns {Promise<import('puppeteer').Page>} Page instance
35
+ */
36
+ async function createPageWithTimeout(browser, timeout = 30000) {
37
+ return Promise.race([
38
+ browser.newPage(),
39
+ new Promise((_, reject) =>
40
+ setTimeout(() => reject(new Error('Page creation timeout - browser may be unresponsive')), timeout)
41
+ )
42
+ ]);
43
+ }
44
+
45
+ /**
46
+ * Sets request interception with timeout protection
47
+ * @param {import('puppeteer').Page} page - Page instance
48
+ * @param {number} timeout - Timeout in milliseconds (default: 15000)
49
+ * @returns {Promise<void>}
50
+ */
51
+ async function setRequestInterceptionWithTimeout(page, timeout = 15000) {
52
+ return Promise.race([
53
+ page.setRequestInterception(true),
54
+ new Promise((_, reject) =>
55
+ setTimeout(() => reject(new Error('Request interception setup timeout')), timeout)
56
+ )
57
+ ]);
58
+ }
59
+
30
60
  /**
31
61
  * Creates and manages a CDP session for network monitoring
32
62
  *
@@ -88,8 +118,13 @@ async function createCDPSession(page, currentUrl, options = {}) {
88
118
 
89
119
  try {
90
120
  // Create CDP session using modern Puppeteer 20+ API
91
- // page.target().createCDPSession() was deprecated in 19+ and removed in 20+
92
- cdpSession = await page.createCDPSession();
121
+ // Add timeout protection for CDP session creation
122
+ cdpSession = await Promise.race([
123
+ page.createCDPSession(),
124
+ new Promise((_, reject) =>
125
+ setTimeout(() => reject(new Error('CDP session creation timeout')), 20000)
126
+ )
127
+ ]);
93
128
 
94
129
  // Enable network domain - required for network event monitoring
95
130
  await cdpSession.send('Network.enable');
@@ -302,6 +337,8 @@ async function createEnhancedCDPSession(page, currentUrl, options = {}) {
302
337
  // - Some features may not work in --no-sandbox mode
303
338
  module.exports = {
304
339
  createCDPSession,
340
+ createPageWithTimeout,
341
+ setRequestInterceptionWithTimeout,
305
342
  validateCDPConfig,
306
343
  createEnhancedCDPSession
307
344
  };
package/nwss.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v1.0.95 ===
1
+ // === Network scanner script (nwss.js) v1.0.97 ===
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
@@ -33,7 +33,7 @@ const { createNetToolsHandler, createEnhancedDryRunCallback, validateWhoisAvaila
33
33
  // File compare
34
34
  const { loadComparisonRules, filterUniqueRules } = require('./lib/compare');
35
35
  // CDP functionality
36
- const { createCDPSession } = require('./lib/cdp');
36
+ const { createCDPSession, createPageWithTimeout, setRequestInterceptionWithTimeout } = require('./lib/cdp');
37
37
  // Post-processing cleanup
38
38
  const { processResults } = require('./lib/post-processing');
39
39
  // Colorize various text when used
@@ -64,7 +64,7 @@ const TIMEOUTS = {
64
64
  EMERGENCY_RESTART_DELAY: 2000,
65
65
  BROWSER_STABILIZE_DELAY: 1000,
66
66
  CURL_HANDLER_DELAY: 3000,
67
- PROTOCOL_TIMEOUT: 60000,
67
+ PROTOCOL_TIMEOUT: 120000,
68
68
  REDIRECT_JS_TIMEOUT: 5000
69
69
  };
70
70
 
@@ -122,10 +122,10 @@ function detectPuppeteerVersion() {
122
122
  // Enhanced redirect handling
123
123
  const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/redirect');
124
124
  // Ensure web browser is working correctly
125
- const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive } = require('./lib/browserhealth');
125
+ const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup } = require('./lib/browserhealth');
126
126
 
127
127
  // --- Script Configuration & Constants ---
128
- const VERSION = '1.0.95'; // Script version
128
+ const VERSION = '1.0.97'; // Script version
129
129
 
130
130
  // get startTime
131
131
  const startTime = Date.now();
@@ -571,6 +571,8 @@ Advanced Options:
571
571
  dig_subdomain: true/false Use subdomain for dig lookup instead of root domain (default: false)
572
572
  digRecordType: "A" DNS record type for dig (default: A)
573
573
 
574
+ window_cleanup: true/false Close extra browser windows/tabs after entire URL group completes with 5s delay (default: false)
575
+
574
576
  Referrer Header Options:
575
577
  referrer_headers: "https://google.com" Single referrer URL
576
578
  referrer_headers: ["url1", "url2"] Random selection from array
@@ -1465,7 +1467,7 @@ function setupFrameHandling(page, forceDebug) {
1465
1467
  if (browserInstance.process() && browserInstance.process().killed) {
1466
1468
  throw new Error('Browser process was killed - restart required');
1467
1469
  }
1468
- page = await browserInstance.newPage();
1470
+ page = await createPageWithTimeout(browserInstance, 30000);
1469
1471
 
1470
1472
  // Enhanced page validation for Puppeteer 23.x
1471
1473
  if (!page || page.isClosed()) {
@@ -1779,13 +1781,8 @@ function setupFrameHandling(page, forceDebug) {
1779
1781
 
1780
1782
  // Protected request interception setup with timeout
1781
1783
  try {
1782
- // Test if network operations are responsive before enabling request interception
1783
- await Promise.race([
1784
- page.setRequestInterception(true),
1785
- new Promise((_, reject) =>
1786
- setTimeout(() => reject(new Error('Network.enable timeout')), 10000)
1787
- )
1788
- ]);
1784
+ // Use timeout-protected request interception setup
1785
+ await setRequestInterceptionWithTimeout(page, 15000);
1789
1786
 
1790
1787
  if (forceDebug) {
1791
1788
  console.log(formatLogMessage('debug', `Request interception enabled successfully for ${currentUrl}`));
@@ -1794,13 +1791,13 @@ function setupFrameHandling(page, forceDebug) {
1794
1791
  if (networkErr.message.includes('timed out') ||
1795
1792
  networkErr.message.includes('Network.enable') ||
1796
1793
  networkErr.message.includes('timeout')) {
1797
- console.warn(formatLogMessage('warn', `Network setup failed for ${currentUrl}: ${networkErr.message} - triggering browser restart`));
1794
+ console.warn(formatLogMessage('warn', `Request interception setup failed for ${currentUrl}: ${networkErr.message} - triggering browser restart`));
1798
1795
  return {
1799
1796
  url: currentUrl,
1800
1797
  rules: [],
1801
1798
  success: false,
1802
1799
  needsImmediateRestart: true,
1803
- error: 'Network.enable timeout - browser restart required'
1800
+ error: 'Request interception timeout - browser restart required'
1804
1801
  };
1805
1802
  }
1806
1803
  throw networkErr; // Re-throw other errors
@@ -3235,9 +3232,21 @@ function setupFrameHandling(page, forceDebug) {
3235
3232
  }, 30000); // Check every 30 seconds
3236
3233
 
3237
3234
  // Process URLs in batches to maintain concurrency while allowing browser restarts
3235
+ let siteGroupIndex = 0;
3238
3236
  for (let batchStart = 0; batchStart < totalUrls; batchStart += RESOURCE_CLEANUP_INTERVAL) {
3239
3237
  const batchEnd = Math.min(batchStart + RESOURCE_CLEANUP_INTERVAL, totalUrls);
3240
3238
  const currentBatch = allTasks.slice(batchStart, batchEnd);
3239
+
3240
+
3241
+ // Group tasks by their source site configuration for window cleanup
3242
+ const tasksBySite = new Map();
3243
+ currentBatch.forEach(task => {
3244
+ const siteKey = `site_${sites.indexOf(task.config)}`;
3245
+ if (!tasksBySite.has(siteKey)) {
3246
+ tasksBySite.set(siteKey, []);
3247
+ }
3248
+ tasksBySite.get(siteKey).push(task);
3249
+ });
3241
3250
 
3242
3251
  // IMPROVED: Only check health if we have indicators of problems
3243
3252
  let healthCheck = { shouldRestart: false, reason: null };
@@ -3391,6 +3400,25 @@ function setupFrameHandling(page, forceDebug) {
3391
3400
  }
3392
3401
 
3393
3402
  results.push(...batchResults);
3403
+
3404
+ // Perform group window cleanup for completed sites
3405
+ for (const [siteKey, siteTasks] of tasksBySite) {
3406
+ const siteConfig = siteTasks[0].config; // All tasks in group have same config
3407
+
3408
+ if (siteConfig.window_cleanup === true) {
3409
+ const urlCount = siteTasks.length;
3410
+ const groupDescription = `${urlCount} URLs from site group ${++siteGroupIndex}`;
3411
+
3412
+ try {
3413
+ const groupCleanupResult = await performGroupWindowCleanup(browser, groupDescription, forceDebug);
3414
+ if (!silentMode && groupCleanupResult.success && groupCleanupResult.closedCount > 0) {
3415
+ console.log(`🗑️ Group cleanup: ${groupCleanupResult.closedCount} windows closed after completing ${groupDescription}`);
3416
+ }
3417
+ } catch (groupCleanupErr) {
3418
+ if (forceDebug) console.log(formatLogMessage('debug', `Group window cleanup failed: ${groupCleanupErr.message}`));
3419
+ }
3420
+ }
3421
+ }
3394
3422
 
3395
3423
  processedUrlCount += batchSize;
3396
3424
  urlsSinceLastCleanup += batchSize;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "1.0.95",
3
+ "version": "1.0.97",
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": {