UNPKG

@cityssm/pdf-puppeteer

Version:

Converts URLs and HTML to PDFs using Puppeteer.

241 lines (197 loc) 7.18 kB
import launchPuppeteer, { type puppeteer } from '@cityssm/puppeteer-launch' import Debug from 'debug' import exitHook from 'exit-hook' import { DEBUG_NAMESPACE } from './debug.config.js' import { type PDFOptions, type PDFPuppeteerOptions, defaultPdfPuppeteerOptions, defaultPuppeteerOptions, htmlNavigationTimeoutMillis, urlNavigationTimeoutMillis } from './defaultOptions.js' import pageToPdf from './pageToPdf.js' const debug = Debug(`${DEBUG_NAMESPACE}:index`) export class PdfPuppeteer { #browser: puppeteer.Browser | undefined #browserTimeout: NodeJS.Timeout | undefined readonly #pdfPuppeteerOptions: PDFPuppeteerOptions #puppeteerOptions: Exclude<Parameters<typeof launchPuppeteer>[0], undefined> = { ...defaultPuppeteerOptions } constructor(pdfPuppeteerOptions: Partial<PDFPuppeteerOptions> = {}) { this.#pdfPuppeteerOptions = { ...defaultPdfPuppeteerOptions, ...pdfPuppeteerOptions } exitHook(() => { debug('Exit hook triggered. Closing browser...') this.#clearBrowserCloseTimeout() void this.closeBrowser() }) } /** * Closes the Puppeteer browser instance. * This method ensures that the browser is closed properly. */ async closeBrowser(): Promise<void> { if (this.#browser !== undefined) { debug('Closing browser...') this.#clearBrowserCloseTimeout() try { await this.#browser.close() } catch (error) { debug('Error closing browser:', error) } this.#browser = undefined } } /** * Converts HTML content to a PDF document. * This method can handle both local HTML content and remote resources. * If the HTML contains remote content, it will fetch those resources. * If `hasRemoteContent` is `false`, it will load the HTML without fetching remote resources. * @param html - The HTML content to convert to PDF. * @param pdfOptions - Options for the PDF generation. * The options can include: * - `format`: The paper format (e.g., 'Letter', 'A4'). * - `width` and `height`: Custom dimensions for the PDF. * If `false`, the HTML will be loaded without fetching remote resources. * @returns A Promise that resolves to a Uint8Array containing the PDF data. * @throws {TypeError} If the `html` parameter is not a string. * @throws {Error} If there is an issue with loading the HTML or generating the PDF. */ async fromHtml( html: string, pdfOptions: PDFOptions = {} ): Promise<Uint8Array> { if (typeof html !== 'string') { throw new TypeError( 'Invalid Argument: HTML expected as type of string and received a value of a different type. Check your request body and request headers.' ) } const page = await this.#initializePage() await page.goto( `data:text/html;base64,${Buffer.from(html).toString('base64')}`, { timeout: htmlNavigationTimeoutMillis, waitUntil: this.#puppeteerOptions.browser === 'firefox' ? 'domcontentloaded' : 'networkidle0' } ) debug('Content loaded.') const pdf = await pageToPdf(page, pdfOptions) await page.close() const remainingPages = await this.#browser?.pages() if ( this.#pdfPuppeteerOptions.browserCloseTimeoutMillis === 0 && (remainingPages?.length ?? 0) <= 1 ) { debug('Browser close timeout is set to 0. Closing browser immediately.') await this.closeBrowser() } else { this.#setBrowserCloseTimeout() } return pdf } /** * Converts a URL to a PDF document. * This method loads the content of the URL and generates a PDF from it. * @param url - The URL to convert to PDF. * It should be a valid URL string. * @throws {TypeError} If the `url` parameter is not a string. * @param pdfOptions - Options for the PDF generation. * The options can include: * - `format`: The paper format (e.g., 'Letter', 'A4'). * - `width` and `height`: Custom dimensions for the PDF. * @throws {Error} If there is an issue with loading the URL or generating the PDF. * @returns A Promise that resolves to a Uint8Array containing the PDF data. */ async fromUrl(url: string, pdfOptions: PDFOptions = {}): Promise<Uint8Array> { if (typeof url !== 'string') { throw new TypeError( 'Invalid Argument: URL expected as type of string and received a value of a different type. Check your request body and request headers.' ) } const page = await this.#initializePage() debug('Loading URL...') await page.goto(url, { timeout: urlNavigationTimeoutMillis, waitUntil: this.#puppeteerOptions.browser === 'firefox' ? 'domcontentloaded' : 'networkidle0' }) debug('Content loaded.') const pdf = await pageToPdf(page, pdfOptions) await page.close() const remainingPages = await this.#browser?.pages() if ( this.#pdfPuppeteerOptions.browserCloseTimeoutMillis === 0 && (remainingPages?.length ?? 0) <= 1 ) { debug('Browser close timeout is set to 0. Closing browser immediately.') await this.closeBrowser() } else { this.#setBrowserCloseTimeout() } return pdf } #clearBrowserCloseTimeout(): void { if (this.#browserTimeout !== undefined) { try { clearTimeout(this.#browserTimeout) } catch (error) { debug('Error clearing browser close timeout:', error) } this.#browserTimeout = undefined } } async #initializePage(): Promise<puppeteer.Page> { this.#clearBrowserCloseTimeout() if (this.#browser === undefined || !this.#browser.connected) { this.#puppeteerOptions = { ...defaultPuppeteerOptions, browser: this.#pdfPuppeteerOptions.browser, browserOrder: this.#pdfPuppeteerOptions.browser === 'chrome' ? ['chrome', 'firefox', 'chrome-user', 'firefox-user'] : ['firefox', 'chrome', 'firefox-user', 'chrome-user'] } if (this.#pdfPuppeteerOptions.disableSandbox) { this.#puppeteerOptions.args = [ '--no-sandbox', '--disable-setuid-sandbox' ] } let puppeteerLaunchFunction = launchPuppeteer if (this.#pdfPuppeteerOptions.usePackagePuppeteer) { const puppeteerPackage = await import('puppeteer') puppeteerLaunchFunction = puppeteerPackage.launch } this.#browser = await puppeteerLaunchFunction(this.#puppeteerOptions) } return await this.#browser.newPage() } #setBrowserCloseTimeout(): void { this.#clearBrowserCloseTimeout() if (this.#pdfPuppeteerOptions.browserCloseTimeoutMillis > 0) { this.#browserTimeout = setTimeout(() => { debug('Browser timeout reached. Closing browser...') void this.closeBrowser() }, this.#pdfPuppeteerOptions.browserCloseTimeoutMillis) } } } export default PdfPuppeteer export { installBrowser, installChromeBrowser, installFirefoxBrowser, testInstalledBrowser, testInstalledChromeBrowser, testInstalledFirefoxBrowser } from '@cityssm/puppeteer-launch'