| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- "use strict";
- var __create = Object.create;
- var __defProp = Object.defineProperty;
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
- var __getOwnPropNames = Object.getOwnPropertyNames;
- var __getProtoOf = Object.getPrototypeOf;
- var __hasOwnProp = Object.prototype.hasOwnProperty;
- var __export = (target, all) => {
- for (var name in all)
- __defProp(target, name, { get: all[name], enumerable: true });
- };
- var __copyProps = (to, from, except, desc) => {
- if (from && typeof from === "object" || typeof from === "function") {
- for (let key of __getOwnPropNames(from))
- if (!__hasOwnProp.call(to, key) && key !== except)
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
- }
- return to;
- };
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
- // If the importer is in node compatibility mode or this is not an ESM
- // file that has been converted to a CommonJS file using a Babel-
- // compatible transform (i.e. "__esModule" has not been set), then set
- // "default" to the CommonJS "module.exports" for node compatibility.
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
- mod
- ));
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
- var browserContextFactory_exports = {};
- __export(browserContextFactory_exports, {
- SharedContextFactory: () => SharedContextFactory,
- contextFactory: () => contextFactory
- });
- module.exports = __toCommonJS(browserContextFactory_exports);
- var import_crypto = __toESM(require("crypto"));
- var import_fs = __toESM(require("fs"));
- var import_net = __toESM(require("net"));
- var import_path = __toESM(require("path"));
- var playwright = __toESM(require("playwright-core"));
- var import_registry = require("playwright-core/lib/server/registry/index");
- var import_server = require("playwright-core/lib/server");
- var import_log = require("../log");
- var import_config = require("./config");
- var import_server2 = require("../sdk/server");
- function contextFactory(config) {
- if (config.sharedBrowserContext)
- return SharedContextFactory.create(config);
- if (config.browser.remoteEndpoint)
- return new RemoteContextFactory(config);
- if (config.browser.cdpEndpoint)
- return new CdpContextFactory(config);
- if (config.browser.isolated)
- return new IsolatedContextFactory(config);
- return new PersistentContextFactory(config);
- }
- class BaseContextFactory {
- constructor(name, config) {
- this._logName = name;
- this.config = config;
- }
- async _obtainBrowser(clientInfo) {
- if (this._browserPromise)
- return this._browserPromise;
- (0, import_log.testDebug)(`obtain browser (${this._logName})`);
- this._browserPromise = this._doObtainBrowser(clientInfo);
- void this._browserPromise.then((browser) => {
- browser.on("disconnected", () => {
- this._browserPromise = void 0;
- });
- }).catch(() => {
- this._browserPromise = void 0;
- });
- return this._browserPromise;
- }
- async _doObtainBrowser(clientInfo) {
- throw new Error("Not implemented");
- }
- async createContext(clientInfo) {
- (0, import_log.testDebug)(`create browser context (${this._logName})`);
- const browser = await this._obtainBrowser(clientInfo);
- const browserContext = await this._doCreateContext(browser);
- await addInitScript(browserContext, this.config.browser.initScript);
- return {
- browserContext,
- close: (afterClose) => this._closeBrowserContext(browserContext, browser, afterClose)
- };
- }
- async _doCreateContext(browser) {
- throw new Error("Not implemented");
- }
- async _closeBrowserContext(browserContext, browser, afterClose) {
- (0, import_log.testDebug)(`close browser context (${this._logName})`);
- if (browser.contexts().length === 1)
- this._browserPromise = void 0;
- await browserContext.close().catch(import_log.logUnhandledError);
- await afterClose();
- if (browser.contexts().length === 0) {
- (0, import_log.testDebug)(`close browser (${this._logName})`);
- await browser.close().catch(import_log.logUnhandledError);
- }
- }
- }
- class IsolatedContextFactory extends BaseContextFactory {
- constructor(config) {
- super("isolated", config);
- }
- async _doObtainBrowser(clientInfo) {
- await injectCdpPort(this.config.browser);
- const browserType = playwright[this.config.browser.browserName];
- const tracesDir = await computeTracesDir(this.config, clientInfo);
- if (tracesDir && this.config.saveTrace)
- await startTraceServer(this.config, tracesDir);
- return browserType.launch({
- tracesDir,
- ...this.config.browser.launchOptions,
- handleSIGINT: false,
- handleSIGTERM: false
- }).catch((error) => {
- if (error.message.includes("Executable doesn't exist"))
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
- throw error;
- });
- }
- async _doCreateContext(browser) {
- return browser.newContext(this.config.browser.contextOptions);
- }
- }
- class CdpContextFactory extends BaseContextFactory {
- constructor(config) {
- super("cdp", config);
- }
- async _doObtainBrowser() {
- return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint, { headers: this.config.browser.cdpHeaders });
- }
- async _doCreateContext(browser) {
- return this.config.browser.isolated ? await browser.newContext() : browser.contexts()[0];
- }
- }
- class RemoteContextFactory extends BaseContextFactory {
- constructor(config) {
- super("remote", config);
- }
- async _doObtainBrowser() {
- const url = new URL(this.config.browser.remoteEndpoint);
- url.searchParams.set("browser", this.config.browser.browserName);
- if (this.config.browser.launchOptions)
- url.searchParams.set("launch-options", JSON.stringify(this.config.browser.launchOptions));
- return playwright[this.config.browser.browserName].connect(String(url));
- }
- async _doCreateContext(browser) {
- return browser.newContext();
- }
- }
- class PersistentContextFactory {
- constructor(config) {
- this.name = "persistent";
- this.description = "Create a new persistent browser context";
- this._userDataDirs = /* @__PURE__ */ new Set();
- this.config = config;
- }
- async createContext(clientInfo) {
- await injectCdpPort(this.config.browser);
- (0, import_log.testDebug)("create browser context (persistent)");
- const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
- const tracesDir = await computeTracesDir(this.config, clientInfo);
- if (tracesDir && this.config.saveTrace)
- await startTraceServer(this.config, tracesDir);
- this._userDataDirs.add(userDataDir);
- (0, import_log.testDebug)("lock user data dir", userDataDir);
- const browserType = playwright[this.config.browser.browserName];
- for (let i = 0; i < 5; i++) {
- const launchOptions = {
- tracesDir,
- ...this.config.browser.launchOptions,
- ...this.config.browser.contextOptions,
- handleSIGINT: false,
- handleSIGTERM: false,
- ignoreDefaultArgs: [
- "--disable-extensions"
- ],
- assistantMode: true
- };
- try {
- const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
- await addInitScript(browserContext, this.config.browser.initScript);
- const close = (afterClose) => this._closeBrowserContext(browserContext, userDataDir, afterClose);
- return { browserContext, close };
- } catch (error) {
- if (error.message.includes("Executable doesn't exist"))
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
- if (error.message.includes("ProcessSingleton") || error.message.includes("Invalid URL")) {
- await new Promise((resolve) => setTimeout(resolve, 1e3));
- continue;
- }
- throw error;
- }
- }
- throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
- }
- async _closeBrowserContext(browserContext, userDataDir, afterClose) {
- (0, import_log.testDebug)("close browser context (persistent)");
- (0, import_log.testDebug)("release user data dir", userDataDir);
- await browserContext.close().catch(() => {
- });
- await afterClose();
- this._userDataDirs.delete(userDataDir);
- (0, import_log.testDebug)("close browser context complete (persistent)");
- }
- async _createUserDataDir(clientInfo) {
- const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? import_registry.registryDirectory;
- const browserToken = this.config.browser.launchOptions?.channel ?? this.config.browser?.browserName;
- const rootPath = (0, import_server2.firstRootPath)(clientInfo);
- const rootPathToken = rootPath ? `-${createHash(rootPath)}` : "";
- const result = import_path.default.join(dir, `mcp-${browserToken}${rootPathToken}`);
- await import_fs.default.promises.mkdir(result, { recursive: true });
- return result;
- }
- }
- async function injectCdpPort(browserConfig) {
- if (browserConfig.browserName === "chromium")
- browserConfig.launchOptions.cdpPort = await findFreePort();
- }
- async function findFreePort() {
- return new Promise((resolve, reject) => {
- const server = import_net.default.createServer();
- server.listen(0, () => {
- const { port } = server.address();
- server.close(() => resolve(port));
- });
- server.on("error", reject);
- });
- }
- async function startTraceServer(config, tracesDir) {
- if (!config.saveTrace)
- return;
- const server = await (0, import_server.startTraceViewerServer)();
- const urlPrefix = server.urlPrefix("human-readable");
- const url = urlPrefix + "/trace/index.html?trace=" + tracesDir + "/trace.json";
- console.error("\nTrace viewer listening on " + url);
- }
- function createHash(data) {
- return import_crypto.default.createHash("sha256").update(data).digest("hex").slice(0, 7);
- }
- async function addInitScript(browserContext, initScript) {
- for (const scriptPath of initScript ?? [])
- await browserContext.addInitScript({ path: import_path.default.resolve(scriptPath) });
- }
- class SharedContextFactory {
- static create(config) {
- if (SharedContextFactory._instance)
- throw new Error("SharedContextFactory already exists");
- const baseConfig = { ...config, sharedBrowserContext: false };
- const baseFactory = contextFactory(baseConfig);
- SharedContextFactory._instance = new SharedContextFactory(baseFactory);
- return SharedContextFactory._instance;
- }
- constructor(baseFactory) {
- this._baseFactory = baseFactory;
- }
- async createContext(clientInfo, abortSignal, toolName) {
- if (!this._contextPromise) {
- (0, import_log.testDebug)("create shared browser context");
- this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, toolName);
- }
- const { browserContext } = await this._contextPromise;
- (0, import_log.testDebug)(`shared context client connected`);
- return {
- browserContext,
- close: async () => {
- (0, import_log.testDebug)(`shared context client disconnected`);
- }
- };
- }
- static async dispose() {
- await SharedContextFactory._instance?._dispose();
- }
- async _dispose() {
- const contextPromise = this._contextPromise;
- this._contextPromise = void 0;
- if (!contextPromise)
- return;
- const { close } = await contextPromise;
- await close(async () => {
- });
- }
- }
- async function computeTracesDir(config, clientInfo) {
- if (!config.saveTrace && !config.capabilities?.includes("tracing"))
- return;
- return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", reason: "Collecting trace" });
- }
- // Annotate the CommonJS export names for ESM import in node:
- 0 && (module.exports = {
- SharedContextFactory,
- contextFactory
- });
|