@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
JavaScript
"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