@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.
@@ -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: Check response time performance
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 = true;
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
- const health = await checkBrowserHealth(browserInstance, 5000); // Faster timeout
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
- return health.healthy && connectivity.connected && connectivity.cdpResponsive;
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
- // Convert wildcard pattern to regex
22
- const regexPattern = pattern
23
- .replace(/\./g, '\\.') // Escape dots
24
- .replace(/\*/g, '.*'); // Convert * to .*
25
- return new RegExp(`^${regexPattern}$`).test(domain);
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
 
@@ -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 3: Validate results
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.84 ===
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.84'; // Script version
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
- console.warn(formatLogMessage('warn', `[evalOnDoc] Failed to set up Fetch/XHR interception for ${currentUrl}: ${evalErr.message}`));
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
- await page.setRequestInterception(true);
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.84",
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": {