"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 session_exports = {}; __export(session_exports, { Session: () => Session, renderResolvedConfig: () => renderResolvedConfig }); module.exports = __toCommonJS(session_exports); var import_child_process = require("child_process"); var import_fs = __toESM(require("fs")); var import_net = __toESM(require("net")); var import_os = __toESM(require("os")); var import_path = __toESM(require("path")); var import_socketConnection = require("../utils/socketConnection"); var import_registry = require("./registry"); class Session { constructor(sessionFile) { this.config = sessionFile.config; this.name = this.config.name; this._sessionFile = sessionFile; } isCompatible(clientInfo) { return (0, import_socketConnection.compareSemver)(clientInfo.version, this.config.version) >= 0; } async run(clientInfo, args) { if (!this.isCompatible(clientInfo)) throw new Error(`Client is v${clientInfo.version}, session '${this.name}' is v${this.config.version}. Run playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open to restart the browser session.`); const { socket } = await this._connect(); if (!socket) throw new Error(`Browser '${this.name}' is not open. Run playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open to start the browser session.`); return await SocketConnectionClient.sendAndClose(socket, "run", { args, cwd: process.cwd() }); } async stop(quiet = false) { if (!await this.canConnect()) { if (!quiet) console.log(`Browser '${this.name}' is not open.`); return; } await this._stopDaemon(); if (!quiet) console.log(`Browser '${this.name}' closed `); } async deleteData() { await this.stop(); const dataDirs = await import_fs.default.promises.readdir(this._sessionFile.daemonDir).catch(() => []); const matchingEntries = dataDirs.filter((file) => file === `${this.name}.session` || file.startsWith(`ud-${this.name}-`)); if (matchingEntries.length === 0) { console.log(`No user data found for browser '${this.name}'.`); return; } for (const entry of matchingEntries) { const userDataDir = import_path.default.resolve(this._sessionFile.daemonDir, entry); for (let i = 0; i < 5; i++) { try { await import_fs.default.promises.rm(userDataDir, { recursive: true }); if (entry.startsWith("ud-")) console.log(`Deleted user data for browser '${this.name}'.`); break; } catch (e) { if (e.code === "ENOENT") { console.log(`No user data found for browser '${this.name}'.`); break; } await new Promise((resolve) => setTimeout(resolve, 1e3)); if (i === 4) throw e; } } } } async _connect() { return await new Promise((resolve) => { const socket = import_net.default.createConnection(this.config.socketPath, () => { resolve({ socket }); }); socket.on("error", (error) => { if (import_os.default.platform() !== "win32") void import_fs.default.promises.unlink(this.config.socketPath).catch(() => { }).then(() => resolve({ error })); else resolve({ error }); }); }); } async canConnect() { const { socket } = await this._connect(); if (socket) { socket.destroy(); return true; } return false; } static async startDaemon(clientInfo, cliArgs) { await import_fs.default.promises.mkdir(clientInfo.daemonProfilesDir, { recursive: true }); const cliPath = require.resolve("../cli-daemon/program.js"); const sessionName = (0, import_registry.resolveSessionName)(cliArgs.session); const errLog = import_path.default.join(clientInfo.daemonProfilesDir, sessionName + ".err"); const err = import_fs.default.openSync(errLog, "w"); const args = [ cliPath, sessionName ]; if (cliArgs.headed) args.push("--headed"); if (cliArgs.extension) args.push("--extension"); if (cliArgs.browser) args.push(`--browser=${cliArgs.browser}`); if (cliArgs.persistent) args.push("--persistent"); if (cliArgs.profile) args.push(`--profile=${cliArgs.profile}`); if (cliArgs.config) args.push(`--config=${cliArgs.config}`); if (cliArgs.endpoint || process.env.PLAYWRIGHT_CLI_SESSION) args.push(`--endpoint=${cliArgs.endpoint || process.env.PLAYWRIGHT_CLI_SESSION}`); const child = (0, import_child_process.spawn)(process.execPath, args, { detached: true, stdio: ["ignore", "pipe", err], cwd: process.cwd() // Will be used as root. }); let signalled = false; const sigintHandler = () => { signalled = true; child.kill("SIGINT"); }; const sigtermHandler = () => { signalled = true; child.kill("SIGTERM"); }; process.on("SIGINT", sigintHandler); process.on("SIGTERM", sigtermHandler); let outLog = ""; await new Promise((resolve, reject) => { child.stdout.on("data", (data) => { outLog += data.toString(); if (!outLog.includes("")) return; const errorMatch = outLog.match(/### Error\n([\s\S]*)/); const error = errorMatch ? errorMatch[1].trim() : void 0; if (error) { const errLogContent = import_fs.default.readFileSync(errLog, "utf-8"); const message = error + (errLogContent ? "\n" + errLogContent : ""); reject(new Error(message)); } const successMatch = outLog.match(/### Success\nDaemon listening on (.*)\n/); if (successMatch) resolve(); }); child.on("close", (code) => { if (!signalled) { const errLogContent = import_fs.default.readFileSync(errLog, "utf-8"); const message = `Daemon process exited with code ${code}` + (errLogContent ? "\n" + errLogContent : ""); reject(new Error(message)); } }); }); process.off("SIGINT", sigintHandler); process.off("SIGTERM", sigtermHandler); child.stdout.destroy(); child.unref(); if (cliArgs["endpoint"]) { console.log(`### Session \`${sessionName}\` created, attached to \`${cliArgs["endpoint"]}\`.`); console.log(`Run commands with: playwright-cli --session=${sessionName} `); } else { console.log(`### Browser \`${sessionName}\` opened with pid ${child.pid}.`); } } async _stopDaemon() { const { socket, error: socketError } = await this._connect(); if (!socket) { console.log(`Browser '${this.name}' is not open.${socketError ? " Error: " + socketError.message : ""}`); return; } let error; await SocketConnectionClient.sendAndClose(socket, "stop", {}).catch((e) => error = e); if (error && !error?.message?.includes("Session closed")) throw error; } async deleteSessionConfig() { await import_fs.default.promises.rm(this._sessionFile.file).catch(() => { }); } } function renderResolvedConfig(config) { const channel = config.browser.launchOptions.channel ?? config.browser.browserName; const lines = []; if (channel) lines.push(` - browser-type: ${channel}`); if (!config.cli.persistent) lines.push(` - user-data-dir: `); else lines.push(` - user-data-dir: ${config.browser.userDataDir}`); lines.push(` - headed: ${!config.browser.launchOptions.headless}`); return lines; } class SocketConnectionClient { constructor(socket) { this._nextMessageId = 1; this._callbacks = /* @__PURE__ */ new Map(); this._connection = new import_socketConnection.SocketConnection(socket); this._connection.onmessage = (message) => this._onMessage(message); this._connection.onclose = () => this._rejectCallbacks(); } async send(method, params = {}) { const messageId = this._nextMessageId++; const message = { id: messageId, method, params }; const responsePromise = new Promise((resolve, reject) => { this._callbacks.set(messageId, { resolve, reject, method, params }); }); const [result] = await Promise.all([responsePromise, this._connection.send(message)]); return result; } static async sendAndClose(socket, method, params = {}) { const connection = new SocketConnectionClient(socket); try { return await connection.send(method, params); } finally { connection.close(); } } close() { this._connection.close(); } _onMessage(object) { if (object.id && this._callbacks.has(object.id)) { const callback = this._callbacks.get(object.id); this._callbacks.delete(object.id); if (object.error) callback.reject(new Error(object.error)); else callback.resolve(object.result); } else if (object.id) { throw new Error(`Unexpected message id: ${object.id}`); } else { throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`); } } _rejectCallbacks() { for (const callback of this._callbacks.values()) callback.reject(new Error("Session closed")); this._callbacks.clear(); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Session, renderResolvedConfig });