"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
|
});
|
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_package = require("../../package");
|
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, options) {
|
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(), raw: options?.raw, json: options?.json });
|
}
|
async stop() {
|
if (!await this.canConnect())
|
return { wasOpen: false };
|
await this._stopDaemon();
|
return { wasOpen: true };
|
}
|
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)
|
return { existed: false, deletedUserDataDir: false };
|
let deletedUserDataDir = false;
|
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-"))
|
deletedUserDataDir = true;
|
break;
|
} catch (e) {
|
if (e.code === "ENOENT")
|
break;
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
if (i === 4)
|
throw e;
|
}
|
}
|
}
|
return { existed: true, deletedUserDataDir };
|
}
|
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, mode) {
|
await import_fs.default.promises.mkdir(clientInfo.daemonProfilesDir, { recursive: true });
|
const cliPath = (0, import_package.libPath)("entry", "cliDaemon.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.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.extension)
|
args.push("--extension");
|
else if (cliArgs.cdp)
|
args.push(`--cdp=${cliArgs.cdp}`);
|
else if (cliArgs.endpoint)
|
args.push(`--endpoint=${cliArgs.endpoint}`);
|
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 = "";
|
const rejectWithPid = (reject, message) => reject(Object.assign(new Error(`Daemon pid=${child.pid}: ${message}`), { daemonPid: child.pid }));
|
await new Promise((resolve, reject) => {
|
child.stdout.on("data", (data) => {
|
outLog += data.toString();
|
if (outLog.includes("Daemon listening on"))
|
resolve();
|
});
|
child.on("close", (code) => {
|
if (!signalled) {
|
const errLogContent = import_fs.default.readFileSync(errLog, "utf-8");
|
rejectWithPid(reject, `Daemon process exited with code ${code}` + (outLog ? "\n" + outLog : "") + (errLogContent ? "\n" + errLogContent : ""));
|
}
|
});
|
});
|
process.off("SIGINT", sigintHandler);
|
process.off("SIGTERM", sigtermHandler);
|
child.stdout.destroy();
|
child.unref();
|
return { pid: child.pid, sessionName, endpoint: cliArgs.endpoint };
|
}
|
async _stopDaemon() {
|
const { socket } = await this._connect();
|
if (!socket)
|
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(() => {
|
});
|
}
|
}
|
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
|
});
|