/ src / lib / utils / puppeteer.ts
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  }