@fanboynz/network-scanner 2.0.4 → 2.0.6
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/lib/browserhealth.js +8 -8
- package/lib/cdp.js +31 -6
- package/lib/cloudflare.js +74 -39
- package/nwss.js +15 -5
- package/package.json +1 -1
package/lib/browserhealth.js
CHANGED
|
@@ -9,7 +9,7 @@ const { formatLogMessage, messageColors } = require('./colorize');
|
|
|
9
9
|
// Window cleanup delay constant
|
|
10
10
|
const WINDOW_CLEANUP_DELAY_MS = 15000;
|
|
11
11
|
// window_clean REALTIME
|
|
12
|
-
const REALTIME_CLEANUP_BUFFER_MS =
|
|
12
|
+
const REALTIME_CLEANUP_BUFFER_MS = 35000; // Additional buffer time after site delay (increased for Cloudflare)
|
|
13
13
|
const REALTIME_CLEANUP_THRESHOLD = 8; // Default number of pages to keep
|
|
14
14
|
const REALTIME_CLEANUP_MIN_PAGES = 3; // Minimum pages before cleanup kicks in
|
|
15
15
|
|
|
@@ -270,14 +270,14 @@ function updatePageUsage(page, isProcessing = false) {
|
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
272
|
* Performs realtime window cleanup - removes oldest pages when threshold is exceeded
|
|
273
|
-
* Waits for site delay +
|
|
273
|
+
* Waits for site delay + buffer before cleanup, with extended buffer for Cloudflare sites
|
|
274
274
|
* @param {import('puppeteer').Browser} browserInstance - Browser instance
|
|
275
275
|
* @param {number} threshold - Maximum number of pages to keep (default: 8)
|
|
276
276
|
* @param {boolean} forceDebug - Debug logging flag
|
|
277
|
-
* @param {number}
|
|
277
|
+
* @param {number} totalDelay - Total delay including site delay and appropriate buffer (default: 4000 + 15000)
|
|
278
278
|
* @returns {Promise<Object>} Cleanup results
|
|
279
279
|
*/
|
|
280
|
-
async function performRealtimeWindowCleanup(browserInstance, threshold = REALTIME_CLEANUP_THRESHOLD, forceDebug,
|
|
280
|
+
async function performRealtimeWindowCleanup(browserInstance, threshold = REALTIME_CLEANUP_THRESHOLD, forceDebug, totalDelay = 19000) {
|
|
281
281
|
try {
|
|
282
282
|
const allPages = await browserInstance.pages();
|
|
283
283
|
|
|
@@ -289,11 +289,11 @@ async function performRealtimeWindowCleanup(browserInstance, threshold = REALTIM
|
|
|
289
289
|
return { success: true, closedCount: 0, totalPages: allPages.length, reason: 'below_threshold' };
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
//
|
|
293
|
-
const cleanupDelay =
|
|
292
|
+
// Use the provided total delay (already includes appropriate buffer)
|
|
293
|
+
const cleanupDelay = totalDelay;
|
|
294
294
|
|
|
295
295
|
if (forceDebug) {
|
|
296
|
-
console.log(formatLogMessage('debug', `[realtime_cleanup] Waiting ${cleanupDelay}ms
|
|
296
|
+
console.log(formatLogMessage('debug', `[realtime_cleanup] Waiting ${cleanupDelay}ms before cleanup (threshold: ${threshold})`));
|
|
297
297
|
}
|
|
298
298
|
await new Promise(resolve => setTimeout(resolve, cleanupDelay));
|
|
299
299
|
|
|
@@ -999,4 +999,4 @@ if (originalPageClose) {
|
|
|
999
999
|
}
|
|
1000
1000
|
return originalPageClose.apply(this, args);
|
|
1001
1001
|
};
|
|
1002
|
-
}
|
|
1002
|
+
}
|
package/lib/cdp.js
CHANGED
|
@@ -49,12 +49,37 @@ async function createPageWithTimeout(browser, timeout = 30000) {
|
|
|
49
49
|
* @returns {Promise<void>}
|
|
50
50
|
*/
|
|
51
51
|
async function setRequestInterceptionWithTimeout(page, timeout = 15000) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
try {
|
|
53
|
+
await Promise.race([
|
|
54
|
+
page.setRequestInterception(true),
|
|
55
|
+
new Promise((_, reject) =>
|
|
56
|
+
setTimeout(() => reject(new Error('Request interception timeout - first attempt')), timeout)
|
|
57
|
+
)
|
|
58
|
+
]);
|
|
59
|
+
} catch (firstError) {
|
|
60
|
+
// Check for immediate critical failures
|
|
61
|
+
if (firstError.message.includes('Target closed') ||
|
|
62
|
+
firstError.message.includes('Session closed') ||
|
|
63
|
+
firstError.message.includes('Browser has been closed')) {
|
|
64
|
+
throw new Error('CRITICAL_BROWSER_ERROR: ' + firstError.message);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Retry with extended timeout
|
|
68
|
+
try {
|
|
69
|
+
await Promise.race([
|
|
70
|
+
page.setRequestInterception(true),
|
|
71
|
+
new Promise((_, reject) =>
|
|
72
|
+
setTimeout(() => reject(new Error('Request interception timeout - retry failed')), timeout * 2)
|
|
73
|
+
)
|
|
74
|
+
]);
|
|
75
|
+
} catch (retryError) {
|
|
76
|
+
if (retryError.message.includes('Network.enable timed out') ||
|
|
77
|
+
retryError.message.includes('ProtocolError')) {
|
|
78
|
+
throw new Error('CRITICAL_NETWORK_ERROR: ' + retryError.message);
|
|
79
|
+
}
|
|
80
|
+
throw retryError;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/**
|
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.2 - Further detached Frame fixes
|
|
3
4
|
* Version: 2.6.1 - timeoutId is not defined & race condition fix
|
|
4
5
|
* Version: 2.6.0 - Memory leak fixes and timeout cleanup
|
|
5
6
|
* Version: 2.5.0 - Fix Frame Lifecycle issue, Timing and Race condition
|
|
@@ -26,11 +27,11 @@ const CLOUDFLARE_MODULE_VERSION = '2.6.1';
|
|
|
26
27
|
* All values tuned for maximum scanning speed while maintaining functionality
|
|
27
28
|
*/
|
|
28
29
|
const TIMEOUTS = {
|
|
29
|
-
PAGE_EVALUATION:
|
|
30
|
-
PAGE_EVALUATION_SAFE:
|
|
30
|
+
PAGE_EVALUATION: 12000, // Standard page evaluation timeout
|
|
31
|
+
PAGE_EVALUATION_SAFE: 12000, // Safe page evaluation with extra buffer
|
|
31
32
|
PHISHING_CLICK: 3000, // Timeout for clicking phishing continue button
|
|
32
33
|
PHISHING_NAVIGATION: 8000, // Wait for navigation after phishing bypass
|
|
33
|
-
JS_CHALLENGE_BUFFER:
|
|
34
|
+
JS_CHALLENGE_BUFFER: 26000, // JS challenge with safety buffer
|
|
34
35
|
TURNSTILE_COMPLETION: 20000, // Turnstile completion check
|
|
35
36
|
TURNSTILE_COMPLETION_BUFFER: 25000, // Turnstile completion with buffer
|
|
36
37
|
CLICK_TIMEOUT: 5000, // Standard click operation timeout
|
|
@@ -50,13 +51,13 @@ const TIMEOUTS = {
|
|
|
50
51
|
|
|
51
52
|
// Fast timeout constants - optimized for speed
|
|
52
53
|
const FAST_TIMEOUTS = {
|
|
53
|
-
QUICK_DETECTION:
|
|
54
|
+
QUICK_DETECTION: 4000, // Fast Cloudflare detection
|
|
54
55
|
PHISHING_WAIT: 1000, // Fast phishing check
|
|
55
56
|
CHALLENGE_WAIT: 500, // Fast challenge detection
|
|
56
57
|
ELEMENT_INTERACTION_DELAY: 250, // Fast element interactions
|
|
57
|
-
SELECTOR_WAIT:
|
|
58
|
+
SELECTOR_WAIT: 3000, // Fast selector waits
|
|
58
59
|
TURNSTILE_OPERATION: 6000, // Fast Turnstile operations
|
|
59
|
-
JS_CHALLENGE:
|
|
60
|
+
JS_CHALLENGE: 19000, // Fast JS challenge completion
|
|
60
61
|
CHALLENGE_SOLVING: 30000, // Fast overall challenge solving
|
|
61
62
|
CHALLENGE_COMPLETION: 8000 // Fast completion check
|
|
62
63
|
};
|
|
@@ -323,19 +324,64 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
323
324
|
let timeoutId = null;
|
|
324
325
|
|
|
325
326
|
try {
|
|
326
|
-
//
|
|
327
|
+
// Multi-layered page state validation
|
|
327
328
|
if (page.isClosed()) {
|
|
328
|
-
throw new Error('Page is closed');
|
|
329
|
+
throw new Error('Page is closed or invalid');
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
// Check if page is still navigating or has valid context
|
|
333
|
+
let currentUrl;
|
|
334
|
+
try {
|
|
335
|
+
currentUrl = await page.url();
|
|
336
|
+
if (!currentUrl || currentUrl === 'about:blank') {
|
|
337
|
+
throw new Error('Page URL is invalid or blank');
|
|
338
|
+
}
|
|
339
|
+
} catch (urlError) {
|
|
340
|
+
throw new Error('Page URL access failed - likely detached');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Quick execution context validation with timeout
|
|
344
|
+
const contextValid = await Promise.race([
|
|
345
|
+
page.evaluate(() => {
|
|
346
|
+
try {
|
|
347
|
+
// Quick context validation
|
|
348
|
+
if (typeof window === 'undefined' || !document) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
// Check if document is ready for interaction
|
|
352
|
+
if (document.readyState === 'uninitialized') {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}),
|
|
360
|
+
new Promise((_, reject) => {
|
|
361
|
+
setTimeout(() => reject(new Error('Context validation timeout')), 3500);
|
|
362
|
+
})
|
|
363
|
+
]).catch(() => false);
|
|
364
|
+
|
|
365
|
+
if (!contextValid) {
|
|
366
|
+
throw new Error('Page execution context is invalid');
|
|
367
|
+
}
|
|
331
368
|
|
|
332
369
|
const result = await Promise.race([
|
|
333
370
|
page.evaluate(() => {
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
371
|
+
// Additional runtime validation inside evaluation
|
|
372
|
+
try {
|
|
373
|
+
if (typeof window === 'undefined' || !document) {
|
|
374
|
+
throw new Error('Execution context invalid during evaluation');
|
|
375
|
+
}
|
|
376
|
+
return func();
|
|
377
|
+
} catch (evalError) {
|
|
378
|
+
// Return error info instead of throwing to avoid unhandled promise rejections
|
|
379
|
+
return {
|
|
380
|
+
__evaluation_error: true,
|
|
381
|
+
message: evalError.message,
|
|
382
|
+
type: 'evaluation_error'
|
|
383
|
+
};
|
|
337
384
|
}
|
|
338
|
-
return (func)();
|
|
339
385
|
}),
|
|
340
386
|
new Promise((_, reject) => {
|
|
341
387
|
timeoutId = setTimeout(() => reject(new Error('Page evaluation timeout')), timeout);
|
|
@@ -346,7 +392,12 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
346
392
|
if (timeoutId) {
|
|
347
393
|
clearTimeout(timeoutId);
|
|
348
394
|
}
|
|
349
|
-
|
|
395
|
+
|
|
396
|
+
// Check if evaluation returned an error
|
|
397
|
+
if (result && result.__evaluation_error) {
|
|
398
|
+
throw new Error(`Evaluation failed: ${result.message}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
350
401
|
if (forceDebug && attempt > 1) {
|
|
351
402
|
console.log(formatLogMessage('cloudflare', `Page evaluation succeeded on attempt ${attempt}`));
|
|
352
403
|
}
|
|
@@ -370,23 +421,19 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
370
421
|
if (forceDebug) {
|
|
371
422
|
console.warn(formatLogMessage('cloudflare', `Detached frame detected on attempt ${attempt}/${maxRetries} - using longer delay`));
|
|
372
423
|
}
|
|
373
|
-
// For detached frames, wait longer
|
|
374
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
424
|
+
// For detached frames, wait longer
|
|
425
|
+
await new Promise(resolve => setTimeout(resolve, 3000)); // Longer delay
|
|
426
|
+
|
|
427
|
+
// For detached frames, only retry once more
|
|
428
|
+
if (attempt >= 2) {
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
375
431
|
continue;
|
|
376
432
|
}
|
|
377
433
|
|
|
378
434
|
// Don't retry if error type is not retryable or if it's the last attempt
|
|
379
435
|
if (!RETRY_CONFIG.retryableErrors.includes(errorType) || attempt === maxRetries) {
|
|
380
|
-
|
|
381
|
-
isChallengePresent: false,
|
|
382
|
-
isPhishingWarning: false,
|
|
383
|
-
isTurnstile: false,
|
|
384
|
-
isJSChallenge: false,
|
|
385
|
-
isChallengeCompleted: false,
|
|
386
|
-
error: error.message,
|
|
387
|
-
errorType: errorType,
|
|
388
|
-
attempts: attempt
|
|
389
|
-
};
|
|
436
|
+
break;
|
|
390
437
|
}
|
|
391
438
|
|
|
392
439
|
// Wait before retrying with exponential backoff
|
|
@@ -394,19 +441,7 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
394
441
|
}
|
|
395
442
|
}
|
|
396
443
|
|
|
397
|
-
//
|
|
398
|
-
if (lastError?.message.includes('detached Frame') || lastError?.message.includes('Attempted to use detached')) {
|
|
399
|
-
return {
|
|
400
|
-
isChallengePresent: false,
|
|
401
|
-
isPhishingWarning: false,
|
|
402
|
-
isTurnstile: false,
|
|
403
|
-
isJSChallenge: false,
|
|
404
|
-
isChallengeCompleted: false,
|
|
405
|
-
error: 'Frame detached - skipping evaluation',
|
|
406
|
-
errorType: ERROR_TYPES.DETACHED_FRAME,
|
|
407
|
-
attempts: maxRetries
|
|
408
|
-
};
|
|
409
|
-
}
|
|
444
|
+
// Return safe defaults if all retries failed
|
|
410
445
|
|
|
411
446
|
return {
|
|
412
447
|
isChallengePresent: false,
|
|
@@ -1845,4 +1880,4 @@ module.exports = {
|
|
|
1845
1880
|
detectChallengeLoop,
|
|
1846
1881
|
// Memory management
|
|
1847
1882
|
cleanup
|
|
1848
|
-
};
|
|
1883
|
+
};
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v2.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v2.0.6 ===
|
|
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
|
|
@@ -127,7 +127,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
|
|
|
127
127
|
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage } = require('./lib/browserhealth');
|
|
128
128
|
|
|
129
129
|
// --- Script Configuration & Constants ---
|
|
130
|
-
const VERSION = '2.0.
|
|
130
|
+
const VERSION = '2.0.6'; // Script version
|
|
131
131
|
|
|
132
132
|
// get startTime
|
|
133
133
|
const startTime = Date.now();
|
|
@@ -1541,10 +1541,17 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1541
1541
|
? siteConfig.window_cleanup_threshold
|
|
1542
1542
|
: REALTIME_CLEANUP_THRESHOLD;
|
|
1543
1543
|
|
|
1544
|
-
//
|
|
1544
|
+
// Calculate appropriate delay based on site configuration
|
|
1545
1545
|
const siteDelay = siteConfig.delay || 4000;
|
|
1546
|
+
const hasCloudflareConfig = siteConfig.cloudflare_bypass || siteConfig.cloudflare_phish;
|
|
1547
|
+
const bufferTime = hasCloudflareConfig ? 23000 : REALTIME_CLEANUP_BUFFER_MS; // 23s for Cloudflare, 15s for normal
|
|
1548
|
+
const totalDelay = siteDelay + bufferTime;
|
|
1546
1549
|
|
|
1547
|
-
|
|
1550
|
+
if (forceDebug && hasCloudflareConfig) {
|
|
1551
|
+
console.log(formatLogMessage('debug', `[realtime_cleanup] Using extended delay for Cloudflare site: ${totalDelay}ms (${siteDelay}ms + ${bufferTime}ms CF buffer)`));
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const realtimeResult = await performRealtimeWindowCleanup(browserInstance, threshold, forceDebug, totalDelay);
|
|
1548
1555
|
if (realtimeResult.success && realtimeResult.closedCount > 0 && forceDebug) {
|
|
1549
1556
|
console.log(formatLogMessage('debug', `[realtime_cleanup] Cleaned ${realtimeResult.closedCount} old pages, ${realtimeResult.remainingPages} remaining`));
|
|
1550
1557
|
}
|
|
@@ -1896,7 +1903,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1896
1903
|
console.log(formatLogMessage('debug', `Request interception enabled successfully for ${currentUrl}`));
|
|
1897
1904
|
}
|
|
1898
1905
|
} catch (networkErr) {
|
|
1899
|
-
if (networkErr.message.includes('
|
|
1906
|
+
if (networkErr.message.includes('CRITICAL_NETWORK_ERROR') ||
|
|
1907
|
+
networkErr.message.includes('CRITICAL_BROWSER_ERROR') ||
|
|
1908
|
+
networkErr.message.includes('ProtocolError') ||
|
|
1909
|
+
networkErr.message.includes('timed out') ||
|
|
1900
1910
|
networkErr.message.includes('Network.enable') ||
|
|
1901
1911
|
networkErr.message.includes('timeout')) {
|
|
1902
1912
|
console.warn(formatLogMessage('warn', `Request interception setup failed for ${currentUrl}: ${networkErr.message} - triggering browser restart`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
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": {
|