UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

256 lines 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserFramework = void 0; const playwright_extra_1 = require("playwright-extra"); const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth")); const JsonUtils_1 = require("../utils/JsonUtils"); const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException"); const path_1 = __importDefault(require("path")); const envVars_1 = require("../envVars"); const Logger_1 = require("../utils/Logger"); class BrowserFramework { constructor(browserConfig, browser, browserContext, browserBaseData) { this.browserConfig = browserConfig; this.browser = browser; this.browserContext = browserContext; this.browserBaseData = browserBaseData; } /** * Closes the browser and browser context. If the browser is a BrowserBase * instance, it will also release the session. */ async close() { try { await this.browserContext.close(); } catch (_error) { // Ignore, the browser context may have already been closed and that is fine. } try { await this.browser.close(); } catch (_error) { // Ignore, the browser may have already been closed and that is fine. } if (this.browserConfig.using.type === 'browserBase' && this.browserBaseData) { const password = process.env[envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY] ?? ''; const options = { method: 'POST', headers: { 'X-BB-API-Key': password, 'Content-Type': 'application/json', }, body: `{"projectId":"${this.browserBaseData.projectId}","status":"REQUEST_RELEASE"}`, }; try { await fetch(`https://api.browserbase.com/v1/sessions/${this.browserBaseData.id}`, options); } catch (error) { // Ignore, BrowserBase sessions expire automatically anyway. Logger_1.appLogger.warn('Failed to release BrowserBase session', error); } } } /** * Loads all the pre-canned browser device configurations from the local * filesystem. * * The returned map keys the devices by their name (ex: 'Desktop Firefox'). * * See `assets/devices.json` for details. */ static getSupportedDevices() { const rawDevices = JsonUtils_1.JsonUtils.readResourceAsJson(path_1.default.join('devices.json')); if (!rawDevices) { throw new Error('Failed to load devices configuration'); } return new Map(Object.entries(rawDevices)); } static async create(browserConfig, outputDir, storageState) { const type = browserConfig.using.type; switch (type) { case 'device': { const { browser, browserContext } = await BrowserFramework.forDevice(browserConfig.using.deviceName ?? BrowserFramework.DEFAULT_DEVICE_NAME, browserConfig.using.headless ?? false, outputDir, storageState); return new BrowserFramework(browserConfig, browser, browserContext); } case 'remoteInstance': { const { browser, browserContext } = await BrowserFramework.forRemoteBrowser(browserConfig.using.url, outputDir, storageState); return new BrowserFramework(browserConfig, browser, browserContext); } case 'browserBase': { const { browser, browserContext, browserBaseData } = await BrowserFramework.forBrowserBase(browserConfig.using.sessionArgs, outputDir, storageState); return new BrowserFramework(browserConfig, browser, browserContext, browserBaseData); } default: { throw new InvalidParamValueException_1.InvalidParamValueException('type', type); } } } /** * Connects to an existing Chromium browser using the Chrome DevTools Protocol (CDP) at the given * URL. If the URL contains "${env.VARIABLE_NAME}", then it will be expanded to the value of the * specified environment variable. */ static async forRemoteBrowser(remoteBrowserInstanceUrl, outputDir, storageState) { const expandedUrl = this.expandEnvVariables(remoteBrowserInstanceUrl); try { const browser = await playwright_extra_1.chromium.connectOverCDP(expandedUrl); const contextOptions = { recordVideo: { dir: outputDir }, }; if (storageState) { contextOptions.storageState = storageState; } return { browser: browser, browserContext: await browser.newContext(contextOptions), }; } catch (_) { throw new InvalidParamValueException_1.InvalidParamValueException('remoteBrowserInstanceUrl', remoteBrowserInstanceUrl); } } /** * If {@link storageState} is present, must be an object conforming to what is returned by * {@link BrowserContext.storageState()}. */ static async forDevice(deviceName, headless, outputDir, storageState) { const device = BrowserFramework.getSupportedDevices().get(deviceName); if (!device) { throw new InvalidParamValueException_1.InvalidParamValueException('deviceName', deviceName); } const contextOptions = { userAgent: device.userAgent, recordVideo: { dir: outputDir, size: { width: device.viewport?.width ?? 1280, height: device.viewport?.height ?? 720, }, }, viewport: { width: device.viewport?.width ?? 1280, height: device.viewport?.height ?? 720, }, screen: { width: device.screen?.width ?? device.viewport?.width ?? 1280, height: device.screen?.height ?? device.viewport?.height ?? 720, }, deviceScaleFactor: device.deviceScaleFactor ?? 1.0, isMobile: device.isMobile ?? false, hasTouch: device.hasTouch ?? false, }; if (storageState) { contextOptions.storageState = storageState; } const launchOptions = { headless, args: [ '--ignore-certificate-errors', '--disable-blink-features=AutomationControlled', ], }; let browser; switch (device.defaultBrowserType.toLowerCase()) { case 'firefox': browser = await playwright_extra_1.firefox.launch(launchOptions); break; case 'chromium': browser = await playwright_extra_1.chromium.launch(launchOptions); break; case 'chrome': browser = await playwright_extra_1.chromium.launch({ ...launchOptions, channel: 'chrome', }); break; case 'webkit': case 'ios': browser = await playwright_extra_1.webkit.launch(launchOptions); break; default: throw new InvalidParamValueException_1.InvalidParamValueException('browserType', device.defaultBrowserType); } try { const browserContext = await browser.newContext(contextOptions); return { browser, browserContext }; } catch (error) { await browser.close(); throw error; } } /** * Creates a BrowserBase session. If the project ID * contains "${env.VARIABLE_NAME}", then it will be expanded to the value of * the specified environment variable. Using this method requires the * BROWSERBASE_API_KEY environment variable to be set. * * The returned browserBaseData object conforms to the response of the session * creation API endpoint. See... * https://docs.browserbase.com/reference/api/create-a-session#response-id */ static async forBrowserBase(sessionArgs, outputDir, storageState) { const browserBaseData = await BrowserFramework.establishBrowserBaseSession(sessionArgs); const browser = await playwright_extra_1.chromium.connectOverCDP(browserBaseData.connectUrl); const contextOptions = { recordVideo: { dir: outputDir }, }; if (storageState) { contextOptions.storageState = storageState; } return { browser: browser, browserContext: await browser.newContext(contextOptions), browserBaseData: browserBaseData, }; } /** * Establishes a BrowserBase session. The returned structure matches the * response structore from the BrowserBase session API. See... * https://docs.browserbase.com/reference/api/create-a-session#response-id */ static async establishBrowserBaseSession(sessionArgs) { const expandedSessionArgs = { ...sessionArgs, projectId: this.expandEnvVariables(sessionArgs.projectId), }; const password = process.env[envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY]; if (!password) { throw new InvalidParamValueException_1.InvalidParamValueException(envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY, null); } const options = { method: 'POST', headers: { 'X-BB-API-Key': password, 'Content-Type': 'application/json' }, body: JSON.stringify(expandedSessionArgs), }; const browserBaseData = await fetch('https://api.browserbase.com/v1/sessions', options).then((response) => response.json()); if (browserBaseData.error) { throw new InvalidParamValueException_1.InvalidParamValueException(envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY, '(redacted)', `${browserBaseData.error}: ${browserBaseData.message}`); } return browserBaseData; } static expandEnvVariables(str) { const pattern = /\${env\.(\w+)}/g; return str.replace(pattern, (_, envVarName) => { const envVarValue = process.env[envVarName]; if (!envVarValue) { throw new InvalidParamValueException_1.InvalidParamValueException(envVarName, null); } return envVarValue; }); } } exports.BrowserFramework = BrowserFramework; BrowserFramework.DEFAULT_DEVICE_NAME = 'Desktop Chromium'; (() => { playwright_extra_1.firefox.use((0, puppeteer_extra_plugin_stealth_1.default)()); playwright_extra_1.chromium.use((0, puppeteer_extra_plugin_stealth_1.default)()); playwright_extra_1.webkit.use((0, puppeteer_extra_plugin_stealth_1.default)()); })(); //# sourceMappingURL=BrowserFramework.js.map