UNPKG

@fanboynz/network-scanner

Version:

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.

348 lines (300 loc) 11.1 kB
/** * Browser health monitoring module for nwss.js * Provides health checks and recovery mechanisms to prevent protocol timeouts */ const { formatLogMessage, messageColors } = require('./colorize'); /** * Checks if browser instance is still responsive * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance * @param {number} timeout - Timeout in milliseconds (default: 5000) * @returns {Promise<object>} Health check result */ async function checkBrowserHealth(browserInstance, timeout = 5000) { const healthResult = { healthy: false, pageCount: 0, error: null, responseTime: 0, recommendations: [], criticalError: false }; const startTime = Date.now(); try { // Test 1: Check if browser is connected if (!browserInstance || browserInstance.process() === null) { healthResult.error = 'Browser process not running'; healthResult.recommendations.push('Create new browser instance'); healthResult.criticalError = true; return healthResult; } // Test 2: Try to get pages list with timeout const pages = await Promise.race([ browserInstance.pages(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser unresponsive - pages() timeout')), timeout) ) ]); healthResult.pageCount = pages.length; healthResult.responseTime = Date.now() - startTime; // Test 3: Check for excessive pages (memory leak indicator) if (pages.length > 20) { healthResult.recommendations.push('Too many open pages - consider browser restart'); } // Test 4: Try to create a test page to verify browser functionality let testPage = null; try { testPage = await Promise.race([ browserInstance.newPage(), new Promise((_, reject) => setTimeout(() => reject(new Error('Page creation timeout')), timeout) ) ]); // Quick test navigation to about:blank await Promise.race([ testPage.goto('about:blank'), new Promise((_, reject) => setTimeout(() => reject(new Error('Navigation timeout')), timeout) ) ]); await testPage.close(); } catch (pageTestError) { if (testPage && !testPage.isClosed()) { try { await testPage.close(); } catch (e) { /* ignore */ } } healthResult.error = `Page creation/navigation failed: ${pageTestError.message}`; if (isCriticalProtocolError(pageTestError)) { healthResult.recommendations.push('Browser restart required - critical protocol error'); healthResult.criticalError = true; } else { healthResult.recommendations.push('Browser restart recommended'); } return healthResult; } // Test 5: Check response time performance if (healthResult.responseTime > 3000) { healthResult.recommendations.push('Slow browser response - consider restart'); } // If all tests pass healthResult.healthy = true; } catch (error) { healthResult.error = error.message; healthResult.responseTime = Date.now() - startTime; // Categorize error types for better recommendations if (error.message.includes('Runtime.callFunctionOn timed out') || error.message.includes('Protocol error') || error.message.includes('Target closed')) { healthResult.recommendations.push('Browser restart required - critical protocol error'); healthResult.criticalError = true; } else if (error.message.includes('timeout') || error.message.includes('unresponsive')) { healthResult.recommendations.push('Browser restart required - unresponsive'); healthResult.criticalError = true; } else { healthResult.recommendations.push('Browser restart recommended - unknown error'); } } return healthResult; } /** * Checks memory usage of browser process (if available) * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance * @returns {Promise<object>} Memory usage information */ async function checkBrowserMemory(browserInstance) { const memoryResult = { available: false, usage: null, error: null, recommendations: [] }; try { const browserProcess = browserInstance.process(); if (!browserProcess || !browserProcess.pid) { memoryResult.error = 'No browser process available'; return memoryResult; } // Try to get process memory info (Linux/Unix) try { const { execSync } = require('child_process'); const memInfo = execSync(`ps -p ${browserProcess.pid} -o rss=`, { encoding: 'utf8', timeout: 2000 }); const memoryKB = parseInt(memInfo.trim()); if (!isNaN(memoryKB)) { const memoryMB = Math.round(memoryKB / 1024); memoryResult.available = true; memoryResult.usage = { rss: memoryKB, rssMB: memoryMB }; // Memory usage recommendations if (memoryMB > 1000) { memoryResult.recommendations.push(`High memory usage: ${memoryMB}MB - restart recommended`); } else if (memoryMB > 500) { memoryResult.recommendations.push(`Elevated memory usage: ${memoryMB}MB - monitor closely`); } } } catch (psError) { memoryResult.error = `Memory check failed: ${psError.message}`; } } catch (error) { memoryResult.error = error.message; } return memoryResult; } /** * Detects critical protocol errors that require immediate browser restart */ function isCriticalProtocolError(error) { if (!error || !error.message) return false; const criticalErrors = [ 'Runtime.callFunctionOn timed out', 'Protocol error', 'Target closed', 'Session closed', 'Connection closed', 'Browser has been closed', 'Runtime.evaluate timed out' ]; return criticalErrors.some(criticalError => error.message.includes(criticalError) ); } /** * Performs comprehensive browser health assessment * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance * @param {object} options - Health check options * @returns {Promise<object>} Comprehensive health report */ async function performHealthAssessment(browserInstance, options = {}) { const { timeout = 5000, checkMemory = true, forceDebug = false } = options; const assessment = { overall: 'unknown', timestamp: new Date().toISOString(), browser: {}, memory: {}, recommendations: [], needsRestart: false }; if (forceDebug) { console.log(formatLogMessage('debug', 'Starting browser health assessment...')); } // Browser responsiveness check assessment.browser = await checkBrowserHealth(browserInstance, timeout); // Memory usage check (if enabled and available) if (checkMemory) { assessment.memory = await checkBrowserMemory(browserInstance); } // Combine recommendations assessment.recommendations = [ ...assessment.browser.recommendations, ...(assessment.memory.recommendations || []) ]; // Determine overall health and restart necessity if (!assessment.browser.healthy) { assessment.overall = 'unhealthy'; assessment.needsRestart = true; } else if (assessment.browser.criticalError) { assessment.overall = 'critical'; assessment.needsRestart = true; } else if (assessment.recommendations.length > 0) { assessment.overall = 'degraded'; assessment.needsRestart = assessment.recommendations.some(rec => rec.includes('restart required') || rec.includes('High memory usage') ); } else { assessment.overall = 'healthy'; assessment.needsRestart = false; } if (forceDebug) { console.log(formatLogMessage('debug', `Health assessment complete: ${assessment.overall}`)); if (assessment.recommendations.length > 0) { console.log(formatLogMessage('debug', `Recommendations: ${assessment.recommendations.join(', ')}`)); } } return assessment; } /** * Monitors browser health and suggests actions for nwss.js integration * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance * @param {object} context - Context information for logging * @param {object} options - Monitoring options * @returns {Promise<object>} Monitoring result with action suggestions */ async function monitorBrowserHealth(browserInstance, context = {}, options = {}) { const { siteIndex = 0, totalSites = 0, urlsSinceCleanup = 0, cleanupInterval = 40, forceDebug = false, silentMode = false } = options; const result = { shouldRestart: false, shouldContinue: true, reason: null, assessment: null }; try { // Perform health assessment const assessment = await performHealthAssessment(browserInstance, { timeout: 5000, checkMemory: true, forceDebug }); result.assessment = assessment; // Decision logic for restart if (assessment.browser.criticalError) { result.shouldRestart = true; result.reason = `Critical protocol error detected - immediate restart required`; } else if (assessment.needsRestart) { result.shouldRestart = true; result.reason = `Browser health: ${assessment.overall} - ${assessment.recommendations[0] || 'restart needed'}`; } else if (urlsSinceCleanup >= cleanupInterval) { result.shouldRestart = true; result.reason = `Scheduled cleanup after ${urlsSinceCleanup} URLs`; } else if (assessment.browser.responseTime > 4000) { result.shouldRestart = true; result.reason = `Slow browser response: ${assessment.browser.responseTime}ms`; } // Logging if (!silentMode && result.shouldRestart) { const progress = totalSites > 0 ? ` (${siteIndex + 1}/${totalSites})` : ''; console.log(`\n${messageColors.fileOp('?? Browser restart needed')} before site${progress}: ${result.reason}`); } if (forceDebug && !result.shouldRestart) { console.log(formatLogMessage('debug', `Browser health OK - continuing (pages: ${assessment.browser.pageCount}, response: ${assessment.browser.responseTime}ms)`)); } } catch (monitorError) { result.shouldRestart = true; result.reason = `Health monitoring failed: ${monitorError.message}`; if (forceDebug) { console.log(formatLogMessage('debug', `Browser health monitoring error: ${monitorError.message}`)); } } return result; } /** * Simple health check function for quick integration * @param {import('puppeteer').Browser} browserInstance - Puppeteer browser instance * @returns {Promise<boolean>} True if browser is healthy, false otherwise */ async function isBrowserHealthy(browserInstance) { try { const health = await checkBrowserHealth(browserInstance, 3000); return health.healthy; } catch (error) { return false; } } module.exports = { checkBrowserHealth, checkBrowserMemory, performHealthAssessment, monitorBrowserHealth, isBrowserHealthy, isCriticalProtocolError };