import { chromium, type Browser } from 'playwright-core' import env from '#start/env' import logger from '@adonisjs/core/services/logger' const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' let browserPromise: Promise | null = null /** * Lazily launches a single shared headless Chromium instance, reused * across requests instead of paying the ~1-2s browser startup cost on * every merchant lookup. Points at a system-installed Chromium/Chrome * binary via CHROMIUM_EXECUTABLE_PATH (see .env.example) rather than * bundling Playwright's own ~300MB download. */ function getBrowser(): Promise { if (!browserPromise) { browserPromise = chromium .launch({ executablePath: env.get('CHROMIUM_EXECUTABLE_PATH') || undefined, headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }) .catch((error) => { browserPromise = null throw error }) } return browserPromise } /** * Runs `fn` with a fresh browser page/context, always closing the context * afterwards (success or failure) so pages don't accumulate across many * lookups. The underlying browser process itself stays warm between calls. * Returns null (rather than throwing) if the browser fails to launch or the * callback throws, since merchant lookups are best-effort. */ async function withPage( fn: (page: import('playwright-core').Page) => Promise ): Promise { try { const browser = await getBrowser() const context = await browser.newContext({ userAgent: USER_AGENT, locale: 'fr-FR' }) try { const page = await context.newPage() return await fn(page) } finally { await context.close() } } catch (error) { logger.warn({ err: error }, 'Headless browser lookup failed') return null } } /** * Closes the shared browser instance, if running. Intended for graceful * shutdown hooks / tests, not needed during normal request handling. */ async function closeBrowser(): Promise { if (!browserPromise) return const browser = await browserPromise.catch(() => null) browserPromise = null await browser?.close() } export { withPage, closeBrowser }