puppeteer.ts
1 import puppeteer, { type Browser, type PuppeteerNodeLaunchOptions } from 'puppeteer'; 2 3 /** 4 * Manages a singleton instance of a Puppeteer Browser, preserving 5 * the last known launch configuration. 6 */ 7 export class PuppeteerManager { 8 static browser: Browser | undefined; 9 static browserConfig: PuppeteerNodeLaunchOptions | undefined; 10 11 static #onBrowserExit = () => { 12 // eslint-disable-next-line no-console 13 console.warn('PuppeteerManager: browser exited'); 14 // if the browser process exits for any reason, mark the browser as dead 15 // so that we can perform re-launch on the next call to this.launch 16 this.browser = undefined; 17 }; 18 19 /** 20 * Launch a Puppeteer Browser with the specified configuration. Subsequent calls to 21 * launch() with no parameters, will return the previously launched 22 * browser instance. If a subsequent call to launch() provides a configuration, 23 * any existing browser instance will be closed and a new one created. Existing instances will 24 * be relaunched with the same configuration if their underlying process dies. 25 * 26 * @param browserConfig {PuppeteerNodeLaunchOptions} Puppeteer browser configuration. 27 * @returns {Promise<Browser>} A browser instance. 28 */ 29 static async launch(browserConfig?: PuppeteerNodeLaunchOptions): Promise<Browser> { 30 if (browserConfig && this.browser) { 31 await this.browser.close(); 32 } 33 34 if (!this.browser) { 35 this.browserConfig = 36 this.browserConfig && !browserConfig ? this.browserConfig : browserConfig; 37 this.browser = await puppeteer.launch({ 38 args: ['--no-sandbox'], // Required. 39 headless: true, 40 ...this.browserConfig, 41 }); 42 43 const childProcess = this.browser.process(); 44 childProcess?.on('exit', this.#onBrowserExit); 45 } 46 47 return this.browser; 48 } 49 }