import log4js from 'log4js'; import puppeteer from 'puppeteer'; import config from '../config.js'; import { getPuppeteerLaunchOptions } from '../puppeteerLaunchOptions.js'; const logger = log4js.getLogger('PDF Factory'); logger.level = config.server.logLevel; let browser = null; let launchPromise = null; let shutdownRegistered = false; function isBrowserConnected() { return browser != null && browser.connected; } async function launchBrowser() { logger.info('Launching persistent Puppeteer browser...'); const instance = await puppeteer.launch(getPuppeteerLaunchOptions()); instance.on('disconnected', () => { if (browser === instance) { logger.warn('Puppeteer browser disconnected'); browser = null; launchPromise = null; } }); logger.info('Persistent Puppeteer browser ready'); return instance; } function registerShutdownHandlers() { if (shutdownRegistered) { return; } shutdownRegistered = true; const shutdown = async () => { await closePDFBrowser(); }; process.once('SIGINT', shutdown); process.once('SIGTERM', shutdown); process.once('beforeExit', shutdown); } export async function startPDFBrowser() { if (process.env.NODE_ENV !== 'test') { registerShutdownHandlers(); } if (isBrowserConnected()) { return browser; } if (launchPromise == null) { launchPromise = launchBrowser() .then((instance) => { browser = instance; return instance; }) .catch((error) => { launchPromise = null; throw error; }); } return launchPromise; } export async function closePDFBrowser() { const instance = browser; browser = null; launchPromise = null; if (instance != null) { try { await instance.close(); } catch (error) { logger.warn('Error closing Puppeteer browser:', error.message); } } } async function withPage(run) { let page; try { const instance = await startPDFBrowser(); page = await instance.newPage(); return await run(page); } catch (error) { if (page == null && isBrowserConnected() === false) { const instance = await startPDFBrowser(); page = await instance.newPage(); return await run(page); } throw error; } finally { if (page != null) { await page.close().catch(() => {}); } } } /** * Generates a PDF from HTML content using a persistent Puppeteer browser * @param {string} html - The HTML content to convert to PDF * @param {Object} options - PDF generation options * @param {number} options.width - Document width in mm * @param {number|string} options.height - Document height in mm, or 'auto' * @returns {Promise} The PDF buffer */ export async function generatePDF(html, options = {}) { try { return await withPage(async (page) => { await page.setContent(html, { waitUntil: 'domcontentloaded', }); var height = options?.height || '50'; if (options.height == 'auto') { const calculatedHeight = await page.evaluate(() => { return document.getElementById('content').scrollHeight; }); height = calculatedHeight; } return page.pdf({ printBackground: true, preferCSSPageSize: true, width: options.width ? `${options.width}mm` : undefined, height: height ? (options.height == 'auto' ? `${height + 2}px` : `${height}mm`) : undefined, margin: { top: '0mm', right: '0mm', bottom: '0mm', left: '0mm', }, }); }); } catch (error) { logger.error('Error generating PDF:', error.message); throw error; } }