@fanboynz/network-scanner 2.0.58 → 2.0.60
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/CHANGELOG.md +970 -0
- package/README.md +31 -0
- package/lib/adblock.js +215 -179
- package/lib/cdp.js +17 -169
- package/lib/compare.js +19 -32
- package/lib/domain-cache.js +9 -7
- package/lib/fingerprint.js +9 -1
- package/lib/ghost-cursor.js +258 -0
- package/lib/grep.js +9 -13
- package/lib/nettools.js +177 -42
- package/lib/output.js +17 -30
- package/nwss.js +214 -73
- package/package.json +5 -1
package/nwss.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
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
|
|
5
|
-
const
|
|
5
|
+
const usePuppeteerCore = process.argv.includes('--use-puppeteer-core');
|
|
6
|
+
const puppeteer = usePuppeteerCore ? require('puppeteer-core') : require('puppeteer');
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const os = require('os');
|
|
8
9
|
const psl = require('psl');
|
|
@@ -31,7 +32,7 @@ const { shouldIgnoreSimilarDomain, calculateSimilarity } = require('./lib/ignore
|
|
|
31
32
|
// Graceful exit
|
|
32
33
|
const { handleBrowserExit, cleanupChromeTempFiles } = require('./lib/browserexit');
|
|
33
34
|
// Whois & Dig
|
|
34
|
-
const { createNetToolsHandler, createEnhancedDryRunCallback, validateWhoisAvailability, validateDigAvailability } = require('./lib/nettools');
|
|
35
|
+
const { createNetToolsHandler, createEnhancedDryRunCallback, validateWhoisAvailability, validateDigAvailability, enableDiskCache, getDnsCacheStats } = require('./lib/nettools');
|
|
35
36
|
// File compare
|
|
36
37
|
const { loadComparisonRules, filterUniqueRules } = require('./lib/compare');
|
|
37
38
|
// CDP functionality
|
|
@@ -42,6 +43,8 @@ const { processResults } = require('./lib/post-processing');
|
|
|
42
43
|
const { colorize, colors, messageColors, tags, formatLogMessage } = require('./lib/colorize');
|
|
43
44
|
// Enhanced mouse interaction and page simulation
|
|
44
45
|
const { performPageInteraction, createInteractionConfig, performContentClicks, humanLikeMouseMove } = require('./lib/interaction');
|
|
46
|
+
// Optional ghost-cursor support for advanced Bezier-based mouse movements
|
|
47
|
+
const { isGhostCursorAvailable, createGhostCursor, ghostMove, ghostClick, ghostRandomMove, resolveGhostCursorConfig } = require('./lib/ghost-cursor');
|
|
45
48
|
// Domain detection cache for performance optimization
|
|
46
49
|
const { createGlobalHelpers, getTotalDomainsSkipped, getDetectedDomainsCount } = require('./lib/domain-cache');
|
|
47
50
|
const { createSmartCache } = require('./lib/smart-cache'); // Smart cache system
|
|
@@ -118,7 +121,7 @@ const REALTIME_CLEANUP_THRESHOLD = 8; // Default pages to keep for realtime clea
|
|
|
118
121
|
*/
|
|
119
122
|
function detectPuppeteerVersion() {
|
|
120
123
|
try {
|
|
121
|
-
const puppeteer = require('puppeteer');
|
|
124
|
+
const puppeteer = usePuppeteerCore ? require('puppeteer-core') : require('puppeteer');
|
|
122
125
|
let versionString = null;
|
|
123
126
|
|
|
124
127
|
// Try multiple methods to get version
|
|
@@ -204,7 +207,15 @@ const localhostIndex = args.findIndex(arg => arg.startsWith('--localhost'));
|
|
|
204
207
|
if (localhostIndex !== -1) {
|
|
205
208
|
localhostIP = args[localhostIndex].includes('=') ? args[localhostIndex].split('=')[1] : '127.0.0.1';
|
|
206
209
|
}
|
|
210
|
+
const keepBrowserOpen = args.includes('--keep-open');
|
|
211
|
+
const loadExtensionPaths = [];
|
|
212
|
+
args.forEach((arg, idx) => {
|
|
213
|
+
if (arg === '--load-extension' && args[idx + 1] && !args[idx + 1].startsWith('--')) {
|
|
214
|
+
loadExtensionPaths.push(path.resolve(args[idx + 1]));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
207
217
|
const disableInteract = args.includes('--no-interact');
|
|
218
|
+
const globalGhostCursor = args.includes('--ghost-cursor');
|
|
208
219
|
const plainOutput = args.includes('--plain');
|
|
209
220
|
const enableCDP = args.includes('--cdp');
|
|
210
221
|
const dnsmasqMode = args.includes('--dnsmasq');
|
|
@@ -224,6 +235,8 @@ let cleanRules = args.includes('--clean-rules');
|
|
|
224
235
|
const clearCache = args.includes('--clear-cache');
|
|
225
236
|
const ignoreCache = args.includes('--ignore-cache');
|
|
226
237
|
const cacheRequests = args.includes('--cache-requests');
|
|
238
|
+
const dnsCacheMode = args.includes('--dns-cache');
|
|
239
|
+
if (dnsCacheMode) enableDiskCache();
|
|
227
240
|
|
|
228
241
|
let validateRulesFile = null;
|
|
229
242
|
const validateRulesIndex = args.findIndex(arg => arg === '--validate-rules');
|
|
@@ -494,22 +507,38 @@ if (validateRules || validateRulesFile) {
|
|
|
494
507
|
}
|
|
495
508
|
}
|
|
496
509
|
|
|
497
|
-
// Parse --block-ads argument for request-level ad blocking
|
|
510
|
+
// Parse --block-ads argument for request-level ad blocking (supports comma-separated lists)
|
|
498
511
|
const blockAdsIndex = args.findIndex(arg => arg.startsWith('--block-ads'));
|
|
499
512
|
if (blockAdsIndex !== -1) {
|
|
500
|
-
const
|
|
501
|
-
? args[blockAdsIndex].split('=')[1]
|
|
513
|
+
const rulesArg = args[blockAdsIndex].includes('=')
|
|
514
|
+
? args[blockAdsIndex].split('=')[1]
|
|
502
515
|
: args[blockAdsIndex + 1];
|
|
503
|
-
|
|
504
|
-
if (!
|
|
505
|
-
console.log(
|
|
516
|
+
|
|
517
|
+
if (!rulesArg) {
|
|
518
|
+
console.log('Error: No adblock rules file specified');
|
|
506
519
|
process.exit(1);
|
|
507
520
|
}
|
|
508
|
-
|
|
521
|
+
|
|
522
|
+
const rulesFiles = rulesArg.split(',').map(f => f.trim()).filter(f => f);
|
|
523
|
+
for (const file of rulesFiles) {
|
|
524
|
+
if (!fs.existsSync(file)) {
|
|
525
|
+
console.log(`Error: Adblock rules file not found: ${file}`);
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Concatenate multiple lists into a single temp file for the parser
|
|
531
|
+
let rulesFile = rulesFiles[0];
|
|
532
|
+
if (rulesFiles.length > 1) {
|
|
533
|
+
rulesFile = path.join(os.tmpdir(), `nwss-adblock-combined-${Date.now()}.txt`);
|
|
534
|
+
const combined = rulesFiles.map(f => fs.readFileSync(f, 'utf-8')).join('\n');
|
|
535
|
+
fs.writeFileSync(rulesFile, combined);
|
|
536
|
+
}
|
|
537
|
+
|
|
509
538
|
adblockEnabled = true;
|
|
510
539
|
adblockMatcher = parseAdblockRules(rulesFile, { enableLogging: forceDebug });
|
|
511
540
|
const stats = adblockMatcher.getStats();
|
|
512
|
-
if (!silentMode) console.log(messageColors.success(`Adblock enabled: Loaded ${stats.total} blocking rules from ${
|
|
541
|
+
if (!silentMode) console.log(messageColors.success(`Adblock enabled: Loaded ${stats.total} blocking rules from ${rulesFiles.length} list${rulesFiles.length > 1 ? 's' : ''}`));
|
|
513
542
|
}
|
|
514
543
|
|
|
515
544
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -546,8 +575,12 @@ General Options:
|
|
|
546
575
|
--compress-logs Compress log files with gzip (requires --dumpurls)
|
|
547
576
|
--sub-domains Output full subdomains instead of collapsing to root
|
|
548
577
|
--no-interact Disable page interactions globally
|
|
578
|
+
--ghost-cursor Use ghost-cursor Bezier mouse movements (requires: npm i ghost-cursor)
|
|
549
579
|
--custom-json <file> Use a custom config JSON file instead of config.json
|
|
550
580
|
--headful Launch browser with GUI (not headless)
|
|
581
|
+
--keep-open Keep browser open after scan completes (use with --headful)
|
|
582
|
+
--use-puppeteer-core Use puppeteer-core with system Chrome instead of bundled Chromium
|
|
583
|
+
--load-extension <path> Load unpacked Chrome extension from directory
|
|
551
584
|
--cdp Enable Chrome DevTools Protocol logging (now per-page if enabled)
|
|
552
585
|
--remove-dupes Remove duplicate domains from output (only with -o)
|
|
553
586
|
--eval-on-doc Globally enable evaluateOnNewDocument() for Fetch/XHR interception
|
|
@@ -559,6 +592,7 @@ General Options:
|
|
|
559
592
|
|
|
560
593
|
Validation Options:
|
|
561
594
|
--cache-requests Cache HTTP requests to avoid re-requesting same URLs within scan
|
|
595
|
+
--dns-cache Persist dig/whois results to disk between runs (3hr/4hr TTL)
|
|
562
596
|
--validate-config Validate config.json file and exit
|
|
563
597
|
--validate-rules [file] Validate rule file format (uses --output/--compare files if no file specified)
|
|
564
598
|
--clean-rules [file] Clean rule files by removing invalid lines and optionally duplicates (uses --output/--compare files if no file specified)
|
|
@@ -575,6 +609,7 @@ Global config.json options:
|
|
|
575
609
|
ignore_similar_ignored_domains: true/false Ignore domains similar to ignoreDomains list (default: true)
|
|
576
610
|
max_concurrent_sites: 8 Maximum concurrent site processing (1-50, default: 8)
|
|
577
611
|
resource_cleanup_interval: 80 Browser restart interval in URLs processed (1-1000, default: 80)
|
|
612
|
+
disable_ad_tagging: true/false Disable Chrome AdTagging to prevent ad frame throttling (default: true)
|
|
578
613
|
|
|
579
614
|
Per-site config.json options:
|
|
580
615
|
url: "site" or ["site1", "site2"] Single URL or list of URLs
|
|
@@ -658,6 +693,11 @@ Advanced Options:
|
|
|
658
693
|
interact_scrolling: true/false Enable scrolling simulation (default: true)
|
|
659
694
|
interact_clicks: true/false Enable element clicking simulation (default: false)
|
|
660
695
|
interact_typing: true/false Enable typing simulation (default: false)
|
|
696
|
+
cursor_mode: "ghost" Use ghost-cursor Bezier mouse (requires: npm i ghost-cursor)
|
|
697
|
+
ghost_cursor_speed: <number> Ghost-cursor speed multiplier (default: auto)
|
|
698
|
+
ghost_cursor_hesitate: <milliseconds> Delay before ghost-cursor clicks (default: 50)
|
|
699
|
+
ghost_cursor_overshoot: <pixels> Max ghost-cursor overshoot distance (default: auto)
|
|
700
|
+
ghost_cursor_duration: <milliseconds> Ghost-cursor interaction duration (default: interact_duration or 2000)
|
|
661
701
|
whois: ["term1", "term2"] Check whois data for ALL specified terms (AND logic)
|
|
662
702
|
whois-or: ["term1", "term2"] Check whois data for ANY specified term (OR logic)
|
|
663
703
|
whois_server_mode: "random" or "cycle" Server selection mode: random (default) or cycle through list
|
|
@@ -735,7 +775,8 @@ const {
|
|
|
735
775
|
whois_server_mode = 'random',
|
|
736
776
|
ignore_similar = true,
|
|
737
777
|
ignore_similar_threshold = 80,
|
|
738
|
-
ignore_similar_ignored_domains = true,
|
|
778
|
+
ignore_similar_ignored_domains = true,
|
|
779
|
+
disable_ad_tagging = true,
|
|
739
780
|
max_concurrent_sites = 6,
|
|
740
781
|
resource_cleanup_interval = 80,
|
|
741
782
|
comments: globalComments,
|
|
@@ -1430,6 +1471,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1430
1471
|
break;
|
|
1431
1472
|
}
|
|
1432
1473
|
}
|
|
1474
|
+
if (usePuppeteerCore && !executablePath) {
|
|
1475
|
+
console.error(formatLogMessage('error', '--use-puppeteer-core requires a system Chrome installation. No Chrome found in standard paths.'));
|
|
1476
|
+
process.exit(1);
|
|
1477
|
+
}
|
|
1433
1478
|
const browser = await puppeteer.launch({
|
|
1434
1479
|
// Use system Chrome if available to avoid downloads
|
|
1435
1480
|
executablePath: executablePath,
|
|
@@ -1443,7 +1488,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1443
1488
|
'--disable-blink-features=AutomationControlled',
|
|
1444
1489
|
'--no-first-run',
|
|
1445
1490
|
'--disable-default-apps',
|
|
1446
|
-
'--disable-component-extensions-with-background-pages',
|
|
1491
|
+
...(keepBrowserOpen ? [] : ['--disable-component-extensions-with-background-pages']),
|
|
1447
1492
|
// HIGH IMPACT: Normal Chrome behavior simulation
|
|
1448
1493
|
'--password-store=basic',
|
|
1449
1494
|
'--use-mock-keychain',
|
|
@@ -1457,30 +1502,29 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1457
1502
|
'--disable-background-downloads',
|
|
1458
1503
|
// DISK I/O REDUCTION: Eliminate unnecessary Chrome disk writes
|
|
1459
1504
|
'--disable-breakpad', // No crash dump files
|
|
1460
|
-
'--disable-component-update', // No component update downloads
|
|
1505
|
+
...(keepBrowserOpen ? [] : ['--disable-component-update']), // No component update downloads
|
|
1461
1506
|
'--disable-logging', // No Chrome internal log files
|
|
1462
1507
|
'--log-level=3', // Fatal errors only (suppresses verbose disk logging)
|
|
1463
1508
|
'--no-service-autorun', // No background service disk activity
|
|
1464
1509
|
'--disable-domain-reliability', // No reliability monitor disk writes
|
|
1465
|
-
// PERFORMANCE:
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
'--disable-features=BackForwardCache,AcceptCHFrame',
|
|
1510
|
+
// PERFORMANCE: Disable non-essential Chrome features in a single flag
|
|
1511
|
+
// IMPORTANT: Chrome only reads the LAST --disable-features flag, so combine all into one
|
|
1512
|
+
`--disable-features=AudioServiceOutOfProcess,VizDisplayCompositor,TranslateUI,BlinkGenPropertyTrees,Translate,BackForwardCache,AcceptCHFrame,SafeBrowsing,HttpsFirstBalancedModeAutoEnable,site-per-process,PaintHolding${disable_ad_tagging ? ',AdTagging' : ''}`,
|
|
1469
1513
|
'--disable-ipc-flooding-protection',
|
|
1470
1514
|
'--aggressive-cache-discard',
|
|
1471
1515
|
'--memory-pressure-off',
|
|
1472
1516
|
'--max_old_space_size=2048', // V8 heap limit
|
|
1473
1517
|
'--disable-prompt-on-repost', // Fixes form popup on page reload
|
|
1474
|
-
'--disable-background-networking',
|
|
1518
|
+
...(keepBrowserOpen ? [] : ['--disable-background-networking']),
|
|
1475
1519
|
'--no-sandbox',
|
|
1476
1520
|
'--disable-setuid-sandbox',
|
|
1477
|
-
'--disable-features=SafeBrowsing',
|
|
1478
1521
|
'--disable-dev-shm-usage',
|
|
1479
|
-
'--disable-sync',
|
|
1522
|
+
...(keepBrowserOpen ? [] : ['--disable-sync']),
|
|
1480
1523
|
'--mute-audio',
|
|
1481
1524
|
'--disable-translate',
|
|
1482
1525
|
'--window-size=1920,1080',
|
|
1483
|
-
'--disable-extensions',
|
|
1526
|
+
...(keepBrowserOpen ? [] : ['--disable-extensions', '--disable-component-update']),
|
|
1527
|
+
...(loadExtensionPaths.length ? [`--load-extension=${loadExtensionPaths.join(',')}`, '--enable-extensions'] : []),
|
|
1484
1528
|
'--no-default-browser-check',
|
|
1485
1529
|
'--safebrowsing-disable-auto-update',
|
|
1486
1530
|
'--ignore-ssl-errors',
|
|
@@ -1489,18 +1533,15 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1489
1533
|
'--ignore-certificate-errors-ca-list',
|
|
1490
1534
|
'--disable-web-security',
|
|
1491
1535
|
'--allow-running-insecure-content',
|
|
1492
|
-
'--disable-features=HttpsFirstBalancedModeAutoEnable',
|
|
1493
1536
|
// Puppeteer 23.x: Enhanced performance and stability args
|
|
1494
1537
|
'--disable-renderer-backgrounding',
|
|
1495
1538
|
'--disable-backgrounding-occluded-windows',
|
|
1496
1539
|
'--disable-background-timer-throttling',
|
|
1497
|
-
'--disable-features=site-per-process', // Better for single-site scanning
|
|
1498
1540
|
'--no-zygote', // Better process isolation
|
|
1499
1541
|
// PERFORMANCE: Process and memory reduction for high concurrency
|
|
1500
1542
|
'--renderer-process-limit=10', // Cap renderer processes (default: unlimited)
|
|
1501
1543
|
'--disable-accelerated-2d-canvas', // Software canvas only (we spoof it anyway)
|
|
1502
1544
|
'--disable-hang-monitor', // Remove per-renderer hang check overhead
|
|
1503
|
-
'--disable-features=PaintHolding', // Don't hold frames in renderer memory
|
|
1504
1545
|
'--js-flags=--max-old-space-size=512', // Cap V8 heap per renderer to 512MB
|
|
1505
1546
|
...extraArgs,
|
|
1506
1547
|
],
|
|
@@ -3420,53 +3461,115 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3420
3461
|
}
|
|
3421
3462
|
}
|
|
3422
3463
|
|
|
3423
|
-
if (interactEnabled && !disableInteract) {
|
|
3424
|
-
if (forceDebug) console.log(formatLogMessage('debug', `interaction simulation enabled for ${currentUrl}`));
|
|
3425
|
-
|
|
3426
|
-
// Mark page as processing during interactions
|
|
3427
|
-
updatePageUsage(page, true);
|
|
3428
|
-
// Use enhanced interaction module with hard abort timeout
|
|
3429
|
-
const INTERACTION_HARD_TIMEOUT = 15000;
|
|
3430
|
-
try {
|
|
3431
|
-
await Promise.race([
|
|
3432
|
-
performPageInteraction(page, currentUrl, interactionConfig, forceDebug),
|
|
3433
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('interaction hard timeout')), INTERACTION_HARD_TIMEOUT))
|
|
3434
|
-
]);
|
|
3435
|
-
} catch (interactTimeoutErr) {
|
|
3436
|
-
if (forceDebug) console.log(formatLogMessage('debug', `[interaction] Aborted after ${INTERACTION_HARD_TIMEOUT}ms: ${interactTimeoutErr.message}`));
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
3464
|
const delayMs = DEFAULT_DELAY;
|
|
3441
|
-
|
|
3465
|
+
|
|
3442
3466
|
// Optimized delays for Puppeteer 23.x performance
|
|
3443
3467
|
const isFastSite = timeout <= TIMEOUTS.FAST_SITE_THRESHOLD;
|
|
3444
3468
|
const networkIdleTime = TIMEOUTS.NETWORK_IDLE; // Balanced: 2s for reliable network detection
|
|
3445
3469
|
const networkIdleTimeout = Math.min(timeout / 2, TIMEOUTS.NETWORK_IDLE_MAX); // Balanced: 10s timeout
|
|
3446
3470
|
const actualDelay = Math.min(delayMs, TIMEOUTS.NETWORK_IDLE); // Balanced: 2s delay for stability
|
|
3447
|
-
|
|
3448
|
-
//
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3471
|
+
|
|
3472
|
+
// Build delay promise (networkIdle + delay + optional flowProxy delay)
|
|
3473
|
+
const delayPromise = (async () => {
|
|
3474
|
+
if (page && !page.isClosed()) {
|
|
3475
|
+
try {
|
|
3476
|
+
await page.waitForNetworkIdle({
|
|
3477
|
+
idleTime: networkIdleTime,
|
|
3478
|
+
timeout: networkIdleTimeout
|
|
3479
|
+
});
|
|
3480
|
+
} catch (networkIdleErr) {
|
|
3481
|
+
if (forceDebug) console.log(formatLogMessage('debug', `Network idle wait failed: ${networkIdleErr.message}`));
|
|
3482
|
+
}
|
|
3458
3483
|
}
|
|
3459
|
-
|
|
3484
|
+
await fastTimeout(actualDelay);
|
|
3485
|
+
if (flowproxyDetection) {
|
|
3486
|
+
const additionalDelay = Math.min(siteConfig.flowproxy_additional_delay || 3000, 3000);
|
|
3487
|
+
if (forceDebug) console.log(formatLogMessage('debug', `Applying flowProxy additional delay: ${additionalDelay}ms`));
|
|
3488
|
+
await fastTimeout(additionalDelay);
|
|
3489
|
+
}
|
|
3490
|
+
})();
|
|
3491
|
+
|
|
3492
|
+
// Build interaction promise — runs concurrently with delay
|
|
3493
|
+
const interactPromise = (async () => {
|
|
3494
|
+
if (!(interactEnabled && !disableInteract)) return;
|
|
3495
|
+
if (forceDebug) console.log(formatLogMessage('debug', `interaction simulation enabled for ${currentUrl}`));
|
|
3460
3496
|
|
|
3461
|
-
|
|
3462
|
-
|
|
3497
|
+
// Mark page as processing during interactions
|
|
3498
|
+
updatePageUsage(page, true);
|
|
3499
|
+
const INTERACTION_HARD_TIMEOUT = 15000;
|
|
3463
3500
|
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3501
|
+
// Check if ghost-cursor mode is enabled for this site
|
|
3502
|
+
const ghostConfig = resolveGhostCursorConfig(siteConfig, globalGhostCursor, forceDebug);
|
|
3503
|
+
|
|
3504
|
+
try {
|
|
3505
|
+
if (ghostConfig) {
|
|
3506
|
+
// Ghost-cursor mode: Bezier-based mouse movements
|
|
3507
|
+
if (forceDebug) console.log(formatLogMessage('debug', `[ghost-cursor] Using ghost-cursor for ${currentUrl}`));
|
|
3508
|
+
const cursor = createGhostCursor(page, { forceDebug });
|
|
3509
|
+
if (cursor) {
|
|
3510
|
+
await Promise.race([
|
|
3511
|
+
(async () => {
|
|
3512
|
+
const viewport = page.viewport() || { width: 1200, height: 800 };
|
|
3513
|
+
const ghostDuration = ghostConfig.duration || 2000;
|
|
3514
|
+
const ghostStart = Date.now();
|
|
3515
|
+
const ghostTimeLeft = () => ghostDuration - (Date.now() - ghostStart);
|
|
3516
|
+
|
|
3517
|
+
// Time-based Bezier mouse movements — runs for ghostDuration ms
|
|
3518
|
+
while (ghostTimeLeft() > 200) {
|
|
3519
|
+
const toX = Math.floor(Math.random() * (viewport.width - 100)) + 50;
|
|
3520
|
+
const toY = Math.floor(Math.random() * (viewport.height - 100)) + 50;
|
|
3521
|
+
await ghostMove(cursor, toX, toY, {
|
|
3522
|
+
moveSpeed: ghostConfig.moveSpeed,
|
|
3523
|
+
overshootThreshold: ghostConfig.overshootThreshold,
|
|
3524
|
+
forceDebug
|
|
3525
|
+
});
|
|
3526
|
+
if (ghostTimeLeft() > 100) {
|
|
3527
|
+
await new Promise(r => setTimeout(r, 25 + Math.random() * 75));
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
if (ghostTimeLeft() > 100 && Math.random() < 0.3) {
|
|
3531
|
+
await ghostRandomMove(cursor, { forceDebug });
|
|
3532
|
+
}
|
|
3533
|
+
if (interactionConfig.includeElementClicks && ghostTimeLeft() > 100) {
|
|
3534
|
+
const clickX = Math.floor(viewport.width * 0.2 + Math.random() * viewport.width * 0.6);
|
|
3535
|
+
const clickY = Math.floor(viewport.height * 0.2 + Math.random() * viewport.height * 0.6);
|
|
3536
|
+
await ghostClick(cursor, { x: clickX, y: clickY }, {
|
|
3537
|
+
hesitate: ghostConfig.hesitate,
|
|
3538
|
+
forceDebug
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
if (interactionConfig.includeScrolling) {
|
|
3542
|
+
await performPageInteraction(page, currentUrl, {
|
|
3543
|
+
...interactionConfig,
|
|
3544
|
+
mouseMovements: 0,
|
|
3545
|
+
includeElementClicks: false,
|
|
3546
|
+
includeTyping: false
|
|
3547
|
+
}, forceDebug);
|
|
3548
|
+
}
|
|
3549
|
+
})(),
|
|
3550
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('ghost-cursor interaction hard timeout')), INTERACTION_HARD_TIMEOUT))
|
|
3551
|
+
]);
|
|
3552
|
+
} else {
|
|
3553
|
+
if (forceDebug) console.log(formatLogMessage('debug', '[ghost-cursor] Falling back to built-in mouse'));
|
|
3554
|
+
await Promise.race([
|
|
3555
|
+
performPageInteraction(page, currentUrl, interactionConfig, forceDebug),
|
|
3556
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('interaction hard timeout')), INTERACTION_HARD_TIMEOUT))
|
|
3557
|
+
]);
|
|
3558
|
+
}
|
|
3559
|
+
} else {
|
|
3560
|
+
// Standard built-in mouse interaction
|
|
3561
|
+
await Promise.race([
|
|
3562
|
+
performPageInteraction(page, currentUrl, interactionConfig, forceDebug),
|
|
3563
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('interaction hard timeout')), INTERACTION_HARD_TIMEOUT))
|
|
3564
|
+
]);
|
|
3565
|
+
}
|
|
3566
|
+
} catch (interactTimeoutErr) {
|
|
3567
|
+
if (forceDebug) console.log(formatLogMessage('debug', `[interaction] Aborted after ${INTERACTION_HARD_TIMEOUT}ms: ${interactTimeoutErr.message}`));
|
|
3568
|
+
}
|
|
3569
|
+
})();
|
|
3570
|
+
|
|
3571
|
+
// Run delay and mouse interaction concurrently — mouse moves while page settles
|
|
3572
|
+
await Promise.all([delayPromise, interactPromise]);
|
|
3470
3573
|
|
|
3471
3574
|
// Use fast timeout helper for consistent Puppeteer 23.x compatibility
|
|
3472
3575
|
|
|
@@ -3898,12 +4001,14 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3898
4001
|
}
|
|
3899
4002
|
}
|
|
3900
4003
|
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4004
|
+
if (!keepBrowserOpen) {
|
|
4005
|
+
try {
|
|
4006
|
+
untrackPage(page);
|
|
4007
|
+
await page.close();
|
|
4008
|
+
if (forceDebug) console.log(formatLogMessage('debug', `Page closed for ${currentUrl}`));
|
|
4009
|
+
} catch (pageCloseErr) {
|
|
4010
|
+
if (forceDebug) console.log(formatLogMessage('debug', `Failed to close page for ${currentUrl}: ${pageCloseErr.message}`));
|
|
4011
|
+
}
|
|
3907
4012
|
}
|
|
3908
4013
|
}
|
|
3909
4014
|
}
|
|
@@ -4517,6 +4622,24 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
4517
4622
|
wgDisconnectAll(forceDebug);
|
|
4518
4623
|
ovpnDisconnectAll(forceDebug);
|
|
4519
4624
|
|
|
4625
|
+
// Keep browser open if --keep-open flag is set (useful with --headful for inspection)
|
|
4626
|
+
if (keepBrowserOpen && !launchHeadless) {
|
|
4627
|
+
console.log(messageColors.info('Browser kept open.') + ' Close the browser window or press Ctrl+C to exit.');
|
|
4628
|
+
const cleanup = async () => {
|
|
4629
|
+
try {
|
|
4630
|
+
if (browser.isConnected()) await browser.close();
|
|
4631
|
+
} catch {}
|
|
4632
|
+
process.exit(0);
|
|
4633
|
+
};
|
|
4634
|
+
process.on('SIGINT', cleanup);
|
|
4635
|
+
process.on('SIGTERM', cleanup);
|
|
4636
|
+
await new Promise((resolve) => {
|
|
4637
|
+
browser.on('disconnected', resolve);
|
|
4638
|
+
});
|
|
4639
|
+
process.removeListener('SIGINT', cleanup);
|
|
4640
|
+
process.removeListener('SIGTERM', cleanup);
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4520
4643
|
// Perform comprehensive final cleanup using enhanced browserexit module
|
|
4521
4644
|
if (forceDebug) console.log(formatLogMessage('debug', `Starting comprehensive browser cleanup...`));
|
|
4522
4645
|
|
|
@@ -4541,7 +4664,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
4541
4664
|
if (forceDebug) {
|
|
4542
4665
|
console.log(formatLogMessage('debug', `Final cleanup results: ${cleanupResult.success ? 'success' : 'failed'}`));
|
|
4543
4666
|
console.log(formatLogMessage('debug', `Browser closed: ${cleanupResult.browserClosed}, Temp files cleaned: ${cleanupResult.tempFilesCleanedCount || 0}, User data cleaned: ${cleanupResult.userDataCleaned}`));
|
|
4544
|
-
|
|
4667
|
+
|
|
4545
4668
|
if (cleanupResult.errors.length > 0) {
|
|
4546
4669
|
cleanupResult.errors.forEach(err => console.log(formatLogMessage('debug', `Cleanup error: ${err}`)));
|
|
4547
4670
|
}
|
|
@@ -4549,10 +4672,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
4549
4672
|
|
|
4550
4673
|
// Final aggressive cleanup to catch any remaining temp files
|
|
4551
4674
|
if (forceDebug) console.log(formatLogMessage('debug', 'Performing final aggressive temp file cleanup...'));
|
|
4552
|
-
await cleanupChromeTempFiles({
|
|
4553
|
-
includeSnapTemp: true,
|
|
4675
|
+
await cleanupChromeTempFiles({
|
|
4676
|
+
includeSnapTemp: true,
|
|
4554
4677
|
forceDebug,
|
|
4555
|
-
comprehensive: true
|
|
4678
|
+
comprehensive: true
|
|
4556
4679
|
});
|
|
4557
4680
|
await fastTimeout(TIMEOUTS.BROWSER_STABILIZE_DELAY); // Give filesystem time to sync
|
|
4558
4681
|
|
|
@@ -4591,6 +4714,24 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
4591
4714
|
if (ignoreCache && forceDebug) {
|
|
4592
4715
|
console.log(messageColors.info('Cache:') + ` Smart caching was disabled`);
|
|
4593
4716
|
}
|
|
4717
|
+
// DNS cache statistics
|
|
4718
|
+
const dnsStats = getDnsCacheStats();
|
|
4719
|
+
if (dnsStats.digHits + dnsStats.digMisses > 0 || dnsStats.whoisHits + dnsStats.whoisMisses > 0) {
|
|
4720
|
+
const parts = [];
|
|
4721
|
+
if (dnsStats.digHits + dnsStats.digMisses > 0) {
|
|
4722
|
+
parts.push(`${messageColors.success(dnsStats.digHits)} dig cached, ${messageColors.timing(dnsStats.digMisses)} fresh`);
|
|
4723
|
+
}
|
|
4724
|
+
if (dnsStats.whoisHits + dnsStats.whoisMisses > 0) {
|
|
4725
|
+
parts.push(`${messageColors.success(dnsStats.whoisHits)} whois cached, ${messageColors.timing(dnsStats.whoisMisses)} fresh`);
|
|
4726
|
+
}
|
|
4727
|
+
console.log(messageColors.info('DNS cache:') + ` ${parts.join(' | ')}`);
|
|
4728
|
+
if (dnsStats.freshDig.length > 0) {
|
|
4729
|
+
console.log(messageColors.info(' Fresh dig:') + ` ${dnsStats.freshDig.join(', ')}`);
|
|
4730
|
+
}
|
|
4731
|
+
if (dnsStats.freshWhois.length > 0) {
|
|
4732
|
+
console.log(messageColors.info(' Fresh whois:') + ` ${dnsStats.freshWhois.join(', ')}`);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4594
4735
|
}
|
|
4595
4736
|
|
|
4596
4737
|
// Clean process termination
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.60",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"lint": "eslint *.js lib/*.js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"ghost-cursor": "^1.4.2",
|
|
13
14
|
"lru-cache": "^10.4.3",
|
|
14
15
|
"p-limit": "^4.0.0",
|
|
15
16
|
"psl": "^1.15.0",
|
|
@@ -48,6 +49,9 @@
|
|
|
48
49
|
"url": "https://github.com/ryanbr/network-scanner/issues"
|
|
49
50
|
},
|
|
50
51
|
"homepage": "https://github.com/ryanbr/network-scanner",
|
|
52
|
+
"optionalDependencies": {
|
|
53
|
+
"puppeteer-core": ">=20.0.0"
|
|
54
|
+
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"eslint": "^10.0.2",
|
|
53
57
|
"globals": "^16.3.0"
|