@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 +1 -1
- package/lib/browserhealth.js +131 -0
- package/lib/cdp.js +39 -2
- package/nwss.js +43 -15
- package/package.json +1 -1
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
|
|
package/lib/browserhealth.js
CHANGED
|
@@ -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
|
-
//
|
|
92
|
-
cdpSession = await
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
1783
|
-
await
|
|
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', `
|
|
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: '
|
|
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.
|
|
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": {
|