const puppeteer = require('puppeteer'); const POOL_SIZE = parseInt(process.env.BROWSER_POOL_SIZE) || 5; const VIEWPORT_WIDTH = 3240; const VIEWPORT_HEIGHT = 3240; let browsers = []; let availablePages = []; let initPromise = null; async function initPool() { if (initPromise) return initPromise; initPromise = (async () => { console.log(`Initializing browser pool with ${POOL_SIZE} instances...`); for (let i = 0; i < POOL_SIZE; i++) { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); browsers.push(browser); const page = await browser.newPage(); await page.setViewport({ width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }); availablePages.push(page); } console.log(`Browser pool ready with ${POOL_SIZE} pages`); })(); return initPromise; } async function acquirePage() { await initPool(); // wait for available page while (availablePages.length === 0) { await new Promise(resolve => setTimeout(resolve, 50)); } return availablePages.pop(); } function releasePage(page) { availablePages.push(page); } // renders html and returns screenshot as buffer async function renderHtml(html, waitTime = 1000) { const page = await acquirePage(); try { await page.setContent(html, { waitUntil: 'networkidle0' }); // wait for any animations or laoding await new Promise(resolve => setTimeout(resolve, waitTime)); const screenshot = await page.screenshot({ type: 'png', fullPage: false }); return screenshot; } finally { releasePage(page); } } async function shutdownPool() { console.log('Shutting down browser pool...'); for (const browser of browsers) { await browser.close(); } browsers = []; availablePages = []; initPromise = null; console.log('Browser pool shut down'); } // Graceful shutdown handlers process.on('SIGINT', async () => { await shutdownPool(); process.exit(0); }); process.on('SIGTERM', async () => { await shutdownPool(); process.exit(0); }); module.exports = { initPool, renderHtml, shutdownPool, POOL_SIZE };