UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

278 lines โ€ข 9.84 kB
"use strict"; /** * Browser Pool Manager for optimized resource usage and performance */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserPoolManager = void 0; const playwright_1 = require("playwright"); class BrowserPoolManager { constructor(options = {}) { this.pool = new Map(); this.queue = []; this.cleanupInterval = null; this.metrics = { created: 0, reused: 0, destroyed: 0, errors: 0, activeConnections: 0, totalRequests: 0 }; this.options = { maxConcurrent: options.maxConcurrent || 3, maxIdleTime: options.maxIdleTime || 30000, // 30 seconds browserType: options.browserType || 'chromium', launchOptions: options.launchOptions || { headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-web-security', '--disable-features=VizDisplayCompositor', '--memory-pressure-off', '--max_old_space_size=4096' ] }, enableResourceOptimization: options.enableResourceOptimization !== false }; // Start cleanup interval this.startCleanup(); } /** * Get a browser instance from the pool */ async acquire() { this.metrics.totalRequests++; // Try to reuse an existing browser let pooledBrowser = this.findAvailableBrowser(); if (!pooledBrowser && this.pool.size < this.options.maxConcurrent) { // Create new browser if under limit pooledBrowser = await this.createBrowser(); } else if (!pooledBrowser) { // Wait for available browser pooledBrowser = await this.waitForAvailableBrowser(); } if (!pooledBrowser) { throw new Error('Unable to acquire browser from pool'); } // Mark as in use pooledBrowser.inUse = true; pooledBrowser.lastUsed = Date.now(); this.metrics.activeConnections++; if (this.metrics.reused > 0 || this.pool.size > 1) { this.metrics.reused++; } const release = async () => { pooledBrowser.inUse = false; pooledBrowser.lastUsed = Date.now(); this.metrics.activeConnections--; // Add back to queue this.queue.push(pooledBrowser.id); }; return { browser: pooledBrowser.browser, context: pooledBrowser.context, release }; } /** * Find an available browser in the pool */ findAvailableBrowser() { for (const [id, pooledBrowser] of this.pool.entries()) { if (!pooledBrowser.inUse && pooledBrowser.browser.isConnected()) { return pooledBrowser; } } return null; } /** * Wait for an available browser */ async waitForAvailableBrowser() { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Timeout waiting for available browser')); }, 10000); // 10 second timeout const check = () => { const available = this.findAvailableBrowser(); if (available) { clearTimeout(timeout); resolve(available); } else { setTimeout(check, 100); } }; check(); }); } /** * Create a new browser instance */ async createBrowser() { const id = `browser-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; try { let browser; switch (this.options.browserType) { case 'firefox': browser = await playwright_1.firefox.launch(this.options.launchOptions); break; case 'webkit': browser = await playwright_1.webkit.launch(this.options.launchOptions); break; default: browser = await playwright_1.chromium.launch(this.options.launchOptions); } // Validate browser instance if (!browser || typeof browser.newContext !== 'function') { throw new Error(`Invalid browser instance: browser.newContext is not a function (type: ${typeof browser})`); } // Create optimized browser context const context = await browser.newContext({ ignoreHTTPSErrors: true, bypassCSP: true, ...(this.options.enableResourceOptimization && { // Block unnecessary resources for performance }) }); // Optimize context for performance if (this.options.enableResourceOptimization) { await context.route('**/*', (route) => { const resourceType = route.request().resourceType(); // Block non-essential resources if (['image', 'font', 'media'].includes(resourceType)) { const url = route.request().url(); if (url.includes('tracking') || url.includes('analytics') || url.includes('ads')) { route.abort(); return; } } route.continue(); }); } const pooledBrowser = { id, browser, context, lastUsed: Date.now(), inUse: false }; this.pool.set(id, pooledBrowser); this.queue.push(id); this.metrics.created++; console.log(`๐ŸŒ Created browser ${id} (Pool size: ${this.pool.size})`); return pooledBrowser; } catch (error) { this.metrics.errors++; console.error(`โŒ Failed to create browser:`, error); throw error; } } /** * Start cleanup interval to remove idle browsers */ startCleanup() { this.cleanupInterval = setInterval(() => { this.cleanup(); }, this.options.maxIdleTime / 2); } /** * Clean up idle browsers */ async cleanup() { const now = Date.now(); const toRemove = []; for (const [id, pooledBrowser] of this.pool.entries()) { const isIdle = !pooledBrowser.inUse && (now - pooledBrowser.lastUsed) > this.options.maxIdleTime; const isDisconnected = !pooledBrowser.browser.isConnected(); if (isIdle || isDisconnected) { toRemove.push(id); } } for (const id of toRemove) { await this.destroyBrowser(id); } if (toRemove.length > 0) { console.log(`๐Ÿงน Cleaned up ${toRemove.length} idle browsers`); } } /** * Destroy a specific browser */ async destroyBrowser(id) { const pooledBrowser = this.pool.get(id); if (!pooledBrowser) return; try { await pooledBrowser.context.close(); await pooledBrowser.browser.close(); this.metrics.destroyed++; } catch (error) { console.error(`Error closing browser ${id}:`, error); } this.pool.delete(id); this.queue = this.queue.filter(queueId => queueId !== id); console.log(`๐Ÿ—‘๏ธ Destroyed browser ${id} (Pool size: ${this.pool.size})`); } /** * Get pool metrics */ getMetrics() { return { ...this.metrics, poolSize: this.pool.size, queueLength: this.queue.length, efficiency: this.metrics.totalRequests > 0 ? (this.metrics.reused / this.metrics.totalRequests) * 100 : 0 }; } /** * Warm up the pool by creating initial browsers */ async warmUp(count = 1) { console.log(`๐Ÿ”ฅ Warming up browser pool with ${count} browsers...`); const warmupPromises = []; for (let i = 0; i < Math.min(count, this.options.maxConcurrent); i++) { warmupPromises.push(this.createBrowser()); } await Promise.all(warmupPromises); console.log(`โœ… Browser pool warmed up with ${this.pool.size} browsers`); } /** * Gracefully shutdown all browsers */ async shutdown() { console.log('๐Ÿ”„ Shutting down browser pool...'); if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } const shutdownPromises = []; for (const id of this.pool.keys()) { shutdownPromises.push(this.destroyBrowser(id)); } await Promise.all(shutdownPromises); console.log('โœ… Browser pool shutdown complete'); } /** * Get pool status */ getStatus() { return { totalBrowsers: this.pool.size, activeBrowsers: Array.from(this.pool.values()).filter(b => b.inUse).length, idleBrowsers: Array.from(this.pool.values()).filter(b => !b.inUse).length, queueLength: this.queue.length, metrics: this.getMetrics() }; } } exports.BrowserPoolManager = BrowserPoolManager; exports.default = BrowserPoolManager; //# sourceMappingURL=browser-pool-manager.js.map