@fanboynz/network-scanner 1.0.84 → 1.0.86
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 +121 -8
- package/lib/output.js +28 -6
- package/lib/post-processing.js +182 -1
- package/nwss.js +76 -5
- package/package.json +1 -1
package/lib/browserhealth.js
CHANGED
|
@@ -5,6 +5,89 @@
|
|
|
5
5
|
|
|
6
6
|
const { formatLogMessage, messageColors } = require('./colorize');
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Quick browser responsiveness test for use during page setup
|
|
10
|
+
* Designed to catch browser degradation between operations
|
|
11
|
+
* @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
|
|
12
|
+
* @param {number} timeout - Timeout in milliseconds (default: 3000)
|
|
13
|
+
* @returns {Promise<boolean>} True if browser responds quickly, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
async function isQuicklyResponsive(browserInstance, timeout = 3000) {
|
|
16
|
+
try {
|
|
17
|
+
await Promise.race([
|
|
18
|
+
browserInstance.version(), // Quick responsiveness test
|
|
19
|
+
new Promise((_, reject) =>
|
|
20
|
+
setTimeout(() => reject(new Error('Quick responsiveness timeout')), timeout)
|
|
21
|
+
)
|
|
22
|
+
]);
|
|
23
|
+
return true;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if browser can handle network operations (like Network.enable)
|
|
31
|
+
* Creates a test page and attempts basic network setup
|
|
32
|
+
* @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
|
|
33
|
+
* @param {number} timeout - Timeout in milliseconds (default: 10000)
|
|
34
|
+
* @returns {Promise<object>} Network capability test result
|
|
35
|
+
*/
|
|
36
|
+
async function testNetworkCapability(browserInstance, timeout = 10000) {
|
|
37
|
+
const result = {
|
|
38
|
+
capable: false,
|
|
39
|
+
error: null,
|
|
40
|
+
responseTime: 0
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
let testPage = null;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Create test page
|
|
48
|
+
testPage = await Promise.race([
|
|
49
|
+
browserInstance.newPage(),
|
|
50
|
+
new Promise((_, reject) =>
|
|
51
|
+
setTimeout(() => reject(new Error('Test page creation timeout')), timeout)
|
|
52
|
+
)
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
// Test network operations (the critical operation that's failing)
|
|
56
|
+
await Promise.race([
|
|
57
|
+
testPage.setRequestInterception(true),
|
|
58
|
+
new Promise((_, reject) =>
|
|
59
|
+
setTimeout(() => reject(new Error('Network.enable test timeout')), timeout)
|
|
60
|
+
)
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// Turn off interception and close
|
|
64
|
+
await testPage.setRequestInterception(false);
|
|
65
|
+
result.capable = true;
|
|
66
|
+
result.responseTime = Date.now() - startTime;
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
result.error = error.message;
|
|
70
|
+
result.responseTime = Date.now() - startTime;
|
|
71
|
+
|
|
72
|
+
// Classify the error type
|
|
73
|
+
if (error.message.includes('Network.enable') ||
|
|
74
|
+
error.message.includes('timed out') ||
|
|
75
|
+
error.message.includes('Protocol error')) {
|
|
76
|
+
result.error = `Network capability test failed: ${error.message}`;
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
if (testPage && !testPage.isClosed()) {
|
|
80
|
+
try {
|
|
81
|
+
await testPage.close();
|
|
82
|
+
} catch (closeErr) {
|
|
83
|
+
/* ignore cleanup errors */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
8
91
|
/**
|
|
9
92
|
* Checks if browser instance is still responsive
|
|
10
93
|
* @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
|
|
@@ -18,7 +101,8 @@ async function checkBrowserHealth(browserInstance, timeout = 8000) {
|
|
|
18
101
|
error: null,
|
|
19
102
|
responseTime: 0,
|
|
20
103
|
recommendations: [],
|
|
21
|
-
criticalError: false
|
|
104
|
+
criticalError: false,
|
|
105
|
+
networkCapable: false
|
|
22
106
|
};
|
|
23
107
|
|
|
24
108
|
const startTime = Date.now();
|
|
@@ -82,13 +166,25 @@ async function checkBrowserHealth(browserInstance, timeout = 8000) {
|
|
|
82
166
|
return healthResult;
|
|
83
167
|
}
|
|
84
168
|
|
|
85
|
-
// Test 5:
|
|
169
|
+
// Test 5: Network capability test (critical for Network.enable issues)
|
|
170
|
+
const networkTest = await testNetworkCapability(browserInstance, Math.min(timeout, 5000));
|
|
171
|
+
healthResult.networkCapable = networkTest.capable;
|
|
172
|
+
|
|
173
|
+
if (!networkTest.capable) {
|
|
174
|
+
healthResult.recommendations.push(`Network operations failing: ${networkTest.error}`);
|
|
175
|
+
if (networkTest.error && networkTest.error.includes('Network.enable')) {
|
|
176
|
+
healthResult.criticalError = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Test 6: Check response time performance
|
|
86
181
|
if (healthResult.responseTime > 5000) {
|
|
87
182
|
healthResult.recommendations.push('Slow browser response - consider restart');
|
|
88
183
|
}
|
|
89
184
|
|
|
90
|
-
// If all tests pass
|
|
91
|
-
healthResult.healthy =
|
|
185
|
+
// If all tests pass (including network capability)
|
|
186
|
+
healthResult.healthy = networkTest.capable; // Network capability is now critical for health
|
|
187
|
+
|
|
92
188
|
|
|
93
189
|
} catch (error) {
|
|
94
190
|
healthResult.error = error.message;
|
|
@@ -195,7 +291,11 @@ function isCriticalProtocolError(error) {
|
|
|
195
291
|
'Browser process exited',
|
|
196
292
|
'Navigation timeout of',
|
|
197
293
|
'Page crashed',
|
|
198
|
-
'Renderer process crashed'
|
|
294
|
+
'Renderer process crashed',
|
|
295
|
+
// Network-specific critical errors
|
|
296
|
+
'Network.enable timed out',
|
|
297
|
+
'Network.disable timed out',
|
|
298
|
+
'Network service not available'
|
|
199
299
|
];
|
|
200
300
|
|
|
201
301
|
return criticalErrors.some(criticalError =>
|
|
@@ -413,14 +513,25 @@ async function monitorBrowserHealth(browserInstance, context = {}, options = {})
|
|
|
413
513
|
|
|
414
514
|
/**
|
|
415
515
|
* Simple health check function for quick integration
|
|
516
|
+
* Enhanced version that includes network capability testing
|
|
416
517
|
* @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance
|
|
518
|
+
* @param {boolean} includeNetworkTest - Whether to test network capabilities (default: true)
|
|
417
519
|
* @returns {Promise<boolean>} True if browser is healthy, false otherwise
|
|
418
520
|
*/
|
|
419
|
-
async function isBrowserHealthy(browserInstance) {
|
|
521
|
+
async function isBrowserHealthy(browserInstance, includeNetworkTest = true) {
|
|
420
522
|
try {
|
|
421
|
-
|
|
523
|
+
// Quick responsiveness test first (fastest check)
|
|
524
|
+
const quickCheck = await isQuicklyResponsive(browserInstance, 2500);
|
|
525
|
+
if (!quickCheck) return false;
|
|
526
|
+
|
|
527
|
+
// More comprehensive health check if quick test passes
|
|
528
|
+
const health = await checkBrowserHealth(browserInstance, includeNetworkTest ? 8000 : 5000);
|
|
422
529
|
const connectivity = await testBrowserConnectivity(browserInstance, 3000);
|
|
423
|
-
|
|
530
|
+
|
|
531
|
+
const baseHealth = health.healthy && connectivity.connected && connectivity.cdpResponsive;
|
|
532
|
+
|
|
533
|
+
// Include network capability in health assessment if requested
|
|
534
|
+
return includeNetworkTest ? (baseHealth && health.networkCapable) : baseHealth;
|
|
424
535
|
} catch (error) {
|
|
425
536
|
return false;
|
|
426
537
|
}
|
|
@@ -430,6 +541,8 @@ module.exports = {
|
|
|
430
541
|
checkBrowserHealth,
|
|
431
542
|
checkBrowserMemory,
|
|
432
543
|
testBrowserConnectivity,
|
|
544
|
+
testNetworkCapability,
|
|
545
|
+
isQuicklyResponsive,
|
|
433
546
|
performHealthAssessment,
|
|
434
547
|
monitorBrowserHealth,
|
|
435
548
|
isBrowserHealthy,
|
package/lib/output.js
CHANGED
|
@@ -18,13 +18,35 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
|
|
|
18
18
|
|
|
19
19
|
return ignorePatterns.some(pattern => {
|
|
20
20
|
if (pattern.includes('*')) {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
21
|
+
// Enhanced wildcard pattern handling
|
|
22
|
+
if (pattern.startsWith('*.')) {
|
|
23
|
+
// Pattern: *.example.com
|
|
24
|
+
const wildcardDomain = pattern.substring(2); // Remove "*."
|
|
25
|
+
const wildcardRoot = extractDomainFromRule(`||${wildcardDomain}^`) || wildcardDomain;
|
|
26
|
+
const domainRoot = extractDomainFromRule(`||${domain}^`) || domain;
|
|
27
|
+
|
|
28
|
+
// Use basic root domain comparison for output filtering
|
|
29
|
+
const getSimpleRoot = (d) => {
|
|
30
|
+
const parts = d.split('.');
|
|
31
|
+
return parts.length >= 2 ? parts.slice(-2).join('.') : d;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return getSimpleRoot(domainRoot) === getSimpleRoot(wildcardRoot);
|
|
35
|
+
} else if (pattern.endsWith('.*')) {
|
|
36
|
+
// Pattern: example.*
|
|
37
|
+
const baseDomain = pattern.slice(0, -2); // Remove ".*"
|
|
38
|
+
return domain.startsWith(baseDomain + '.');
|
|
39
|
+
} else {
|
|
40
|
+
// Complex wildcard pattern
|
|
41
|
+
const regexPattern = pattern
|
|
42
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
43
|
+
.replace(/\*/g, '.*'); // Convert * to .*
|
|
44
|
+
return new RegExp(`^${regexPattern}$`).test(domain);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Exact pattern matching
|
|
48
|
+
return domain === pattern || domain.endsWith('.' + pattern);
|
|
26
49
|
}
|
|
27
|
-
return domain.endsWith(pattern);
|
|
28
50
|
});
|
|
29
51
|
}
|
|
30
52
|
|
package/lib/post-processing.js
CHANGED
|
@@ -316,6 +316,77 @@ function cleanupIgnoreDomains(results, ignoreDomains, options = {}) {
|
|
|
316
316
|
return cleanedResults;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Enhanced domain extraction helper that reuses existing parsing logic
|
|
321
|
+
* @param {string} rule - Rule string in various formats
|
|
322
|
+
* @returns {string|null} Extracted domain or null if not found
|
|
323
|
+
*/
|
|
324
|
+
function extractDomainFromRule(rule) {
|
|
325
|
+
if (!rule || typeof rule !== 'string') {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
// Reuse the existing parsing logic from cleanupFirstPartyDomains
|
|
331
|
+
let extractedDomain = null;
|
|
332
|
+
|
|
333
|
+
if (rule.startsWith('||') && rule.includes('^')) {
|
|
334
|
+
// ||domain.com^ format (adblock)
|
|
335
|
+
const match = rule.match(/^\|\|([^/\^]+)/);
|
|
336
|
+
if (match) {
|
|
337
|
+
extractedDomain = match[1];
|
|
338
|
+
}
|
|
339
|
+
} else if (rule.startsWith('127.0.0.1 ') || rule.startsWith('0.0.0.0 ')) {
|
|
340
|
+
// hosts file format
|
|
341
|
+
const parts = rule.split(/\s+/);
|
|
342
|
+
if (parts.length >= 2) {
|
|
343
|
+
extractedDomain = parts[1];
|
|
344
|
+
}
|
|
345
|
+
} else if (rule.includes('local=/') && rule.includes('/')) {
|
|
346
|
+
// dnsmasq format: local=/domain.com/
|
|
347
|
+
const match = rule.match(/local=\/([^/]+)\//);
|
|
348
|
+
if (match) {
|
|
349
|
+
extractedDomain = match[1];
|
|
350
|
+
}
|
|
351
|
+
} else if (rule.includes('server=/') && rule.includes('/')) {
|
|
352
|
+
// dnsmasq old format: server=/domain.com/
|
|
353
|
+
const match = rule.match(/server=\/([^/]+)\//);
|
|
354
|
+
if (match) {
|
|
355
|
+
extractedDomain = match[1];
|
|
356
|
+
}
|
|
357
|
+
} else if (rule.includes('local-zone:') && rule.includes('always_null')) {
|
|
358
|
+
// unbound format: local-zone: "domain.com." always_null
|
|
359
|
+
const match = rule.match(/local-zone:\s*"([^"]+)\.?"/);
|
|
360
|
+
if (match) {
|
|
361
|
+
extractedDomain = match[1];
|
|
362
|
+
}
|
|
363
|
+
} else if (rule.includes('+block') && rule.includes('.')) {
|
|
364
|
+
// privoxy format: { +block } .domain.com
|
|
365
|
+
const match = rule.match(/\{\s*\+block\s*\}\s*\.?([^\s]+)/);
|
|
366
|
+
if (match) {
|
|
367
|
+
extractedDomain = match[1];
|
|
368
|
+
}
|
|
369
|
+
} else if (rule.match(/^\(\^\|\\\.\).*\\\.\w+\$$/)) {
|
|
370
|
+
// pi-hole regex format: (^|\.)domain\.com$
|
|
371
|
+
const match = rule.match(/^\(\^\|\\\.\)(.+)\\\.\w+\$$/);
|
|
372
|
+
if (match) {
|
|
373
|
+
// Unescape the domain
|
|
374
|
+
extractedDomain = match[1].replace(/\\\./g, '.');
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// Try to extract any domain-like pattern as fallback
|
|
378
|
+
const domainMatch = rule.match(/([a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z]{2,})/);
|
|
379
|
+
if (domainMatch) {
|
|
380
|
+
extractedDomain = domainMatch[1];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return extractedDomain;
|
|
385
|
+
} catch (parseErr) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
319
390
|
/**
|
|
320
391
|
* Post-scan cleanup function to remove first-party domains from results
|
|
321
392
|
* Only processes sites that have firstParty: false in their configuration
|
|
@@ -564,6 +635,111 @@ function validateScanResults(results, options = {}) {
|
|
|
564
635
|
return validatedResults;
|
|
565
636
|
}
|
|
566
637
|
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Final validation check for firstParty: false violations
|
|
641
|
+
* Reuses existing domain extraction and matching logic
|
|
642
|
+
*
|
|
643
|
+
* @param {Array} results - Array of scan results
|
|
644
|
+
* @param {Array} sites - Array of site configurations
|
|
645
|
+
* @param {Object} options - Options object
|
|
646
|
+
* @returns {Array} Results with any remaining first-party domains removed
|
|
647
|
+
*/
|
|
648
|
+
function finalFirstPartyValidation(results, sites, options = {}) {
|
|
649
|
+
const { forceDebug = false, silentMode = false } = options;
|
|
650
|
+
|
|
651
|
+
if (!results || results.length === 0) {
|
|
652
|
+
return results;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Reuse the URL-to-config mapping pattern from cleanupFirstPartyDomains
|
|
656
|
+
const urlToSiteConfig = new Map();
|
|
657
|
+
sites.forEach(site => {
|
|
658
|
+
const urls = Array.isArray(site.url) ? site.url : [site.url];
|
|
659
|
+
urls.forEach(url => {
|
|
660
|
+
urlToSiteConfig.set(url, site);
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
const finalResults = [];
|
|
665
|
+
let totalViolationsFound = 0;
|
|
666
|
+
let sitesWithViolations = 0;
|
|
667
|
+
|
|
668
|
+
results.forEach(result => {
|
|
669
|
+
const siteConfig = urlToSiteConfig.get(result.url);
|
|
670
|
+
|
|
671
|
+
// Only validate sites with firstParty: false
|
|
672
|
+
const shouldValidate = siteConfig && siteConfig.firstParty === false;
|
|
673
|
+
|
|
674
|
+
if (!shouldValidate || !result.rules || result.rules.length === 0) {
|
|
675
|
+
finalResults.push(result);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const scannedDomain = safeGetDomain(result.url, false);
|
|
680
|
+
if (!scannedDomain) {
|
|
681
|
+
finalResults.push(result);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Reuse the same filtering logic pattern from cleanupFirstPartyDomains
|
|
686
|
+
const cleanedRules = [];
|
|
687
|
+
const violatingRules = [];
|
|
688
|
+
|
|
689
|
+
result.rules.forEach(rule => {
|
|
690
|
+
const extractedDomain = extractDomainFromRule(rule);
|
|
691
|
+
if (extractedDomain) {
|
|
692
|
+
// Reuse the shouldRemoveAsFirstParty logic
|
|
693
|
+
const matchResult = shouldRemoveAsFirstParty(extractedDomain, scannedDomain, forceDebug);
|
|
694
|
+
|
|
695
|
+
if (matchResult.shouldRemove) {
|
|
696
|
+
violatingRules.push({
|
|
697
|
+
rule: rule,
|
|
698
|
+
domain: extractedDomain,
|
|
699
|
+
reason: `VALIDATION FAILURE: ${matchResult.reason}`
|
|
700
|
+
});
|
|
701
|
+
totalViolationsFound++;
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
cleanedRules.push(rule);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
if (violatingRules.length > 0) {
|
|
709
|
+
sitesWithViolations++;
|
|
710
|
+
|
|
711
|
+
if (!silentMode) {
|
|
712
|
+
const errorMessage = `? CONFIG VIOLATION: Found ${violatingRules.length} first-party rule(s) in ${scannedDomain} (firstParty: false)`;
|
|
713
|
+
if (messageColors && messageColors.error) {
|
|
714
|
+
console.log(messageColors.error(errorMessage));
|
|
715
|
+
} else {
|
|
716
|
+
console.log(errorMessage);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (forceDebug) {
|
|
721
|
+
console.log(formatLogMessage('debug', `[final-validation] Violations found for ${result.url}:`));
|
|
722
|
+
violatingRules.forEach((violation, idx) => {
|
|
723
|
+
console.log(formatLogMessage('debug', ` [${idx + 1}] ${violation.rule} -> ${violation.domain}`));
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
finalResults.push({ ...result, rules: cleanedRules });
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Summary using existing message patterns
|
|
732
|
+
if (totalViolationsFound > 0 && !silentMode) {
|
|
733
|
+
const summaryMessage = `\n? SCAN FILTERING FAILURE: Removed ${totalViolationsFound} first-party rules from ${sitesWithViolations} site(s) in post-processing`;
|
|
734
|
+
console.log(summaryMessage);
|
|
735
|
+
console.log('?? This indicates firstParty: false filtering failed during scan - consider investigating root cause.');
|
|
736
|
+
} else if (forceDebug) {
|
|
737
|
+
console.log(formatLogMessage('debug', '[final-validation] No first-party violations found - filtering working correctly'));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return finalResults;
|
|
741
|
+
}
|
|
742
|
+
|
|
567
743
|
/**
|
|
568
744
|
* Main post-processing function that runs all cleanup and validation steps
|
|
569
745
|
*
|
|
@@ -587,8 +763,11 @@ function processResults(results, sites, options = {}) {
|
|
|
587
763
|
|
|
588
764
|
// Step 2: Clean up ignoreDomains (final safety net)
|
|
589
765
|
processedResults = cleanupIgnoreDomains(processedResults, options.ignoreDomains || [], options);
|
|
766
|
+
|
|
767
|
+
// Step 3: Final validation for firstParty: false configurations
|
|
768
|
+
processedResults = finalFirstPartyValidation(processedResults, sites, options);
|
|
590
769
|
|
|
591
|
-
// Step
|
|
770
|
+
// Step 4: Validate results
|
|
592
771
|
processedResults = validateScanResults(processedResults, options);
|
|
593
772
|
|
|
594
773
|
if (forceDebug) {
|
|
@@ -602,6 +781,8 @@ function processResults(results, sites, options = {}) {
|
|
|
602
781
|
module.exports = {
|
|
603
782
|
cleanupFirstPartyDomains,
|
|
604
783
|
cleanupIgnoreDomains,
|
|
784
|
+
finalFirstPartyValidation,
|
|
785
|
+
extractDomainFromRule,
|
|
605
786
|
validateScanResults,
|
|
606
787
|
processResults
|
|
607
788
|
};
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v1.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v1.0.86 ===
|
|
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
|
|
@@ -120,10 +120,10 @@ function detectPuppeteerVersion() {
|
|
|
120
120
|
// Enhanced redirect handling
|
|
121
121
|
const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/redirect');
|
|
122
122
|
// Ensure web browser is working correctly
|
|
123
|
-
const { monitorBrowserHealth, isBrowserHealthy } = require('./lib/browserhealth');
|
|
123
|
+
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive } = require('./lib/browserhealth');
|
|
124
124
|
|
|
125
125
|
// --- Script Configuration & Constants ---
|
|
126
|
-
const VERSION = '1.0.
|
|
126
|
+
const VERSION = '1.0.86'; // Script version
|
|
127
127
|
|
|
128
128
|
// get startTime
|
|
129
129
|
const startTime = Date.now();
|
|
@@ -1435,6 +1435,9 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1435
1435
|
const originalRootDomain = safeGetDomain(currentUrl, false);
|
|
1436
1436
|
if (originalRootDomain) {
|
|
1437
1437
|
firstPartyDomains.add(originalRootDomain);
|
|
1438
|
+
if (forceDebug) {
|
|
1439
|
+
console.log(formatLogMessage('debug', `Initial first-party domain: ${originalRootDomain} for ${currentUrl}`));
|
|
1440
|
+
}
|
|
1438
1441
|
}
|
|
1439
1442
|
|
|
1440
1443
|
// Track redirect domains to exclude from matching
|
|
@@ -1480,6 +1483,22 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1480
1483
|
if (!page || page.isClosed()) {
|
|
1481
1484
|
throw new Error('Failed to create valid page instance');
|
|
1482
1485
|
}
|
|
1486
|
+
|
|
1487
|
+
// Additional health check after page creation but before critical setup
|
|
1488
|
+
const stillHealthy = await isQuicklyResponsive(browserInstance, 3000);
|
|
1489
|
+
|
|
1490
|
+
if (!stillHealthy) {
|
|
1491
|
+
if (forceDebug) {
|
|
1492
|
+
console.log(formatLogMessage('debug', `Browser unresponsive during page setup for ${currentUrl} - triggering restart`));
|
|
1493
|
+
}
|
|
1494
|
+
return {
|
|
1495
|
+
url: currentUrl,
|
|
1496
|
+
rules: [],
|
|
1497
|
+
success: false,
|
|
1498
|
+
needsImmediateRestart: true,
|
|
1499
|
+
error: 'Browser became unresponsive during page setup - restart required'
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1483
1502
|
|
|
1484
1503
|
// Set aggressive timeouts for problematic operations
|
|
1485
1504
|
// Optimized timeouts for Puppeteer 23.x responsiveness
|
|
@@ -1569,6 +1588,22 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1569
1588
|
console.log(formatLogMessage('debug', `[evalOnDoc] Site-specific Fetch/XHR interception enabled for: ${currentUrl}`));
|
|
1570
1589
|
}
|
|
1571
1590
|
}
|
|
1591
|
+
// Quick browser health check before script injection
|
|
1592
|
+
let browserResponsive = false;
|
|
1593
|
+
try {
|
|
1594
|
+
await Promise.race([
|
|
1595
|
+
browserInstance.version(), // Quick responsiveness test
|
|
1596
|
+
new Promise((_, reject) =>
|
|
1597
|
+
setTimeout(() => reject(new Error('Browser health check timeout')), 5000)
|
|
1598
|
+
)
|
|
1599
|
+
]);
|
|
1600
|
+
browserResponsive = true;
|
|
1601
|
+
} catch (healthErr) {
|
|
1602
|
+
console.warn(formatLogMessage('warn', `[evalOnDoc] Browser unresponsive for ${currentUrl}: ${healthErr.message} - skipping script injection`));
|
|
1603
|
+
browserResponsive = false;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if (browserResponsive) {
|
|
1572
1607
|
try {
|
|
1573
1608
|
await page.evaluateOnNewDocument(() => {
|
|
1574
1609
|
// Prevent infinite reload loops
|
|
@@ -1633,7 +1668,16 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1633
1668
|
};
|
|
1634
1669
|
});
|
|
1635
1670
|
} catch (evalErr) {
|
|
1636
|
-
|
|
1671
|
+
if (evalErr.message.includes('timed out') || evalErr.message.includes('ProtocolError')) {
|
|
1672
|
+
console.warn(formatLogMessage('warn', `[evalOnDoc] Script injection protocol timeout for ${currentUrl} - continuing without XHR/Fetch interception`));
|
|
1673
|
+
} else {
|
|
1674
|
+
console.warn(formatLogMessage('warn', `[evalOnDoc] Failed to set up Fetch/XHR interception for ${currentUrl}: ${evalErr.message}`));
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
} else {
|
|
1678
|
+
if (forceDebug) {
|
|
1679
|
+
console.log(formatLogMessage('debug', `[evalOnDoc] Continuing ${currentUrl} without XHR/Fetch interception due to browser health`));
|
|
1680
|
+
}
|
|
1637
1681
|
}
|
|
1638
1682
|
}
|
|
1639
1683
|
// --- END: evaluateOnNewDocument for Fetch/XHR Interception ---
|
|
@@ -1680,7 +1724,34 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1680
1724
|
}
|
|
1681
1725
|
// --- End of Per-Page CDP Setup ---
|
|
1682
1726
|
|
|
1683
|
-
|
|
1727
|
+
// Protected request interception setup with timeout
|
|
1728
|
+
try {
|
|
1729
|
+
// Test if network operations are responsive before enabling request interception
|
|
1730
|
+
await Promise.race([
|
|
1731
|
+
page.setRequestInterception(true),
|
|
1732
|
+
new Promise((_, reject) =>
|
|
1733
|
+
setTimeout(() => reject(new Error('Network.enable timeout')), 10000)
|
|
1734
|
+
)
|
|
1735
|
+
]);
|
|
1736
|
+
|
|
1737
|
+
if (forceDebug) {
|
|
1738
|
+
console.log(formatLogMessage('debug', `Request interception enabled successfully for ${currentUrl}`));
|
|
1739
|
+
}
|
|
1740
|
+
} catch (networkErr) {
|
|
1741
|
+
if (networkErr.message.includes('timed out') ||
|
|
1742
|
+
networkErr.message.includes('Network.enable') ||
|
|
1743
|
+
networkErr.message.includes('timeout')) {
|
|
1744
|
+
console.warn(formatLogMessage('warn', `Network setup failed for ${currentUrl}: ${networkErr.message} - triggering browser restart`));
|
|
1745
|
+
return {
|
|
1746
|
+
url: currentUrl,
|
|
1747
|
+
rules: [],
|
|
1748
|
+
success: false,
|
|
1749
|
+
needsImmediateRestart: true,
|
|
1750
|
+
error: 'Network.enable timeout - browser restart required'
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
throw networkErr; // Re-throw other errors
|
|
1754
|
+
}
|
|
1684
1755
|
|
|
1685
1756
|
// Set up frame handling to suppress invalid URL errors
|
|
1686
1757
|
setupFrameHandling(page, forceDebug);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.86",
|
|
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": {
|