@fanboynz/network-scanner 1.0.41 → 1.0.43
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/cloudflare.js +144 -34
- package/lib/domain-cache.js +296 -0
- package/lib/nettools.js +159 -19
- package/lib/output.js +11 -3
- package/lib/searchstring.js +21 -5
- package/nwss.js +36 -2
- package/package.json +1 -1
package/lib/cloudflare.js
CHANGED
|
@@ -9,6 +9,38 @@
|
|
|
9
9
|
*/
|
|
10
10
|
const CLOUDFLARE_MODULE_VERSION = '2.1.0';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Timeout constants for various operations (in milliseconds)
|
|
14
|
+
*/
|
|
15
|
+
const TIMEOUTS = {
|
|
16
|
+
QUICK_DETECTION: 3000, // Quick Cloudflare detection check
|
|
17
|
+
PAGE_EVALUATION: 8000, // Standard page evaluation timeout
|
|
18
|
+
PAGE_EVALUATION_SAFE: 10000, // Safe page evaluation with extra buffer
|
|
19
|
+
CHALLENGE_COMPLETION: 3000, // Challenge completion check
|
|
20
|
+
PHISHING_WAIT: 2000, // Wait before checking phishing warning
|
|
21
|
+
PHISHING_CLICK: 3000, // Timeout for clicking phishing continue button
|
|
22
|
+
PHISHING_NAVIGATION: 8000, // Wait for navigation after phishing bypass
|
|
23
|
+
CHALLENGE_WAIT: 1000, // Wait before checking verification challenge
|
|
24
|
+
CHALLENGE_SOLVING: 20000, // Overall challenge solving timeout
|
|
25
|
+
JS_CHALLENGE: 15000, // JS challenge completion wait
|
|
26
|
+
JS_CHALLENGE_BUFFER: 18000, // JS challenge with safety buffer
|
|
27
|
+
TURNSTILE_OPERATION: 8000, // Turnstile iframe operations
|
|
28
|
+
TURNSTILE_COMPLETION: 12000, // Turnstile completion check
|
|
29
|
+
TURNSTILE_COMPLETION_BUFFER: 15000, // Turnstile completion with buffer
|
|
30
|
+
SELECTOR_WAIT: 2000, // Wait for selector to appear
|
|
31
|
+
SELECTOR_WAIT_BUFFER: 2500, // Selector wait with safety buffer
|
|
32
|
+
ELEMENT_INTERACTION_DELAY: 500, // Delay before element interactions
|
|
33
|
+
CLICK_TIMEOUT: 5000, // Standard click operation timeout
|
|
34
|
+
CLICK_TIMEOUT_BUFFER: 1000, // Click timeout safety buffer
|
|
35
|
+
NAVIGATION_TIMEOUT: 15000, // Standard navigation timeout
|
|
36
|
+
NAVIGATION_TIMEOUT_BUFFER: 2000, // Navigation timeout safety buffer
|
|
37
|
+
FALLBACK_TIMEOUT: 5000, // Fallback timeout for failed operations
|
|
38
|
+
ADAPTIVE_TIMEOUT_WITH_INDICATORS: 25000, // Adaptive timeout when indicators found + explicit config
|
|
39
|
+
ADAPTIVE_TIMEOUT_WITHOUT_INDICATORS: 20000, // Adaptive timeout with explicit config only
|
|
40
|
+
ADAPTIVE_TIMEOUT_AUTO_WITH_INDICATORS: 15000, // Adaptive timeout for auto-detected with indicators
|
|
41
|
+
ADAPTIVE_TIMEOUT_AUTO_WITHOUT_INDICATORS: 10000 // Adaptive timeout for auto-detected without indicators
|
|
42
|
+
};
|
|
43
|
+
|
|
12
44
|
/**
|
|
13
45
|
* Gets module version information
|
|
14
46
|
* @returns {object} Version information object
|
|
@@ -29,26 +61,26 @@ async function waitForTimeout(page, timeout) {
|
|
|
29
61
|
if (typeof page.waitForTimeout === 'function') {
|
|
30
62
|
await Promise.race([
|
|
31
63
|
page.waitForTimeout(timeout),
|
|
32
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('waitForTimeout exceeded')), timeout +
|
|
64
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('waitForTimeout exceeded')), timeout + TIMEOUTS.FALLBACK_TIMEOUT))
|
|
33
65
|
]);
|
|
34
66
|
} else if (typeof page.waitFor === 'function') {
|
|
35
67
|
await Promise.race([
|
|
36
68
|
page.waitFor(timeout),
|
|
37
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('waitFor exceeded')), timeout +
|
|
69
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('waitFor exceeded')), timeout + TIMEOUTS.FALLBACK_TIMEOUT))
|
|
38
70
|
]);
|
|
39
71
|
} else {
|
|
40
72
|
await new Promise(resolve => setTimeout(resolve, timeout));
|
|
41
73
|
}
|
|
42
74
|
} catch (error) {
|
|
43
75
|
// If all else fails, use setTimeout
|
|
44
|
-
await new Promise(resolve => setTimeout(resolve, Math.min(timeout,
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(timeout, TIMEOUTS.FALLBACK_TIMEOUT)));
|
|
45
77
|
}
|
|
46
78
|
}
|
|
47
79
|
|
|
48
80
|
/**
|
|
49
81
|
* Safe page evaluation with timeout protection
|
|
50
82
|
*/
|
|
51
|
-
async function safePageEvaluate(page, func, timeout =
|
|
83
|
+
async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_SAFE) {
|
|
52
84
|
try {
|
|
53
85
|
return await Promise.race([
|
|
54
86
|
page.evaluate(func),
|
|
@@ -72,12 +104,12 @@ async function safePageEvaluate(page, func, timeout = 10000) {
|
|
|
72
104
|
/**
|
|
73
105
|
* Safe element clicking with timeout protection
|
|
74
106
|
*/
|
|
75
|
-
async function safeClick(page, selector, timeout =
|
|
107
|
+
async function safeClick(page, selector, timeout = TIMEOUTS.CLICK_TIMEOUT) {
|
|
76
108
|
try {
|
|
77
109
|
return await Promise.race([
|
|
78
110
|
page.click(selector, { timeout: timeout }),
|
|
79
111
|
new Promise((_, reject) =>
|
|
80
|
-
setTimeout(() => reject(new Error('Click timeout')), timeout +
|
|
112
|
+
setTimeout(() => reject(new Error('Click timeout')), timeout + TIMEOUTS.CLICK_TIMEOUT_BUFFER)
|
|
81
113
|
)
|
|
82
114
|
]);
|
|
83
115
|
} catch (error) {
|
|
@@ -88,12 +120,12 @@ async function safeClick(page, selector, timeout = 5000) {
|
|
|
88
120
|
/**
|
|
89
121
|
* Safe navigation waiting with timeout protection
|
|
90
122
|
*/
|
|
91
|
-
async function safeWaitForNavigation(page, timeout =
|
|
123
|
+
async function safeWaitForNavigation(page, timeout = TIMEOUTS.NAVIGATION_TIMEOUT) {
|
|
92
124
|
try {
|
|
93
125
|
return await Promise.race([
|
|
94
126
|
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: timeout }),
|
|
95
127
|
new Promise((_, reject) =>
|
|
96
|
-
setTimeout(() => reject(new Error('Navigation timeout')), timeout +
|
|
128
|
+
setTimeout(() => reject(new Error('Navigation timeout')), timeout + TIMEOUTS.NAVIGATION_TIMEOUT_BUFFER)
|
|
97
129
|
)
|
|
98
130
|
]);
|
|
99
131
|
} catch (error) {
|
|
@@ -141,7 +173,7 @@ async function quickCloudflareDetection(page, forceDebug = false) {
|
|
|
141
173
|
url,
|
|
142
174
|
bodySnippet: bodyText.substring(0, 200)
|
|
143
175
|
};
|
|
144
|
-
},
|
|
176
|
+
}, TIMEOUTS.QUICK_DETECTION);
|
|
145
177
|
|
|
146
178
|
if (forceDebug && quickCheck.hasIndicators) {
|
|
147
179
|
console.log(`[debug][cloudflare] Quick detection found Cloudflare indicators on ${quickCheck.url}`);
|
|
@@ -232,7 +264,7 @@ async function analyzeCloudflareChallenge(page) {
|
|
|
232
264
|
url: window.location.href,
|
|
233
265
|
bodySnippet: bodyText.substring(0, 200)
|
|
234
266
|
};
|
|
235
|
-
},
|
|
267
|
+
}, TIMEOUTS.PAGE_EVALUATION);
|
|
236
268
|
} catch (error) {
|
|
237
269
|
return {
|
|
238
270
|
isChallengePresent: false,
|
|
@@ -247,6 +279,17 @@ async function analyzeCloudflareChallenge(page) {
|
|
|
247
279
|
|
|
248
280
|
/**
|
|
249
281
|
* Handles Cloudflare phishing warnings with timeout protection and enhanced debug logging
|
|
282
|
+
*
|
|
283
|
+
* @param {Object} page - Puppeteer page instance
|
|
284
|
+
* @param {string} currentUrl - URL being processed
|
|
285
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
286
|
+
* @returns {Promise<Object>} Phishing warning result:
|
|
287
|
+
* {
|
|
288
|
+
* success: boolean, // True if no warning found OR successfully bypassed
|
|
289
|
+
* attempted: boolean, // True if warning was detected and bypass attempted
|
|
290
|
+
* error: string|null, // Error message if bypass failed
|
|
291
|
+
* details: object|null // Analysis details from analyzeCloudflareChallenge()
|
|
292
|
+
* }
|
|
250
293
|
*/
|
|
251
294
|
async function handlePhishingWarning(page, currentUrl, forceDebug = false) {
|
|
252
295
|
const result = {
|
|
@@ -260,7 +303,7 @@ async function handlePhishingWarning(page, currentUrl, forceDebug = false) {
|
|
|
260
303
|
if (forceDebug) console.log(`[debug][cloudflare] Checking for phishing warning on ${currentUrl}`);
|
|
261
304
|
|
|
262
305
|
// Shorter wait with timeout protection
|
|
263
|
-
await waitForTimeout(page,
|
|
306
|
+
await waitForTimeout(page, TIMEOUTS.PHISHING_WAIT);
|
|
264
307
|
|
|
265
308
|
const challengeInfo = await analyzeCloudflareChallenge(page);
|
|
266
309
|
|
|
@@ -277,8 +320,8 @@ async function handlePhishingWarning(page, currentUrl, forceDebug = false) {
|
|
|
277
320
|
|
|
278
321
|
try {
|
|
279
322
|
// Use safe click with shorter timeout
|
|
280
|
-
await safeClick(page, 'a[href*="continue"]',
|
|
281
|
-
await safeWaitForNavigation(page,
|
|
323
|
+
await safeClick(page, 'a[href*="continue"]', TIMEOUTS.PHISHING_CLICK);
|
|
324
|
+
await safeWaitForNavigation(page, TIMEOUTS.PHISHING_NAVIGATION);
|
|
282
325
|
|
|
283
326
|
result.success = true;
|
|
284
327
|
if (forceDebug) console.log(`[debug][cloudflare] Successfully bypassed phishing warning for ${currentUrl}`);
|
|
@@ -300,6 +343,19 @@ async function handlePhishingWarning(page, currentUrl, forceDebug = false) {
|
|
|
300
343
|
|
|
301
344
|
/**
|
|
302
345
|
* Attempts to solve Cloudflare challenges with timeout protection and enhanced debug logging
|
|
346
|
+
*
|
|
347
|
+
* @param {Object} page - Puppeteer page instance
|
|
348
|
+
* @param {string} currentUrl - URL being processed
|
|
349
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
350
|
+
* @returns {Promise<Object>} Challenge verification result:
|
|
351
|
+
* {
|
|
352
|
+
* success: boolean, // True if no challenge found OR successfully solved
|
|
353
|
+
* attempted: boolean, // True if challenge was detected and solving attempted
|
|
354
|
+
* error: string|null, // Error message if solving failed
|
|
355
|
+
* requiresHuman: boolean, // True if CAPTCHA detected (requires manual intervention)
|
|
356
|
+
* method: string|null, // Method that succeeded: 'js_challenge_wait', 'turnstile', 'legacy_checkbox'
|
|
357
|
+
* details: object|null // Analysis details from analyzeCloudflareChallenge()
|
|
358
|
+
* }
|
|
303
359
|
*/
|
|
304
360
|
async function handleVerificationChallenge(page, currentUrl, forceDebug = false) {
|
|
305
361
|
const result = {
|
|
@@ -315,7 +371,7 @@ async function handleVerificationChallenge(page, currentUrl, forceDebug = false)
|
|
|
315
371
|
if (forceDebug) console.log(`[debug][cloudflare] Checking for verification challenge on ${currentUrl}`);
|
|
316
372
|
|
|
317
373
|
// Reduced wait time
|
|
318
|
-
await waitForTimeout(page,
|
|
374
|
+
await waitForTimeout(page, TIMEOUTS.CHALLENGE_WAIT);
|
|
319
375
|
|
|
320
376
|
const challengeInfo = await analyzeCloudflareChallenge(page);
|
|
321
377
|
result.details = challengeInfo;
|
|
@@ -381,7 +437,7 @@ async function attemptChallengeSolveWithTimeout(page, currentUrl, challengeInfo,
|
|
|
381
437
|
return await Promise.race([
|
|
382
438
|
attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug),
|
|
383
439
|
new Promise((_, reject) =>
|
|
384
|
-
setTimeout(() => reject(new Error('Challenge solving timeout')),
|
|
440
|
+
setTimeout(() => reject(new Error('Challenge solving timeout')), TIMEOUTS.CHALLENGE_SOLVING)
|
|
385
441
|
)
|
|
386
442
|
]);
|
|
387
443
|
} catch (error) {
|
|
@@ -480,10 +536,10 @@ async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
|
480
536
|
!document.querySelector('.cf-challenge-running') &&
|
|
481
537
|
!document.querySelector('[data-cf-challenge]');
|
|
482
538
|
},
|
|
483
|
-
{ timeout:
|
|
539
|
+
{ timeout: TIMEOUTS.JS_CHALLENGE }
|
|
484
540
|
),
|
|
485
541
|
new Promise((_, reject) =>
|
|
486
|
-
setTimeout(() => reject(new Error('JS challenge timeout')),
|
|
542
|
+
setTimeout(() => reject(new Error('JS challenge timeout')), TIMEOUTS.JS_CHALLENGE_BUFFER)
|
|
487
543
|
)
|
|
488
544
|
]);
|
|
489
545
|
|
|
@@ -508,7 +564,7 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
508
564
|
|
|
509
565
|
try {
|
|
510
566
|
// Reduced timeout for Turnstile operations
|
|
511
|
-
const turnstileTimeout =
|
|
567
|
+
const turnstileTimeout = TIMEOUTS.TURNSTILE_OPERATION;
|
|
512
568
|
|
|
513
569
|
const turnstileSelectors = [
|
|
514
570
|
'iframe[src*="challenges.cloudflare.com"]',
|
|
@@ -520,8 +576,8 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
520
576
|
for (const selector of turnstileSelectors) {
|
|
521
577
|
try {
|
|
522
578
|
await Promise.race([
|
|
523
|
-
page.waitForSelector(selector, { timeout:
|
|
524
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Selector timeout')),
|
|
579
|
+
page.waitForSelector(selector, { timeout: TIMEOUTS.SELECTOR_WAIT }),
|
|
580
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Selector timeout')), TIMEOUTS.SELECTOR_WAIT_BUFFER))
|
|
525
581
|
]);
|
|
526
582
|
|
|
527
583
|
const frames = await page.frames();
|
|
@@ -554,11 +610,11 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
554
610
|
for (const selector of checkboxSelectors) {
|
|
555
611
|
try {
|
|
556
612
|
await Promise.race([
|
|
557
|
-
turnstileFrame.waitForSelector(selector, { timeout:
|
|
558
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Checkbox timeout')),
|
|
613
|
+
turnstileFrame.waitForSelector(selector, { timeout: TIMEOUTS.SELECTOR_WAIT }),
|
|
614
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Checkbox timeout')), TIMEOUTS.SELECTOR_WAIT_BUFFER))
|
|
559
615
|
]);
|
|
560
616
|
|
|
561
|
-
await waitForTimeout(page,
|
|
617
|
+
await waitForTimeout(page, TIMEOUTS.ELEMENT_INTERACTION_DELAY);
|
|
562
618
|
await turnstileFrame.click(selector);
|
|
563
619
|
|
|
564
620
|
if (forceDebug) console.log(`[debug][cloudflare] Clicked Turnstile checkbox: ${selector}`);
|
|
@@ -576,9 +632,9 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
576
632
|
const responseInput = document.querySelector('input[name="cf-turnstile-response"]');
|
|
577
633
|
return responseInput && responseInput.value && responseInput.value.length > 0;
|
|
578
634
|
},
|
|
579
|
-
{ timeout:
|
|
635
|
+
{ timeout: TIMEOUTS.TURNSTILE_COMPLETION }
|
|
580
636
|
),
|
|
581
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Turnstile completion timeout')),
|
|
637
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Turnstile completion timeout')), TIMEOUTS.TURNSTILE_COMPLETION_BUFFER))
|
|
582
638
|
]);
|
|
583
639
|
|
|
584
640
|
if (forceDebug) console.log(`[debug][cloudflare] Turnstile response token generated successfully`);
|
|
@@ -596,11 +652,11 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
596
652
|
for (const selector of containerSelectors) {
|
|
597
653
|
try {
|
|
598
654
|
await Promise.race([
|
|
599
|
-
page.waitForSelector(selector, { timeout:
|
|
600
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Container timeout')),
|
|
655
|
+
page.waitForSelector(selector, { timeout: TIMEOUTS.SELECTOR_WAIT }),
|
|
656
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Container timeout')), TIMEOUTS.SELECTOR_WAIT_BUFFER))
|
|
601
657
|
]);
|
|
602
658
|
|
|
603
|
-
await waitForTimeout(page,
|
|
659
|
+
await waitForTimeout(page, TIMEOUTS.ELEMENT_INTERACTION_DELAY);
|
|
604
660
|
await page.click(selector);
|
|
605
661
|
|
|
606
662
|
if (forceDebug) console.log(`[debug][cloudflare] Clicked Turnstile container: ${selector}`);
|
|
@@ -652,8 +708,8 @@ async function handleLegacyCheckbox(page, forceDebug = false) {
|
|
|
652
708
|
for (const selector of legacySelectors) {
|
|
653
709
|
try {
|
|
654
710
|
await Promise.race([
|
|
655
|
-
page.waitForSelector(selector, { timeout:
|
|
656
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Legacy selector timeout')),
|
|
711
|
+
page.waitForSelector(selector, { timeout: TIMEOUTS.SELECTOR_WAIT }),
|
|
712
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Legacy selector timeout')), TIMEOUTS.SELECTOR_WAIT_BUFFER))
|
|
657
713
|
]);
|
|
658
714
|
|
|
659
715
|
const checkbox = await page.$(selector);
|
|
@@ -705,7 +761,7 @@ async function checkChallengeCompletion(page) {
|
|
|
705
761
|
return (noChallengeRunning && noChallengeContainer && noChallengePage) ||
|
|
706
762
|
hasClearanceCookie ||
|
|
707
763
|
hasTurnstileResponse;
|
|
708
|
-
},
|
|
764
|
+
}, TIMEOUTS.CHALLENGE_COMPLETION);
|
|
709
765
|
|
|
710
766
|
return { isCompleted };
|
|
711
767
|
} catch (error) {
|
|
@@ -715,6 +771,43 @@ async function checkChallengeCompletion(page) {
|
|
|
715
771
|
|
|
716
772
|
/**
|
|
717
773
|
* Main function to handle all Cloudflare challenges with smart detection and adaptive timeouts
|
|
774
|
+
*
|
|
775
|
+
* @param {Object} page - Puppeteer page instance
|
|
776
|
+
* @param {string} currentUrl - URL being processed
|
|
777
|
+
* @param {Object} siteConfig - Configuration object with cloudflare_phish and cloudflare_bypass flags
|
|
778
|
+
* @param {boolean} forceDebug - Enable debug logging
|
|
779
|
+
*
|
|
780
|
+
* @returns {Promise<Object>} Result object with the following structure:
|
|
781
|
+
* {
|
|
782
|
+
* phishingWarning: {
|
|
783
|
+
* attempted: boolean, // Whether phishing bypass was attempted
|
|
784
|
+
* success: boolean, // Whether bypass succeeded (true if no warning or successfully bypassed)
|
|
785
|
+
* error: string|null, // Error message if bypass failed
|
|
786
|
+
* details: object|null // Challenge analysis details from analyzeCloudflareChallenge()
|
|
787
|
+
* },
|
|
788
|
+
* verificationChallenge: {
|
|
789
|
+
* attempted: boolean, // Whether challenge bypass was attempted
|
|
790
|
+
* success: boolean, // Whether challenge was solved (true if no challenge or successfully solved)
|
|
791
|
+
* error: string|null, // Error message if solving failed
|
|
792
|
+
* requiresHuman: boolean, // True if CAPTCHA detected - requires manual intervention
|
|
793
|
+
* method: string|null, // Successful method used: 'js_challenge_wait', 'turnstile', 'legacy_checkbox'
|
|
794
|
+
* details: object|null // Challenge analysis details from analyzeCloudflareChallenge()
|
|
795
|
+
* },
|
|
796
|
+
* overallSuccess: boolean, // True if no critical failures occurred (challenges may be unsolved but didn't error)
|
|
797
|
+
* errors: string[], // Array of error messages from failed operations
|
|
798
|
+
* skippedNoIndicators: boolean, // True if processing was skipped due to no Cloudflare indicators detected
|
|
799
|
+
* timedOut: boolean // True if adaptive timeout was reached (processing continued anyway)
|
|
800
|
+
* }
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* const result = await handleCloudflareProtection(page, url, {cloudflare_bypass: true}, false);
|
|
804
|
+
* if (result.verificationChallenge.requiresHuman) {
|
|
805
|
+
* console.log('Manual CAPTCHA solving required');
|
|
806
|
+
* } else if (!result.overallSuccess) {
|
|
807
|
+
* console.error('Critical errors:', result.errors);
|
|
808
|
+
* } else if (result.verificationChallenge.attempted && result.verificationChallenge.success) {
|
|
809
|
+
* console.log(`Challenge solved using: ${result.verificationChallenge.method}`);
|
|
810
|
+
* }
|
|
718
811
|
*/
|
|
719
812
|
async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDebug = false) {
|
|
720
813
|
if (forceDebug) {
|
|
@@ -722,7 +815,10 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
|
|
|
722
815
|
}
|
|
723
816
|
// Quick detection first - exit early if no Cloudflare detected and no explicit config
|
|
724
817
|
const quickDetection = await quickCloudflareDetection(page, forceDebug);
|
|
725
|
-
|
|
818
|
+
|
|
819
|
+
// Early return structure when no Cloudflare indicators found
|
|
820
|
+
// Sets attempted: false, success: true for both protection types
|
|
821
|
+
|
|
726
822
|
// Only proceed if we have indicators OR explicit config enables Cloudflare handling
|
|
727
823
|
if (!quickDetection.hasIndicators && !siteConfig.cloudflare_phish && !siteConfig.cloudflare_bypass) {
|
|
728
824
|
if (forceDebug) console.log(`[debug][cloudflare] No Cloudflare indicators found and no explicit config, skipping protection handling for ${currentUrl}`);
|
|
@@ -736,6 +832,9 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
|
|
|
736
832
|
};
|
|
737
833
|
}
|
|
738
834
|
|
|
835
|
+
// Standard return structure for all processing paths
|
|
836
|
+
// Individual handlers update their respective sections
|
|
837
|
+
// overallSuccess becomes false if any critical errors occur
|
|
739
838
|
const result = {
|
|
740
839
|
phishingWarning: { attempted: false, success: false },
|
|
741
840
|
verificationChallenge: { attempted: false, success: false },
|
|
@@ -748,10 +847,10 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
|
|
|
748
847
|
let adaptiveTimeout;
|
|
749
848
|
if (siteConfig.cloudflare_phish || siteConfig.cloudflare_bypass) {
|
|
750
849
|
// Explicit config - give more time
|
|
751
|
-
adaptiveTimeout = quickDetection.hasIndicators ?
|
|
850
|
+
adaptiveTimeout = quickDetection.hasIndicators ? TIMEOUTS.ADAPTIVE_TIMEOUT_WITH_INDICATORS : TIMEOUTS.ADAPTIVE_TIMEOUT_WITHOUT_INDICATORS;
|
|
752
851
|
} else {
|
|
753
852
|
// Auto-detected only - shorter timeout
|
|
754
|
-
adaptiveTimeout = quickDetection.hasIndicators ?
|
|
853
|
+
adaptiveTimeout = quickDetection.hasIndicators ? TIMEOUTS.ADAPTIVE_TIMEOUT_AUTO_WITH_INDICATORS : TIMEOUTS.ADAPTIVE_TIMEOUT_AUTO_WITHOUT_INDICATORS;
|
|
755
854
|
}
|
|
756
855
|
|
|
757
856
|
if (forceDebug) {
|
|
@@ -783,6 +882,12 @@ async function handleCloudflareProtection(page, currentUrl, siteConfig, forceDeb
|
|
|
783
882
|
|
|
784
883
|
/**
|
|
785
884
|
* Performs the actual Cloudflare handling with enhanced debug logging
|
|
885
|
+
*
|
|
886
|
+
* @param {Object} page - Puppeteer page instance
|
|
887
|
+
* @param {string} currentUrl - URL being processed
|
|
888
|
+
* @param {Object} siteConfig - Configuration flags
|
|
889
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
890
|
+
* @returns {Promise<Object>} Same structure as handleCloudflareProtection()
|
|
786
891
|
*/
|
|
787
892
|
async function performCloudflareHandling(page, currentUrl, siteConfig, forceDebug = false) {
|
|
788
893
|
const result = {
|
|
@@ -794,6 +899,8 @@ async function performCloudflareHandling(page, currentUrl, siteConfig, forceDebu
|
|
|
794
899
|
|
|
795
900
|
if (forceDebug) console.log(`[debug][cloudflare] Starting Cloudflare protection handling for ${currentUrl}`);
|
|
796
901
|
|
|
902
|
+
// Handle phishing warnings first - updates result.phishingWarning
|
|
903
|
+
// Only runs if siteConfig.cloudflare_phish === true
|
|
797
904
|
// Handle phishing warnings if enabled
|
|
798
905
|
if (siteConfig.cloudflare_phish === true) {
|
|
799
906
|
if (forceDebug) console.log(`[debug][cloudflare] Phishing warning bypass enabled for ${currentUrl}`);
|
|
@@ -812,6 +919,9 @@ async function performCloudflareHandling(page, currentUrl, siteConfig, forceDebu
|
|
|
812
919
|
console.log(`[debug][cloudflare] Phishing warning bypass disabled for ${currentUrl}`);
|
|
813
920
|
}
|
|
814
921
|
|
|
922
|
+
// Handle verification challenges second - updates result.verificationChallenge
|
|
923
|
+
// Only runs if siteConfig.cloudflare_bypass === true
|
|
924
|
+
// Sets requiresHuman: true if CAPTCHA detected (no bypass attempted)
|
|
815
925
|
// Handle verification challenges if enabled
|
|
816
926
|
if (siteConfig.cloudflare_bypass === true) {
|
|
817
927
|
if (forceDebug) console.log(`[debug][cloudflare] Challenge bypass enabled for ${currentUrl}`);
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Cache Module - Tracks detected domains to prevent duplicate processing
|
|
3
|
+
* Provides performance optimization by skipping already detected domains
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { formatLogMessage } = require('./colorize');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Domain detection cache class for tracking processed domains
|
|
10
|
+
*/
|
|
11
|
+
class DomainCache {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.cache = new Set();
|
|
14
|
+
this.stats = {
|
|
15
|
+
totalDetected: 0,
|
|
16
|
+
totalSkipped: 0,
|
|
17
|
+
cacheHits: 0,
|
|
18
|
+
cacheMisses: 0
|
|
19
|
+
};
|
|
20
|
+
this.options = {
|
|
21
|
+
enableLogging: options.enableLogging || false,
|
|
22
|
+
logPrefix: options.logPrefix || '[domain-cache]',
|
|
23
|
+
maxCacheSize: options.maxCacheSize || 10000 // Prevent memory leaks
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a domain was already detected in a previous scan
|
|
29
|
+
* @param {string} domain - Domain to check
|
|
30
|
+
* @returns {boolean} True if domain was already detected
|
|
31
|
+
*/
|
|
32
|
+
isDomainAlreadyDetected(domain) {
|
|
33
|
+
if (!domain || typeof domain !== 'string') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isDetected = this.cache.has(domain);
|
|
38
|
+
|
|
39
|
+
if (isDetected) {
|
|
40
|
+
this.stats.totalSkipped++;
|
|
41
|
+
this.stats.cacheHits++;
|
|
42
|
+
|
|
43
|
+
if (this.options.enableLogging) {
|
|
44
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Cache HIT: ${domain} (skipped)`));
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
this.stats.cacheMisses++;
|
|
48
|
+
|
|
49
|
+
if (this.options.enableLogging) {
|
|
50
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Cache MISS: ${domain} (processing)`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return isDetected;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Mark a domain as detected for future reference
|
|
59
|
+
* @param {string} domain - Domain to mark as detected
|
|
60
|
+
*/
|
|
61
|
+
markDomainAsDetected(domain) {
|
|
62
|
+
if (!domain || typeof domain !== 'string') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Prevent cache from growing too large
|
|
67
|
+
if (this.cache.size >= this.options.maxCacheSize) {
|
|
68
|
+
this.clearOldestEntries(Math.floor(this.options.maxCacheSize * 0.1)); // Remove 10% of entries
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const wasNew = !this.cache.has(domain);
|
|
72
|
+
this.cache.add(domain);
|
|
73
|
+
|
|
74
|
+
if (wasNew) {
|
|
75
|
+
this.stats.totalDetected++;
|
|
76
|
+
|
|
77
|
+
if (this.options.enableLogging) {
|
|
78
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Marked as detected: ${domain} (cache size: ${this.cache.size})`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return wasNew;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clear oldest entries from cache (basic LRU simulation)
|
|
87
|
+
* Note: Set doesn't maintain insertion order in all Node.js versions,
|
|
88
|
+
* so this is a simple implementation that clears a portion of the cache
|
|
89
|
+
* @param {number} count - Number of entries to remove
|
|
90
|
+
*/
|
|
91
|
+
clearOldestEntries(count) {
|
|
92
|
+
if (count <= 0) return;
|
|
93
|
+
|
|
94
|
+
const entries = Array.from(this.cache);
|
|
95
|
+
const toRemove = entries.slice(0, count);
|
|
96
|
+
|
|
97
|
+
toRemove.forEach(domain => this.cache.delete(domain));
|
|
98
|
+
|
|
99
|
+
if (this.options.enableLogging) {
|
|
100
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Cleared ${toRemove.length} old entries, cache size now: ${this.cache.size}`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get cache statistics
|
|
106
|
+
* @returns {object} Cache statistics
|
|
107
|
+
*/
|
|
108
|
+
getStats() {
|
|
109
|
+
return {
|
|
110
|
+
...this.stats,
|
|
111
|
+
cacheSize: this.cache.size,
|
|
112
|
+
hitRate: this.stats.cacheHits > 0 ?
|
|
113
|
+
(this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses) * 100).toFixed(2) + '%' :
|
|
114
|
+
'0%'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clear all cached domains
|
|
120
|
+
*/
|
|
121
|
+
clear() {
|
|
122
|
+
const previousSize = this.cache.size;
|
|
123
|
+
this.cache.clear();
|
|
124
|
+
this.stats = {
|
|
125
|
+
totalDetected: 0,
|
|
126
|
+
totalSkipped: 0,
|
|
127
|
+
cacheHits: 0,
|
|
128
|
+
cacheMisses: 0
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (this.options.enableLogging) {
|
|
132
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Cache cleared (${previousSize} entries removed)`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all cached domains (for debugging)
|
|
138
|
+
* @returns {Array<string>} Array of cached domains
|
|
139
|
+
*/
|
|
140
|
+
getAllCachedDomains() {
|
|
141
|
+
return Array.from(this.cache);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if cache contains a specific domain (without updating stats)
|
|
146
|
+
* @param {string} domain - Domain to check
|
|
147
|
+
* @returns {boolean} True if domain exists in cache
|
|
148
|
+
*/
|
|
149
|
+
has(domain) {
|
|
150
|
+
return this.cache.has(domain);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Remove a specific domain from cache
|
|
155
|
+
* @param {string} domain - Domain to remove
|
|
156
|
+
* @returns {boolean} True if domain was removed, false if it wasn't in cache
|
|
157
|
+
*/
|
|
158
|
+
removeDomain(domain) {
|
|
159
|
+
const wasRemoved = this.cache.delete(domain);
|
|
160
|
+
|
|
161
|
+
if (wasRemoved && this.options.enableLogging) {
|
|
162
|
+
console.log(formatLogMessage('debug', `${this.options.logPrefix} Removed from cache: ${domain}`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return wasRemoved;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Add multiple domains to cache at once
|
|
170
|
+
* @param {Array<string>} domains - Array of domains to add
|
|
171
|
+
* @returns {number} Number of domains actually added (excludes duplicates)
|
|
172
|
+
*/
|
|
173
|
+
markMultipleDomainsAsDetected(domains) {
|
|
174
|
+
if (!Array.isArray(domains)) {
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let addedCount = 0;
|
|
179
|
+
domains.forEach(domain => {
|
|
180
|
+
if (this.markDomainAsDetected(domain)) {
|
|
181
|
+
addedCount++;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return addedCount;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create bound helper functions for easy integration with existing code
|
|
190
|
+
* @returns {object} Object with bound helper functions
|
|
191
|
+
*/
|
|
192
|
+
createHelpers() {
|
|
193
|
+
return {
|
|
194
|
+
isDomainAlreadyDetected: this.isDomainAlreadyDetected.bind(this),
|
|
195
|
+
markDomainAsDetected: this.markDomainAsDetected.bind(this),
|
|
196
|
+
getSkippedCount: () => this.stats.totalSkipped,
|
|
197
|
+
getCacheSize: () => this.cache.size,
|
|
198
|
+
getStats: this.getStats.bind(this)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a global domain cache instance (singleton pattern)
|
|
205
|
+
*/
|
|
206
|
+
let globalDomainCache = null;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get or create the global domain cache instance
|
|
210
|
+
* @param {object} options - Cache options
|
|
211
|
+
* @returns {DomainCache} Global cache instance
|
|
212
|
+
*/
|
|
213
|
+
function getGlobalDomainCache(options = {}) {
|
|
214
|
+
if (!globalDomainCache) {
|
|
215
|
+
globalDomainCache = new DomainCache(options);
|
|
216
|
+
}
|
|
217
|
+
return globalDomainCache;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create helper functions that use the global cache
|
|
222
|
+
* @param {object} options - Cache options (only used if global cache doesn't exist)
|
|
223
|
+
* @returns {object} Helper functions bound to global cache
|
|
224
|
+
*/
|
|
225
|
+
function createGlobalHelpers(options = {}) {
|
|
226
|
+
const cache = getGlobalDomainCache(options);
|
|
227
|
+
return cache.createHelpers();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Reset the global cache (useful for testing or manual resets)
|
|
232
|
+
*/
|
|
233
|
+
function resetGlobalCache() {
|
|
234
|
+
if (globalDomainCache) {
|
|
235
|
+
globalDomainCache.clear();
|
|
236
|
+
}
|
|
237
|
+
globalDomainCache = null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Legacy wrapper functions for backward compatibility
|
|
242
|
+
* These match the original function signatures from nwss.js
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if a domain was already detected (legacy wrapper)
|
|
247
|
+
* @param {string} domain - Domain to check
|
|
248
|
+
* @returns {boolean} True if domain was already detected
|
|
249
|
+
*/
|
|
250
|
+
function isDomainAlreadyDetected(domain) {
|
|
251
|
+
const cache = getGlobalDomainCache();
|
|
252
|
+
return cache.isDomainAlreadyDetected(domain);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Mark a domain as detected (legacy wrapper)
|
|
257
|
+
* @param {string} domain - Domain to mark as detected
|
|
258
|
+
*/
|
|
259
|
+
function markDomainAsDetected(domain) {
|
|
260
|
+
const cache = getGlobalDomainCache();
|
|
261
|
+
cache.markDomainAsDetected(domain);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get total domains skipped (legacy wrapper)
|
|
266
|
+
* @returns {number} Number of domains skipped
|
|
267
|
+
*/
|
|
268
|
+
function getTotalDomainsSkipped() {
|
|
269
|
+
const cache = getGlobalDomainCache();
|
|
270
|
+
return cache.stats.totalSkipped;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get detected domains cache size (legacy wrapper)
|
|
275
|
+
* @returns {number} Size of the detected domains cache
|
|
276
|
+
*/
|
|
277
|
+
function getDetectedDomainsCount() {
|
|
278
|
+
const cache = getGlobalDomainCache();
|
|
279
|
+
return cache.cache.size;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
// Main class
|
|
284
|
+
DomainCache,
|
|
285
|
+
|
|
286
|
+
// Global cache functions
|
|
287
|
+
getGlobalDomainCache,
|
|
288
|
+
createGlobalHelpers,
|
|
289
|
+
resetGlobalCache,
|
|
290
|
+
|
|
291
|
+
// Legacy wrapper functions for backward compatibility
|
|
292
|
+
isDomainAlreadyDetected,
|
|
293
|
+
markDomainAsDetected,
|
|
294
|
+
getTotalDomainsSkipped,
|
|
295
|
+
getDetectedDomainsCount
|
|
296
|
+
};
|
package/lib/nettools.js
CHANGED
|
@@ -728,10 +728,31 @@ function createNetToolsHandler(config) {
|
|
|
728
728
|
// Add separate deduplication caches for different lookup types
|
|
729
729
|
const processedWhoisDomains = new Set();
|
|
730
730
|
const processedDigDomains = new Set();
|
|
731
|
-
|
|
731
|
+
// Add whois resolution caching to avoid redundant whois lookups
|
|
732
|
+
const whoisResultCache = new Map();
|
|
733
|
+
const WHOIS_CACHE_TTL = 900000; // 15 minutes cache TTL (whois data changes less frequently)
|
|
734
|
+
const MAX_CACHE_SIZE = 400; // Larger cache for whois due to longer TTL
|
|
735
|
+
// Size Memory
|
|
736
|
+
// 100 ~900KB
|
|
737
|
+
// 200 1.8MB
|
|
738
|
+
// 300 2.6MB
|
|
739
|
+
// 400 3.4MB
|
|
740
|
+
// 500 4.2MB
|
|
741
|
+
// Add DNS resolution caching to avoid redundant dig lookups
|
|
742
|
+
const digResultCache = new Map();
|
|
743
|
+
const DIG_CACHE_TTL = 300000; // 5 minutes cache TTL
|
|
744
|
+
const DIG_MAX_CACHE_SIZE = 400; // Smaller cache for dig due to shorter TTL
|
|
732
745
|
|
|
733
746
|
return async function handleNetToolsCheck(domain, originalDomain) {
|
|
734
747
|
// Helper function to log to BOTH console and debug file
|
|
748
|
+
|
|
749
|
+
// Check if domain was already detected (skip expensive operations)
|
|
750
|
+
if (typeof config.isDomainAlreadyDetected === 'function' && config.isDomainAlreadyDetected(domain)) {
|
|
751
|
+
if (forceDebug) {
|
|
752
|
+
logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Skipping already detected domain: ${domain}`);
|
|
753
|
+
}
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
735
756
|
|
|
736
757
|
// NOTE: The logToConsoleAndFile function needs to be declared INSIDE this function
|
|
737
758
|
// so it has access to the closure variables (forceDebug, debugLogFile, fs) from the
|
|
@@ -869,14 +890,43 @@ function createNetToolsHandler(config) {
|
|
|
869
890
|
// Mark whois domain as being processed
|
|
870
891
|
processedWhoisDomains.add(domain);
|
|
871
892
|
|
|
893
|
+
// Check whois cache first - cache key includes server for accuracy
|
|
872
894
|
const selectedServer = selectWhoisServer(whoisServer, whoisServerMode);
|
|
895
|
+
const whoisCacheKey = `${domain}-${selectedServer || 'default'}`;
|
|
896
|
+
const now = Date.now();
|
|
897
|
+
let whoisResult = null;
|
|
873
898
|
|
|
874
|
-
if (
|
|
875
|
-
const
|
|
876
|
-
|
|
899
|
+
if (whoisResultCache.has(whoisCacheKey)) {
|
|
900
|
+
const cachedEntry = whoisResultCache.get(whoisCacheKey);
|
|
901
|
+
if (now - cachedEntry.timestamp < WHOIS_CACHE_TTL) {
|
|
902
|
+
if (forceDebug) {
|
|
903
|
+
const age = Math.round((now - cachedEntry.timestamp) / 1000);
|
|
904
|
+
const serverInfo = selectedServer ? ` (server: ${selectedServer})` : ' (default server)';
|
|
905
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Using cached result for ${domain}${serverInfo} [age: ${age}s]`);
|
|
906
|
+
}
|
|
907
|
+
whoisResult = {
|
|
908
|
+
...cachedEntry.result,
|
|
909
|
+
// Add cache metadata
|
|
910
|
+
fromCache: true,
|
|
911
|
+
cacheAge: now - cachedEntry.timestamp,
|
|
912
|
+
originalTimestamp: cachedEntry.timestamp
|
|
913
|
+
};
|
|
914
|
+
} else {
|
|
915
|
+
// Cache expired, remove it
|
|
916
|
+
whoisResultCache.delete(whoisCacheKey);
|
|
917
|
+
if (forceDebug) {
|
|
918
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cache expired for ${domain}, performing fresh lookup`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
877
921
|
}
|
|
878
|
-
|
|
879
|
-
|
|
922
|
+
|
|
923
|
+
// Perform fresh lookup if not cached
|
|
924
|
+
if (!whoisResult) {
|
|
925
|
+
if (forceDebug) {
|
|
926
|
+
const serverInfo = selectedServer ? ` using server ${selectedServer}` : ' using default server';
|
|
927
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Performing fresh whois lookup for ${domain}${serverInfo}`);
|
|
928
|
+
}
|
|
929
|
+
|
|
880
930
|
// Configure retry options based on site config or use defaults
|
|
881
931
|
const retryOptions = {
|
|
882
932
|
maxRetries: siteConfig.whois_max_retries || 2,
|
|
@@ -886,7 +936,47 @@ function createNetToolsHandler(config) {
|
|
|
886
936
|
retryOnError: siteConfig.whois_retry_on_error === true // Default false
|
|
887
937
|
};
|
|
888
938
|
|
|
889
|
-
|
|
939
|
+
try {
|
|
940
|
+
whoisResult = await whoisLookupWithRetry(domain, 8000, whoisServer, forceDebug, retryOptions, whoisDelay, logToConsoleAndFile);
|
|
941
|
+
|
|
942
|
+
// Cache successful results (and certain types of failures)
|
|
943
|
+
if (whoisResult.success ||
|
|
944
|
+
(whoisResult.error && !whoisResult.isTimeout &&
|
|
945
|
+
!whoisResult.error.toLowerCase().includes('connection') &&
|
|
946
|
+
!whoisResult.error.toLowerCase().includes('network'))) {
|
|
947
|
+
|
|
948
|
+
whoisResultCache.set(whoisCacheKey, {
|
|
949
|
+
result: whoisResult,
|
|
950
|
+
timestamp: now
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
if (forceDebug) {
|
|
954
|
+
const cacheType = whoisResult.success ? 'successful' : 'failed';
|
|
955
|
+
const serverInfo = selectedServer ? ` (server: ${selectedServer})` : ' (default server)';
|
|
956
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cached ${cacheType} result for ${domain}${serverInfo}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
} catch (whoisError) {
|
|
960
|
+
// Handle exceptions from whois lookup
|
|
961
|
+
if (forceDebug) {
|
|
962
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception during lookup for ${domain}: ${whoisError.message}`);
|
|
963
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception type: ${whoisError.constructor.name}`);
|
|
964
|
+
if (whoisError.stack) {
|
|
965
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Stack trace: ${whoisError.stack.split('\n').slice(0, 3).join(' -> ')}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Log whois exceptions in dry run mode
|
|
970
|
+
if (dryRunCallback && forceDebug) {
|
|
971
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Exception: ${whoisError.message}`);
|
|
972
|
+
}
|
|
973
|
+
// Continue with dig if configured
|
|
974
|
+
whoisResult = null; // Ensure we don't process a null result
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Process whois result (whether from cache or fresh lookup)
|
|
979
|
+
if (whoisResult) {
|
|
890
980
|
|
|
891
981
|
if (whoisResult.success) {
|
|
892
982
|
// Check AND terms if configured
|
|
@@ -896,6 +986,7 @@ function createNetToolsHandler(config) {
|
|
|
896
986
|
dryRunCallback(domain, 'whois', 'AND logic', whoisTerms.join(', '), 'All terms found in whois data', {
|
|
897
987
|
server: whoisResult.whoisServer || 'default',
|
|
898
988
|
duration: whoisResult.duration,
|
|
989
|
+
fromCache: whoisResult.fromCache || false,
|
|
899
990
|
retryAttempts: whoisResult.retryInfo?.totalAttempts || 1
|
|
900
991
|
});
|
|
901
992
|
}
|
|
@@ -913,6 +1004,7 @@ function createNetToolsHandler(config) {
|
|
|
913
1004
|
dryRunCallback(domain, 'whois', 'OR logic', matchedTerm, 'Term found in whois data', {
|
|
914
1005
|
server: whoisResult.whoisServer || 'default',
|
|
915
1006
|
duration: whoisResult.duration,
|
|
1007
|
+
fromCache: whoisResult.fromCache || false,
|
|
916
1008
|
retryAttempts: whoisResult.retryInfo?.totalAttempts || 1
|
|
917
1009
|
});
|
|
918
1010
|
}
|
|
@@ -925,7 +1017,9 @@ function createNetToolsHandler(config) {
|
|
|
925
1017
|
if (forceDebug) {
|
|
926
1018
|
const serverUsed = whoisResult.whoisServer ? ` (server: ${whoisResult.whoisServer})` : ' (default server)';
|
|
927
1019
|
const retryInfo = whoisResult.retryInfo ? ` [${whoisResult.retryInfo.totalAttempts}/${whoisResult.retryInfo.maxAttempts} attempts]` : '';
|
|
928
|
-
|
|
1020
|
+
const cacheInfo = whoisResult.fromCache ? ` [CACHED - ${Math.round(whoisResult.cacheAge / 1000)}s old]` : '';
|
|
1021
|
+
const duration = whoisResult.fromCache ? `cached in 0ms` : `in ${whoisResult.duration}ms`;
|
|
1022
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Lookup completed for ${domain}${serverUsed} ${duration}${retryInfo}${cacheInfo}`);
|
|
929
1023
|
|
|
930
1024
|
if (whoisResult.retryInfo && whoisResult.retryInfo.retriedAfterFailure) {
|
|
931
1025
|
logToConsoleAndFile(`${messageColors.highlight('[whois]')} Success after retry - servers attempted: [${whoisResult.retryInfo.serversAttempted.map(s => s || 'default').join(', ')}]`);
|
|
@@ -1001,20 +1095,21 @@ function createNetToolsHandler(config) {
|
|
|
1001
1095
|
}
|
|
1002
1096
|
// Don't return early - continue with dig if configured
|
|
1003
1097
|
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Periodic whois cache cleanup to prevent memory leaks
|
|
1101
|
+
if (whoisResultCache.size > MAX_CACHE_SIZE) {
|
|
1102
|
+
const now = Date.now();
|
|
1103
|
+
let cleanedCount = 0;
|
|
1104
|
+
for (const [key, entry] of whoisResultCache.entries()) {
|
|
1105
|
+
if (now - entry.timestamp > WHOIS_CACHE_TTL) {
|
|
1106
|
+
whoisResultCache.delete(key);
|
|
1107
|
+
cleanedCount++;
|
|
1010
1108
|
}
|
|
1011
1109
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
if (dryRunCallback && forceDebug) {
|
|
1015
|
-
logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Exception: ${whoisError.message}`);
|
|
1110
|
+
if (forceDebug && cleanedCount > 0) {
|
|
1111
|
+
logToConsoleAndFile(`${messageColors.highlight('[whois-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${whoisResultCache.size}`);
|
|
1016
1112
|
}
|
|
1017
|
-
// Continue with dig if configured
|
|
1018
1113
|
}
|
|
1019
1114
|
}
|
|
1020
1115
|
|
|
@@ -1031,7 +1126,37 @@ function createNetToolsHandler(config) {
|
|
|
1031
1126
|
}
|
|
1032
1127
|
|
|
1033
1128
|
try {
|
|
1129
|
+
// Check dig cache first to avoid redundant dig operations
|
|
1130
|
+
const digCacheKey = `${digDomain}-${digRecordType}`;
|
|
1131
|
+
const now = Date.now();
|
|
1132
|
+
let digResult = null;
|
|
1133
|
+
|
|
1134
|
+
if (digResultCache.has(digCacheKey)) {
|
|
1135
|
+
const cachedEntry = digResultCache.get(digCacheKey);
|
|
1136
|
+
if (now - cachedEntry.timestamp < DIG_CACHE_TTL) {
|
|
1137
|
+
if (forceDebug) {
|
|
1138
|
+
logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Using cached result for ${digDomain} (${digRecordType}) [age: ${Math.round((now - cachedEntry.timestamp) / 1000)}s]`);
|
|
1139
|
+
}
|
|
1140
|
+
digResult = cachedEntry.result;
|
|
1141
|
+
} else {
|
|
1142
|
+
// Cache expired, remove it
|
|
1143
|
+
digResultCache.delete(digCacheKey);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (!digResult) {
|
|
1034
1148
|
const digResult = await digLookup(digDomain, digRecordType, 5000); // 5 second timeout for dig
|
|
1149
|
+
|
|
1150
|
+
// Cache the result for future use
|
|
1151
|
+
digResultCache.set(digCacheKey, {
|
|
1152
|
+
result: digResult,
|
|
1153
|
+
timestamp: now
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
if (forceDebug && digResult.success) {
|
|
1157
|
+
logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Cached new result for ${digDomain} (${digRecordType})`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1035
1160
|
|
|
1036
1161
|
if (digResult.success) {
|
|
1037
1162
|
// Check AND terms if configured
|
|
@@ -1091,6 +1216,21 @@ function createNetToolsHandler(config) {
|
|
|
1091
1216
|
logToConsoleAndFile(`${messageColors.highlight('[dig-dryrun]')} Exception: ${digError.message}`);
|
|
1092
1217
|
}
|
|
1093
1218
|
}
|
|
1219
|
+
|
|
1220
|
+
// Periodic dig cache cleanup to prevent memory leaks
|
|
1221
|
+
if (digResultCache.size > DIG_MAX_CACHE_SIZE) {
|
|
1222
|
+
const now = Date.now();
|
|
1223
|
+
let cleanedCount = 0;
|
|
1224
|
+
for (const [key, entry] of digResultCache.entries()) {
|
|
1225
|
+
if (now - entry.timestamp > DIG_CACHE_TTL) {
|
|
1226
|
+
digResultCache.delete(key);
|
|
1227
|
+
cleanedCount++;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (forceDebug && cleanedCount > 0) {
|
|
1231
|
+
logToConsoleAndFile(`${messageColors.highlight('[dig-cache]')} Cleaned ${cleanedCount} expired entries, cache size: ${digResultCache.size}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1094
1234
|
}
|
|
1095
1235
|
|
|
1096
1236
|
// Domain matches if any of these conditions are true:
|
package/lib/output.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
// Import domain cache functions for statistics
|
|
4
|
+
const { getTotalDomainsSkipped } = require('./domain-cache');
|
|
3
5
|
const { loadComparisonRules, filterUniqueRules } = require('./compare');
|
|
4
6
|
const { colorize, colors, messageColors, tags, formatLogMessage } = require('./colorize');
|
|
5
7
|
|
|
@@ -426,7 +428,6 @@ function writeOutput(lines, outputFile = null, silentMode = false) {
|
|
|
426
428
|
* Main output handler that combines all output operations
|
|
427
429
|
* @param {Array} results - Processing results from scanner
|
|
428
430
|
* @param {object} config - Output configuration
|
|
429
|
-
* @param {string[]} config.ignoreDomains - Domains to filter out from final output
|
|
430
431
|
* @returns {object} Output statistics and file paths
|
|
431
432
|
*/
|
|
432
433
|
function handleOutput(results, config = {}) {
|
|
@@ -440,7 +441,8 @@ function handleOutput(results, config = {}) {
|
|
|
440
441
|
dumpUrls = false,
|
|
441
442
|
adblockRulesLogFile = null,
|
|
442
443
|
forceDebug = false,
|
|
443
|
-
ignoreDomains = []
|
|
444
|
+
ignoreDomains = [],
|
|
445
|
+
totalDomainsSkipped = null // Allow override or get from cache
|
|
444
446
|
} = config;
|
|
445
447
|
|
|
446
448
|
// Handle append mode
|
|
@@ -572,7 +574,11 @@ function handleOutput(results, config = {}) {
|
|
|
572
574
|
if (dumpUrls && adblockRulesLogFile) {
|
|
573
575
|
logSuccess = writeOutput(outputLinesWithTitles, adblockRulesLogFile, silentMode);
|
|
574
576
|
}
|
|
575
|
-
|
|
577
|
+
|
|
578
|
+
// Get domain skip statistics from cache if not provided
|
|
579
|
+
const finalTotalDomainsSkipped = totalDomainsSkipped !== null ?
|
|
580
|
+
totalDomainsSkipped : getTotalDomainsSkipped();
|
|
581
|
+
|
|
576
582
|
return {
|
|
577
583
|
success: mainSuccess && logSuccess,
|
|
578
584
|
outputFile,
|
|
@@ -582,6 +588,8 @@ function handleOutput(results, config = {}) {
|
|
|
582
588
|
filteredOutCount,
|
|
583
589
|
totalLines: filteredOutputLines.length,
|
|
584
590
|
outputLines: outputFile ? null : filteredOutputLines // Only return lines if not written to file
|
|
591
|
+
// Note: totalDomainsSkipped statistic is now available via getTotalDomainsSkipped()
|
|
592
|
+
// and doesn't need to be passed through the output handler
|
|
585
593
|
};
|
|
586
594
|
}
|
|
587
595
|
|
package/lib/searchstring.js
CHANGED
|
@@ -250,11 +250,19 @@ function createCurlHandler(config) {
|
|
|
250
250
|
} = config;
|
|
251
251
|
|
|
252
252
|
return async function curlHandler(requestUrl) {
|
|
253
|
-
const respDomain = perSiteSubDomains ? (new URL(requestUrl)).hostname : getRootDomain(requestUrl);
|
|
254
253
|
|
|
255
254
|
// Only process URLs that match our regex patterns
|
|
256
255
|
const matchesRegex = regexes.some(re => re.test(requestUrl));
|
|
257
256
|
if (!matchesRegex) return;
|
|
257
|
+
|
|
258
|
+
// Extract domain and check if already detected (skip expensive operations)
|
|
259
|
+
const reqDomain = perSiteSubDomains ? (new URL(requestUrl)).hostname : getRootDomain(requestUrl);
|
|
260
|
+
if (typeof config.isDomainAlreadyDetected === 'function' && config.isDomainAlreadyDetected(reqDomain)) {
|
|
261
|
+
if (forceDebug) {
|
|
262
|
+
console.log(`[debug][curl] Skipping already detected domain: ${reqDomain}`);
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
258
266
|
|
|
259
267
|
// Check if this is a first-party request (same domain as the URL being scanned)
|
|
260
268
|
const currentUrlHostname = new URL(currentUrl).hostname;
|
|
@@ -283,11 +291,11 @@ function createCurlHandler(config) {
|
|
|
283
291
|
|
|
284
292
|
// If NO searchstring is defined, match immediately (like browser behavior)
|
|
285
293
|
if (!hasSearchString && !hasSearchStringAnd) {
|
|
286
|
-
if (!
|
|
294
|
+
if (!reqDomain || matchesIgnoreDomain(reqDomain, ignoreDomains)) {
|
|
287
295
|
return;
|
|
288
296
|
}
|
|
289
297
|
|
|
290
|
-
addDomainToCollection(matchedDomains, addMatchedDomain,
|
|
298
|
+
addDomainToCollection(matchedDomains, addMatchedDomain, reqDomain, resourceType);
|
|
291
299
|
const simplifiedUrl = getRootDomain(currentUrl);
|
|
292
300
|
|
|
293
301
|
if (siteConfig.verbose === 1) {
|
|
@@ -317,11 +325,11 @@ function createCurlHandler(config) {
|
|
|
317
325
|
const { found, matchedString, logicType } = searchContent(content, searchStrings, searchStringsAnd, '');
|
|
318
326
|
|
|
319
327
|
if (found) {
|
|
320
|
-
if (!
|
|
328
|
+
if (!reqDomain || matchesIgnoreDomain(reqDomain, ignoreDomains)) {
|
|
321
329
|
return;
|
|
322
330
|
}
|
|
323
331
|
|
|
324
|
-
addDomainToCollection(matchedDomains, addMatchedDomain,
|
|
332
|
+
addDomainToCollection(matchedDomains, addMatchedDomain, reqDomain, resourceType);
|
|
325
333
|
const simplifiedUrl = getRootDomain(currentUrl);
|
|
326
334
|
|
|
327
335
|
if (siteConfig.verbose === 1) {
|
|
@@ -387,6 +395,14 @@ function createResponseHandler(config) {
|
|
|
387
395
|
const matchesRegex = regexes.some(re => re.test(respUrl));
|
|
388
396
|
if (!matchesRegex) return;
|
|
389
397
|
|
|
398
|
+
// Extract domain and check if already detected (skip expensive operations)
|
|
399
|
+
if (typeof config.isDomainAlreadyDetected === 'function' && config.isDomainAlreadyDetected(respDomain)) {
|
|
400
|
+
if (forceDebug) {
|
|
401
|
+
console.log(`[debug] Skipping response analysis for already detected domain: ${respDomain}`);
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
390
406
|
// Check if this is a first-party response (same domain as the URL being scanned)
|
|
391
407
|
const currentUrlHostname = new URL(currentUrl).hostname;
|
|
392
408
|
const responseHostname = new URL(respUrl).hostname;
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v1.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v1.0.43 ===
|
|
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
|
|
@@ -27,17 +27,23 @@ const { createNetToolsHandler, createEnhancedDryRunCallback, validateWhoisAvaila
|
|
|
27
27
|
const { loadComparisonRules, filterUniqueRules } = require('./lib/compare');
|
|
28
28
|
// Colorize various text when used
|
|
29
29
|
const { colorize, colors, messageColors, tags, formatLogMessage } = require('./lib/colorize');
|
|
30
|
+
// Domain detection cache for performance optimization
|
|
31
|
+
const { createGlobalHelpers, getTotalDomainsSkipped, getDetectedDomainsCount } = require('./lib/domain-cache');
|
|
30
32
|
// Enhanced redirect handling
|
|
31
33
|
const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/redirect');
|
|
32
34
|
// Ensure web browser is working correctly
|
|
33
35
|
const { monitorBrowserHealth, isBrowserHealthy } = require('./lib/browserhealth');
|
|
34
36
|
|
|
35
37
|
// --- Script Configuration & Constants ---
|
|
36
|
-
const VERSION = '1.0.
|
|
38
|
+
const VERSION = '1.0.43'; // Script version
|
|
37
39
|
|
|
38
40
|
// get startTime
|
|
39
41
|
const startTime = Date.now();
|
|
40
42
|
|
|
43
|
+
// Initialize domain cache helpers with debug logging if enabled
|
|
44
|
+
const domainCacheOptions = { enableLogging: false }; // Set to true for cache debug logs
|
|
45
|
+
const { isDomainAlreadyDetected, markDomainAsDetected } = createGlobalHelpers(domainCacheOptions);
|
|
46
|
+
|
|
41
47
|
// --- Command-Line Argument Parsing ---
|
|
42
48
|
const args = process.argv.slice(2);
|
|
43
49
|
|
|
@@ -1517,6 +1523,9 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1517
1523
|
return; // Skip adding this domain
|
|
1518
1524
|
}
|
|
1519
1525
|
}
|
|
1526
|
+
|
|
1527
|
+
// Mark domain as detected for future reference
|
|
1528
|
+
markDomainAsDetected(domain);
|
|
1520
1529
|
|
|
1521
1530
|
if (matchedDomains instanceof Map) {
|
|
1522
1531
|
if (!matchedDomains.has(domain)) {
|
|
@@ -1734,6 +1743,14 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1734
1743
|
}
|
|
1735
1744
|
} else if (hasNetTools && !hasSearchString && !hasSearchStringAnd) {
|
|
1736
1745
|
// If nettools are configured (whois/dig), perform checks on the domain
|
|
1746
|
+
// Skip nettools check if domain was already detected
|
|
1747
|
+
if (isDomainAlreadyDetected(reqDomain)) {
|
|
1748
|
+
if (forceDebug) {
|
|
1749
|
+
console.log(formatLogMessage('debug', `Skipping nettools check for already detected domain: ${reqDomain}`));
|
|
1750
|
+
}
|
|
1751
|
+
break; // Skip to next URL
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1737
1754
|
if (forceDebug) {
|
|
1738
1755
|
console.log(formatLogMessage('debug', `${reqUrl} matched regex ${re} and resourceType ${resourceType}, queued for nettools check`));
|
|
1739
1756
|
}
|
|
@@ -1767,6 +1784,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1767
1784
|
dryRunCallback: dryRunMode ? createEnhancedDryRunCallback(matchedDomains, forceDebug) : null,
|
|
1768
1785
|
matchedDomains,
|
|
1769
1786
|
addMatchedDomain,
|
|
1787
|
+
isDomainAlreadyDetected,
|
|
1770
1788
|
currentUrl,
|
|
1771
1789
|
getRootDomain,
|
|
1772
1790
|
siteConfig,
|
|
@@ -1781,6 +1799,13 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1781
1799
|
setImmediate(() => netToolsHandler(reqDomain, originalDomain));
|
|
1782
1800
|
} else {
|
|
1783
1801
|
// If searchstring or searchstring_and IS defined (with or without nettools), queue for content checking
|
|
1802
|
+
// Skip searchstring check if domain was already detected
|
|
1803
|
+
if (isDomainAlreadyDetected(reqDomain)) {
|
|
1804
|
+
if (forceDebug) {
|
|
1805
|
+
console.log(formatLogMessage('debug', `Skipping searchstring check for already detected domain: ${reqDomain}`));
|
|
1806
|
+
}
|
|
1807
|
+
break; // Skip to next URL
|
|
1808
|
+
}
|
|
1784
1809
|
if (forceDebug) {
|
|
1785
1810
|
const searchType = hasSearchStringAnd ? 'searchstring_and' : 'searchstring';
|
|
1786
1811
|
console.log(formatLogMessage('debug', `${reqUrl} matched regex ${re} and resourceType ${resourceType}, queued for ${searchType} content search`));
|
|
@@ -1808,6 +1833,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1808
1833
|
regexes,
|
|
1809
1834
|
matchedDomains,
|
|
1810
1835
|
addMatchedDomain, // Pass the helper function
|
|
1836
|
+
isDomainAlreadyDetected,
|
|
1811
1837
|
currentUrl,
|
|
1812
1838
|
perSiteSubDomains,
|
|
1813
1839
|
ignoreDomains,
|
|
@@ -1838,6 +1864,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1838
1864
|
regexes,
|
|
1839
1865
|
matchedDomains,
|
|
1840
1866
|
addMatchedDomain, // Pass the helper function
|
|
1867
|
+
isDomainAlreadyDetected,
|
|
1841
1868
|
currentUrl,
|
|
1842
1869
|
perSiteSubDomains,
|
|
1843
1870
|
ignoreDomains,
|
|
@@ -1876,6 +1903,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1876
1903
|
regexes,
|
|
1877
1904
|
matchedDomains,
|
|
1878
1905
|
addMatchedDomain, // Pass the helper function
|
|
1906
|
+
isDomainAlreadyDetected,
|
|
1879
1907
|
currentUrl,
|
|
1880
1908
|
perSiteSubDomains,
|
|
1881
1909
|
ignoreDomains,
|
|
@@ -2462,6 +2490,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2462
2490
|
const totalMatches = results.reduce((sum, r) => sum + (r.rules ? r.rules.length : 0), 0);
|
|
2463
2491
|
|
|
2464
2492
|
// Debug: Show output format being used
|
|
2493
|
+
const totalDomainsSkipped = getTotalDomainsSkipped();
|
|
2494
|
+
const detectedDomainsCount = getDetectedDomainsCount();
|
|
2465
2495
|
if (forceDebug) {
|
|
2466
2496
|
const globalOptions = {
|
|
2467
2497
|
localhostMode,
|
|
@@ -2476,6 +2506,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2476
2506
|
};
|
|
2477
2507
|
console.log(formatLogMessage('debug', `Output format: ${getFormatDescription(globalOptions)}`));
|
|
2478
2508
|
console.log(formatLogMessage('debug', `Generated ${outputResult.totalRules} rules from ${outputResult.successfulPageLoads} successful page loads`));
|
|
2509
|
+
console.log(formatLogMessage('debug', `Performance: ${totalDomainsSkipped} domains skipped (already detected), ${detectedDomainsCount} unique domains cached`));
|
|
2479
2510
|
}
|
|
2480
2511
|
|
|
2481
2512
|
// Compress log files if --compress-logs is enabled
|
|
@@ -2567,6 +2598,9 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2567
2598
|
} else if (outputResult.totalRules > 0 && dryRunMode) {
|
|
2568
2599
|
console.log(messageColors.success('Found') + ` ${outputResult.totalRules} total matches across all URLs`);
|
|
2569
2600
|
}
|
|
2601
|
+
if (totalDomainsSkipped > 0) {
|
|
2602
|
+
console.log(messageColors.info('Performance:') + ` ${totalDomainsSkipped} domains skipped (already detected)`);
|
|
2603
|
+
}
|
|
2570
2604
|
}
|
|
2571
2605
|
|
|
2572
2606
|
// Clean process termination
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
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": {
|