opensteer 0.6.11 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JZF2WC7U.js → chunk-HBTSQ2V4.js} +2680 -455
- package/dist/cli/profile.cjs +1996 -536
- package/dist/cli/profile.js +1 -1
- package/dist/cli/server.cjs +1916 -456
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +2676 -456
- package/dist/index.d.cts +26 -12
- package/dist/index.d.ts +26 -12
- package/dist/index.js +9 -1
- package/package.json +1 -1
package/dist/cli/server.cjs
CHANGED
|
@@ -424,10 +424,7 @@ var import_fs5 = require("fs");
|
|
|
424
424
|
var import_crypto = require("crypto");
|
|
425
425
|
|
|
426
426
|
// src/browser/pool.ts
|
|
427
|
-
var
|
|
428
|
-
var import_promises2 = require("fs/promises");
|
|
429
|
-
var import_node_net = require("net");
|
|
430
|
-
var import_playwright = require("playwright");
|
|
427
|
+
var import_playwright2 = require("playwright");
|
|
431
428
|
|
|
432
429
|
// src/browser/cdp-proxy.ts
|
|
433
430
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -920,281 +917,1892 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
|
|
|
920
917
|
}
|
|
921
918
|
|
|
922
919
|
// src/browser/persistent-profile.ts
|
|
920
|
+
var import_node_crypto3 = require("crypto");
|
|
921
|
+
var import_node_child_process2 = require("child_process");
|
|
922
|
+
var import_node_fs3 = require("fs");
|
|
923
|
+
var import_promises4 = require("fs/promises");
|
|
924
|
+
var import_node_os = require("os");
|
|
925
|
+
var import_node_path4 = require("path");
|
|
926
|
+
var import_node_util2 = require("util");
|
|
927
|
+
|
|
928
|
+
// src/browser/persistent-profile-coordination.ts
|
|
929
|
+
var import_node_path2 = require("path");
|
|
930
|
+
|
|
931
|
+
// src/browser/dir-lock.ts
|
|
923
932
|
var import_node_crypto = require("crypto");
|
|
924
933
|
var import_node_fs = require("fs");
|
|
925
|
-
var
|
|
926
|
-
var import_node_os = require("os");
|
|
934
|
+
var import_promises2 = require("fs/promises");
|
|
927
935
|
var import_node_path = require("path");
|
|
928
|
-
|
|
936
|
+
|
|
937
|
+
// src/browser/process-owner.ts
|
|
938
|
+
var import_node_child_process = require("child_process");
|
|
939
|
+
var import_promises = require("fs/promises");
|
|
940
|
+
var import_node_util = require("util");
|
|
941
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
929
942
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
930
943
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
931
|
-
var
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
var
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
"hyphen-data",
|
|
952
|
-
"OnDeviceHeadSuggestModel",
|
|
953
|
-
"OptimizationGuidePredictionModels",
|
|
954
|
-
"Segmentation Platform",
|
|
955
|
-
"SmartCardDeviceNames",
|
|
956
|
-
"WidevineCdm",
|
|
957
|
-
"pnacl"
|
|
958
|
-
]);
|
|
959
|
-
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
960
|
-
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
961
|
-
const targetUserDataDir = (0, import_node_path.join)(
|
|
962
|
-
expandHome(profilesRootDir),
|
|
963
|
-
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
964
|
-
);
|
|
965
|
-
const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
966
|
-
const metadata = buildPersistentProfileMetadata(
|
|
967
|
-
resolvedSourceUserDataDir,
|
|
968
|
-
profileDirectory
|
|
969
|
-
);
|
|
970
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
|
|
971
|
-
await cleanOrphanedTempDirs(
|
|
972
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
973
|
-
(0, import_node_path.basename)(targetUserDataDir)
|
|
974
|
-
);
|
|
975
|
-
if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
976
|
-
throw new Error(
|
|
977
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
978
|
-
);
|
|
944
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
945
|
+
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
946
|
+
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
947
|
+
var CURRENT_PROCESS_OWNER = {
|
|
948
|
+
pid: process.pid,
|
|
949
|
+
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
950
|
+
};
|
|
951
|
+
var linuxClockTicksPerSecondPromise = null;
|
|
952
|
+
function parseProcessOwner(value) {
|
|
953
|
+
if (!value || typeof value !== "object") {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
const parsed = value;
|
|
957
|
+
const pid = Number(parsed.pid);
|
|
958
|
+
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
959
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
963
|
+
return null;
|
|
979
964
|
}
|
|
980
|
-
const created = await createPersistentProfileClone(
|
|
981
|
-
resolvedSourceUserDataDir,
|
|
982
|
-
sourceProfileDir,
|
|
983
|
-
targetUserDataDir,
|
|
984
|
-
profileDirectory,
|
|
985
|
-
metadata
|
|
986
|
-
);
|
|
987
|
-
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
988
965
|
return {
|
|
989
|
-
|
|
990
|
-
|
|
966
|
+
pid,
|
|
967
|
+
processStartedAtMs
|
|
991
968
|
};
|
|
992
969
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
recursive: true
|
|
999
|
-
}).catch(() => void 0)
|
|
1000
|
-
)
|
|
1001
|
-
);
|
|
970
|
+
function processOwnersEqual(left, right) {
|
|
971
|
+
if (!left || !right) {
|
|
972
|
+
return left === right;
|
|
973
|
+
}
|
|
974
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
1002
975
|
}
|
|
1003
|
-
function
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
976
|
+
async function getProcessLiveness(owner) {
|
|
977
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
978
|
+
owner.processStartedAtMs,
|
|
979
|
+
PROCESS_STARTED_AT_MS
|
|
980
|
+
)) {
|
|
981
|
+
return "live";
|
|
982
|
+
}
|
|
983
|
+
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
984
|
+
if (typeof startedAtMs === "number") {
|
|
985
|
+
return hasMatchingProcessStartTime(
|
|
986
|
+
owner.processStartedAtMs,
|
|
987
|
+
startedAtMs
|
|
988
|
+
) ? "live" : "dead";
|
|
989
|
+
}
|
|
990
|
+
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
1008
991
|
}
|
|
1009
|
-
function
|
|
1010
|
-
|
|
992
|
+
function isProcessRunning(pid) {
|
|
993
|
+
try {
|
|
994
|
+
process.kill(pid, 0);
|
|
995
|
+
return true;
|
|
996
|
+
} catch (error) {
|
|
997
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
998
|
+
return code !== "ESRCH";
|
|
999
|
+
}
|
|
1011
1000
|
}
|
|
1012
|
-
function
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1001
|
+
async function readProcessOwner(pid) {
|
|
1002
|
+
const processStartedAtMs = await readProcessStartedAtMs(pid);
|
|
1003
|
+
if (processStartedAtMs === null) {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
pid,
|
|
1008
|
+
processStartedAtMs
|
|
1009
|
+
};
|
|
1015
1010
|
}
|
|
1016
|
-
function
|
|
1017
|
-
return
|
|
1011
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1012
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
1018
1013
|
}
|
|
1019
|
-
async function
|
|
1020
|
-
|
|
1014
|
+
async function readProcessStartedAtMs(pid) {
|
|
1015
|
+
if (pid <= 0) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
if (process.platform === "linux") {
|
|
1019
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
1020
|
+
}
|
|
1021
|
+
if (process.platform === "win32") {
|
|
1022
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
1023
|
+
}
|
|
1024
|
+
return await readPsProcessStartedAtMs(pid);
|
|
1025
|
+
}
|
|
1026
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
1027
|
+
let statRaw;
|
|
1021
1028
|
try {
|
|
1022
|
-
|
|
1029
|
+
statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
1023
1030
|
} catch {
|
|
1024
|
-
return;
|
|
1031
|
+
return null;
|
|
1025
1032
|
}
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (entry === targetProfileDirectory) continue;
|
|
1030
|
-
const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
|
|
1031
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
|
|
1032
|
-
if ((0, import_node_fs.existsSync)(targetPath)) continue;
|
|
1033
|
-
let entryStat;
|
|
1034
|
-
try {
|
|
1035
|
-
entryStat = await (0, import_promises.stat)(sourcePath);
|
|
1036
|
-
} catch {
|
|
1037
|
-
continue;
|
|
1038
|
-
}
|
|
1039
|
-
if (entryStat.isFile()) {
|
|
1040
|
-
copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1041
|
-
} else if (entryStat.isDirectory()) {
|
|
1042
|
-
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1043
|
-
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1044
|
-
copyTasks.push(
|
|
1045
|
-
(0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1046
|
-
() => void 0
|
|
1047
|
-
)
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1033
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
1034
|
+
if (startTicks === null) {
|
|
1035
|
+
return null;
|
|
1050
1036
|
}
|
|
1051
|
-
await Promise.all(
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1037
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
1038
|
+
readLinuxBootTimeMs(),
|
|
1039
|
+
readLinuxClockTicksPerSecond()
|
|
1040
|
+
]);
|
|
1041
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
return Math.floor(
|
|
1045
|
+
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
1057
1046
|
);
|
|
1058
1047
|
}
|
|
1059
|
-
function
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1048
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1049
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1050
|
+
if (closingParenIndex === -1) {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
1054
|
+
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
1055
|
+
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
1065
1056
|
}
|
|
1066
|
-
async function
|
|
1067
|
-
|
|
1068
|
-
|
|
1057
|
+
async function readLinuxBootTimeMs() {
|
|
1058
|
+
try {
|
|
1059
|
+
const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
|
|
1060
|
+
const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
|
|
1061
|
+
if (!bootTimeLine) {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const bootTimeSeconds = Number.parseInt(
|
|
1065
|
+
bootTimeLine.slice("btime ".length),
|
|
1066
|
+
10
|
|
1067
|
+
);
|
|
1068
|
+
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
1069
|
+
} catch {
|
|
1070
|
+
return null;
|
|
1069
1071
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
)
|
|
1073
|
-
|
|
1072
|
+
}
|
|
1073
|
+
async function readLinuxClockTicksPerSecond() {
|
|
1074
|
+
if (!linuxClockTicksPerSecondPromise) {
|
|
1075
|
+
linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
|
|
1076
|
+
encoding: "utf8",
|
|
1077
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1078
|
+
}).then(({ stdout }) => {
|
|
1079
|
+
const value = Number.parseInt(stdout.trim(), 10);
|
|
1080
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
1081
|
+
}).catch(() => null);
|
|
1082
|
+
}
|
|
1083
|
+
return await linuxClockTicksPerSecondPromise;
|
|
1084
|
+
}
|
|
1085
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
1074
1086
|
try {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1087
|
+
const { stdout } = await execFileAsync(
|
|
1088
|
+
"powershell.exe",
|
|
1089
|
+
[
|
|
1090
|
+
"-NoProfile",
|
|
1091
|
+
"-Command",
|
|
1092
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString("o")`
|
|
1093
|
+
],
|
|
1094
|
+
{
|
|
1095
|
+
encoding: "utf8",
|
|
1096
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1097
|
+
}
|
|
1082
1098
|
);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1099
|
+
const isoTimestamp = stdout.trim();
|
|
1100
|
+
if (!isoTimestamp) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
const startedAtMs = Date.parse(isoTimestamp);
|
|
1104
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
1105
|
+
} catch {
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
async function readPsProcessStartedAtMs(pid) {
|
|
1110
|
+
try {
|
|
1111
|
+
const { stdout } = await execFileAsync(
|
|
1112
|
+
"ps",
|
|
1113
|
+
["-o", "lstart=", "-p", String(pid)],
|
|
1114
|
+
{
|
|
1115
|
+
encoding: "utf8",
|
|
1116
|
+
env: PS_COMMAND_ENV,
|
|
1117
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1089
1118
|
}
|
|
1090
|
-
|
|
1119
|
+
);
|
|
1120
|
+
const startedAt = stdout.trim();
|
|
1121
|
+
if (!startedAt) {
|
|
1122
|
+
return null;
|
|
1091
1123
|
}
|
|
1092
|
-
|
|
1093
|
-
return
|
|
1124
|
+
const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
|
|
1125
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/browser/dir-lock.ts
|
|
1132
|
+
var LOCK_OWNER_FILE = "owner.json";
|
|
1133
|
+
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
1134
|
+
var LOCK_RETRY_DELAY_MS = 50;
|
|
1135
|
+
async function withDirLock(lockDirPath, action) {
|
|
1136
|
+
const releaseLock = await acquireDirLock(lockDirPath);
|
|
1137
|
+
try {
|
|
1138
|
+
return await action();
|
|
1094
1139
|
} finally {
|
|
1095
|
-
|
|
1096
|
-
|
|
1140
|
+
await releaseLock();
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async function acquireDirLock(lockDirPath) {
|
|
1144
|
+
while (true) {
|
|
1145
|
+
const releaseLock = await tryAcquireDirLock(lockDirPath);
|
|
1146
|
+
if (releaseLock) {
|
|
1147
|
+
return releaseLock;
|
|
1148
|
+
}
|
|
1149
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async function tryAcquireDirLock(lockDirPath) {
|
|
1153
|
+
await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
|
|
1154
|
+
while (true) {
|
|
1155
|
+
const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
1156
|
+
try {
|
|
1157
|
+
await (0, import_promises2.mkdir)(tempLockDirPath);
|
|
1158
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
|
|
1159
|
+
try {
|
|
1160
|
+
await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
|
|
1161
|
+
break;
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
} finally {
|
|
1168
|
+
await (0, import_promises2.rm)(tempLockDirPath, {
|
|
1097
1169
|
recursive: true,
|
|
1098
1170
|
force: true
|
|
1099
1171
|
}).catch(() => void 0);
|
|
1100
1172
|
}
|
|
1173
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1174
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
return null;
|
|
1101
1178
|
}
|
|
1179
|
+
return async () => {
|
|
1180
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
1181
|
+
recursive: true,
|
|
1182
|
+
force: true
|
|
1183
|
+
}).catch(() => void 0);
|
|
1184
|
+
};
|
|
1102
1185
|
}
|
|
1103
|
-
async function
|
|
1104
|
-
if ((0, import_node_fs.existsSync)(
|
|
1105
|
-
return;
|
|
1186
|
+
async function isDirLockHeld(lockDirPath) {
|
|
1187
|
+
if (!(0, import_node_fs.existsSync)(lockDirPath)) {
|
|
1188
|
+
return false;
|
|
1106
1189
|
}
|
|
1107
|
-
await
|
|
1190
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1191
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
return (0, import_node_fs.existsSync)(lockDirPath);
|
|
1108
1195
|
}
|
|
1109
|
-
function
|
|
1110
|
-
|
|
1111
|
-
return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
1196
|
+
function getErrorCode(error) {
|
|
1197
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1112
1198
|
}
|
|
1113
|
-
function
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1199
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
1200
|
+
const code = getErrorCode(error);
|
|
1201
|
+
return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
1202
|
+
}
|
|
1203
|
+
async function writeLockOwner(lockDirPath, owner) {
|
|
1204
|
+
await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
|
|
1205
|
+
}
|
|
1206
|
+
async function readLockOwner(lockDirPath) {
|
|
1207
|
+
return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
|
|
1208
|
+
}
|
|
1209
|
+
async function readLockParticipant(filePath) {
|
|
1210
|
+
return (await readLockParticipantRecord(filePath)).owner;
|
|
1211
|
+
}
|
|
1212
|
+
async function readLockParticipantRecord(filePath) {
|
|
1213
|
+
try {
|
|
1214
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf8");
|
|
1215
|
+
const owner = parseProcessOwner(JSON.parse(raw));
|
|
1216
|
+
return {
|
|
1217
|
+
exists: true,
|
|
1218
|
+
owner
|
|
1219
|
+
};
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
return {
|
|
1222
|
+
exists: getErrorCode(error) !== "ENOENT",
|
|
1223
|
+
owner: null
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
1228
|
+
return await readLockParticipantRecord(
|
|
1229
|
+
(0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
1117
1230
|
);
|
|
1118
1231
|
}
|
|
1119
|
-
async function
|
|
1120
|
-
|
|
1232
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
1233
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
1234
|
+
return false;
|
|
1235
|
+
}
|
|
1236
|
+
let reclaimed = false;
|
|
1121
1237
|
try {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1238
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1239
|
+
if (!processOwnersEqual(owner, expectedOwner)) {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
1246
|
+
recursive: true,
|
|
1247
|
+
force: true
|
|
1248
|
+
}).catch(() => void 0);
|
|
1249
|
+
reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
|
|
1250
|
+
return reclaimed;
|
|
1251
|
+
} finally {
|
|
1252
|
+
if (!reclaimed) {
|
|
1253
|
+
await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
|
|
1254
|
+
recursive: true,
|
|
1255
|
+
force: true
|
|
1256
|
+
}).catch(() => void 0);
|
|
1257
|
+
}
|
|
1128
1258
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1259
|
+
}
|
|
1260
|
+
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
1261
|
+
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
1262
|
+
while (true) {
|
|
1263
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
1264
|
+
try {
|
|
1265
|
+
await (0, import_promises2.mkdir)(tempReclaimerDirPath);
|
|
1266
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
|
|
1267
|
+
try {
|
|
1268
|
+
await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
|
|
1269
|
+
return true;
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
1275
|
+
throw error;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const code = getErrorCode(error);
|
|
1280
|
+
if (code === "ENOENT") {
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
throw error;
|
|
1284
|
+
} finally {
|
|
1285
|
+
await (0, import_promises2.rm)(tempReclaimerDirPath, {
|
|
1286
|
+
recursive: true,
|
|
1287
|
+
force: true
|
|
1288
|
+
}).catch(() => void 0);
|
|
1289
|
+
}
|
|
1290
|
+
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
1291
|
+
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
await (0, import_promises2.rm)(reclaimerDirPath, {
|
|
1298
|
+
recursive: true,
|
|
1299
|
+
force: true
|
|
1300
|
+
}).catch(() => void 0);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
1304
|
+
return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
1305
|
+
}
|
|
1306
|
+
async function sleep(ms) {
|
|
1307
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/browser/persistent-profile-coordination.ts
|
|
1311
|
+
var PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS = 50;
|
|
1312
|
+
async function withPersistentProfileControlLock(targetUserDataDir, action) {
|
|
1313
|
+
return await withDirLock(
|
|
1314
|
+
buildPersistentProfileControlLockDirPath(targetUserDataDir),
|
|
1315
|
+
action
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
async function acquirePersistentProfileWriteLock(targetUserDataDir) {
|
|
1319
|
+
const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
|
|
1320
|
+
const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
|
|
1321
|
+
targetUserDataDir
|
|
1322
|
+
);
|
|
1323
|
+
while (true) {
|
|
1324
|
+
let releaseWriteLock = null;
|
|
1325
|
+
const releaseControlLock = await acquireDirLock(controlLockDirPath);
|
|
1326
|
+
try {
|
|
1327
|
+
releaseWriteLock = await tryAcquireDirLock(writeLockDirPath);
|
|
1328
|
+
} finally {
|
|
1329
|
+
await releaseControlLock();
|
|
1330
|
+
}
|
|
1331
|
+
if (releaseWriteLock) {
|
|
1332
|
+
return releaseWriteLock;
|
|
1333
|
+
}
|
|
1334
|
+
await sleep2(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
async function isPersistentProfileWriteLocked(targetUserDataDir) {
|
|
1338
|
+
return await isDirLockHeld(
|
|
1339
|
+
buildPersistentProfileWriteLockDirPath(targetUserDataDir)
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
|
|
1343
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
|
|
1344
|
+
}
|
|
1345
|
+
function buildPersistentProfileControlLockDirPath(targetUserDataDir) {
|
|
1346
|
+
return (0, import_node_path2.join)(
|
|
1347
|
+
(0, import_node_path2.dirname)(targetUserDataDir),
|
|
1348
|
+
`${(0, import_node_path2.basename)(targetUserDataDir)}.control.lock`
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
async function sleep2(ms) {
|
|
1352
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// src/browser/shared-real-browser-session-state.ts
|
|
1356
|
+
var import_node_crypto2 = require("crypto");
|
|
1357
|
+
var import_node_fs2 = require("fs");
|
|
1358
|
+
var import_promises3 = require("fs/promises");
|
|
1359
|
+
var import_node_path3 = require("path");
|
|
1360
|
+
var SHARED_SESSION_METADATA_FILE = "session.json";
|
|
1361
|
+
var SHARED_SESSION_CLIENTS_DIR = "clients";
|
|
1362
|
+
var SHARED_SESSION_RETRY_DELAY_MS = 50;
|
|
1363
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_PREFIX = `${SHARED_SESSION_METADATA_FILE}.`;
|
|
1364
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX = ".tmp";
|
|
1365
|
+
function buildSharedSessionDirPath(persistentUserDataDir) {
|
|
1366
|
+
return (0, import_node_path3.join)(
|
|
1367
|
+
(0, import_node_path3.dirname)(persistentUserDataDir),
|
|
1368
|
+
`${(0, import_node_path3.basename)(persistentUserDataDir)}.session`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
function buildSharedSessionLockPath(persistentUserDataDir) {
|
|
1372
|
+
return `${buildSharedSessionDirPath(persistentUserDataDir)}.lock`;
|
|
1373
|
+
}
|
|
1374
|
+
function buildSharedSessionClientsDirPath(persistentUserDataDir) {
|
|
1375
|
+
return (0, import_node_path3.join)(
|
|
1376
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
1377
|
+
SHARED_SESSION_CLIENTS_DIR
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
function buildSharedSessionClientPath(persistentUserDataDir, clientId) {
|
|
1381
|
+
return (0, import_node_path3.join)(
|
|
1382
|
+
buildSharedSessionClientsDirPath(persistentUserDataDir),
|
|
1383
|
+
`${clientId}.json`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
async function readSharedSessionMetadata(persistentUserDataDir) {
|
|
1387
|
+
return (await readSharedSessionMetadataRecord(persistentUserDataDir)).metadata;
|
|
1388
|
+
}
|
|
1389
|
+
async function writeSharedSessionMetadata(persistentUserDataDir, metadata) {
|
|
1390
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
1391
|
+
const metadataPath = buildSharedSessionMetadataPath(persistentUserDataDir);
|
|
1392
|
+
const tempPath = buildSharedSessionMetadataTempPath(sessionDirPath);
|
|
1393
|
+
await (0, import_promises3.mkdir)(sessionDirPath, { recursive: true });
|
|
1394
|
+
try {
|
|
1395
|
+
await (0, import_promises3.writeFile)(tempPath, JSON.stringify(metadata, null, 2));
|
|
1396
|
+
await (0, import_promises3.rename)(tempPath, metadataPath);
|
|
1397
|
+
} finally {
|
|
1398
|
+
await (0, import_promises3.rm)(tempPath, { force: true }).catch(() => void 0);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async function hasLiveSharedRealBrowserSession(persistentUserDataDir) {
|
|
1402
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
1403
|
+
const metadataRecord = await readSharedSessionMetadataRecord(
|
|
1404
|
+
persistentUserDataDir
|
|
1405
|
+
);
|
|
1406
|
+
if (!metadataRecord.exists) {
|
|
1407
|
+
return await hasLiveSharedSessionPublisherOrClients(sessionDirPath);
|
|
1408
|
+
}
|
|
1409
|
+
if (!metadataRecord.metadata) {
|
|
1410
|
+
return true;
|
|
1411
|
+
}
|
|
1412
|
+
if (await getProcessLiveness(metadataRecord.metadata.browserOwner) === "dead") {
|
|
1413
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
1414
|
+
force: true,
|
|
1415
|
+
recursive: true
|
|
1416
|
+
}).catch(() => void 0);
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
async function waitForSharedRealBrowserSessionToDrain(persistentUserDataDir) {
|
|
1422
|
+
while (true) {
|
|
1423
|
+
if (!await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
await sleep3(SHARED_SESSION_RETRY_DELAY_MS);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
async function readSharedSessionMetadataRecord(persistentUserDataDir) {
|
|
1430
|
+
try {
|
|
1431
|
+
const raw = await (0, import_promises3.readFile)(
|
|
1432
|
+
buildSharedSessionMetadataPath(persistentUserDataDir),
|
|
1433
|
+
"utf8"
|
|
1434
|
+
);
|
|
1435
|
+
return {
|
|
1436
|
+
exists: true,
|
|
1437
|
+
metadata: parseSharedSessionMetadata(JSON.parse(raw))
|
|
1438
|
+
};
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
return {
|
|
1441
|
+
exists: getErrorCode2(error) !== "ENOENT",
|
|
1442
|
+
metadata: null
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async function hasLiveSharedSessionPublisherOrClients(sessionDirPath) {
|
|
1447
|
+
if (!(0, import_node_fs2.existsSync)(sessionDirPath)) {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
let entries;
|
|
1451
|
+
try {
|
|
1452
|
+
entries = await readDirNames(sessionDirPath);
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
1455
|
+
}
|
|
1456
|
+
let hasUnknownEntries = false;
|
|
1457
|
+
for (const entry of entries) {
|
|
1458
|
+
if (entry === SHARED_SESSION_METADATA_FILE) {
|
|
1459
|
+
return true;
|
|
1460
|
+
}
|
|
1461
|
+
if (entry === SHARED_SESSION_CLIENTS_DIR) {
|
|
1462
|
+
if (await hasDirectoryEntries((0, import_node_path3.join)(sessionDirPath, entry))) {
|
|
1463
|
+
return true;
|
|
1464
|
+
}
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
const owner = parseSharedSessionMetadataTempOwner(entry);
|
|
1468
|
+
if (!owner) {
|
|
1469
|
+
if (isSharedSessionMetadataTempFile(entry)) {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
hasUnknownEntries = true;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (await getProcessLiveness(owner) !== "dead") {
|
|
1476
|
+
return true;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (hasUnknownEntries) {
|
|
1480
|
+
return true;
|
|
1481
|
+
}
|
|
1482
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
1483
|
+
force: true,
|
|
1484
|
+
recursive: true
|
|
1485
|
+
}).catch(() => void 0);
|
|
1486
|
+
return false;
|
|
1487
|
+
}
|
|
1488
|
+
function buildSharedSessionMetadataPath(persistentUserDataDir) {
|
|
1489
|
+
return (0, import_node_path3.join)(
|
|
1490
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
1491
|
+
SHARED_SESSION_METADATA_FILE
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
function buildSharedSessionMetadataTempPath(sessionDirPath) {
|
|
1495
|
+
return (0, import_node_path3.join)(
|
|
1496
|
+
sessionDirPath,
|
|
1497
|
+
[
|
|
1498
|
+
SHARED_SESSION_METADATA_FILE,
|
|
1499
|
+
CURRENT_PROCESS_OWNER.pid,
|
|
1500
|
+
CURRENT_PROCESS_OWNER.processStartedAtMs,
|
|
1501
|
+
(0, import_node_crypto2.randomUUID)(),
|
|
1502
|
+
"tmp"
|
|
1503
|
+
].join(".")
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
function parseSharedSessionMetadata(value) {
|
|
1507
|
+
if (!value || typeof value !== "object") {
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1510
|
+
const parsed = value;
|
|
1511
|
+
const browserOwner = parseProcessOwner(parsed.browserOwner);
|
|
1512
|
+
const stateOwner = parseProcessOwner(parsed.stateOwner);
|
|
1513
|
+
const state = parsed.state === "launching" || parsed.state === "ready" || parsed.state === "closing" ? parsed.state : null;
|
|
1514
|
+
if (!browserOwner || !stateOwner || typeof parsed.createdAt !== "string" || typeof parsed.debugPort !== "number" || typeof parsed.executablePath !== "string" || typeof parsed.headless !== "boolean" || typeof parsed.persistentUserDataDir !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.sessionId !== "string" || !state) {
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
return {
|
|
1518
|
+
browserOwner,
|
|
1519
|
+
createdAt: parsed.createdAt,
|
|
1520
|
+
debugPort: parsed.debugPort,
|
|
1521
|
+
executablePath: parsed.executablePath,
|
|
1522
|
+
headless: parsed.headless,
|
|
1523
|
+
persistentUserDataDir: parsed.persistentUserDataDir,
|
|
1524
|
+
profileDirectory: parsed.profileDirectory,
|
|
1525
|
+
sessionId: parsed.sessionId,
|
|
1526
|
+
state,
|
|
1527
|
+
stateOwner
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function parseSharedSessionMetadataTempOwner(entryName) {
|
|
1531
|
+
if (!isSharedSessionMetadataTempFile(entryName)) {
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
const segments = entryName.split(".");
|
|
1535
|
+
if (segments.length < 5) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
return parseProcessOwner({
|
|
1539
|
+
pid: Number.parseInt(segments[2] ?? "", 10),
|
|
1540
|
+
processStartedAtMs: Number.parseInt(segments[3] ?? "", 10)
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
function isSharedSessionMetadataTempFile(entryName) {
|
|
1544
|
+
return entryName.startsWith(SHARED_SESSION_METADATA_TEMP_FILE_PREFIX) && entryName.endsWith(SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX);
|
|
1545
|
+
}
|
|
1546
|
+
function getErrorCode2(error) {
|
|
1547
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1548
|
+
}
|
|
1549
|
+
async function hasDirectoryEntries(dirPath) {
|
|
1550
|
+
try {
|
|
1551
|
+
return (await readDirNames(dirPath)).length > 0;
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
async function readDirNames(dirPath) {
|
|
1557
|
+
return await (0, import_promises3.readdir)(dirPath, { encoding: "utf8" });
|
|
1558
|
+
}
|
|
1559
|
+
async function sleep3(ms) {
|
|
1560
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/browser/persistent-profile.ts
|
|
1564
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
1565
|
+
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
1566
|
+
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
1567
|
+
var OPENSTEER_RUNTIME_CREATING_FILE = ".opensteer-runtime-creating.json";
|
|
1568
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
|
|
1569
|
+
var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
|
|
1570
|
+
var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
|
|
1571
|
+
"SingletonCookie",
|
|
1572
|
+
"SingletonLock",
|
|
1573
|
+
"SingletonSocket",
|
|
1574
|
+
"DevToolsActivePort",
|
|
1575
|
+
"lockfile"
|
|
1576
|
+
]);
|
|
1577
|
+
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
1578
|
+
...CHROME_SINGLETON_ENTRIES,
|
|
1579
|
+
OPENSTEER_META_FILE,
|
|
1580
|
+
OPENSTEER_RUNTIME_META_FILE,
|
|
1581
|
+
OPENSTEER_RUNTIME_CREATING_FILE
|
|
1582
|
+
]);
|
|
1583
|
+
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1584
|
+
"Crash Reports",
|
|
1585
|
+
"Crashpad",
|
|
1586
|
+
"BrowserMetrics",
|
|
1587
|
+
"GrShaderCache",
|
|
1588
|
+
"ShaderCache",
|
|
1589
|
+
"GraphiteDawnCache",
|
|
1590
|
+
"component_crx_cache",
|
|
1591
|
+
"Crowd Deny",
|
|
1592
|
+
"hyphen-data",
|
|
1593
|
+
"OnDeviceHeadSuggestModel",
|
|
1594
|
+
"OptimizationGuidePredictionModels",
|
|
1595
|
+
"Segmentation Platform",
|
|
1596
|
+
"SmartCardDeviceNames",
|
|
1597
|
+
"WidevineCdm",
|
|
1598
|
+
"pnacl"
|
|
1599
|
+
]);
|
|
1600
|
+
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
1601
|
+
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1602
|
+
const targetUserDataDir = (0, import_node_path4.join)(
|
|
1603
|
+
expandHome(profilesRootDir),
|
|
1604
|
+
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
1605
|
+
);
|
|
1606
|
+
const sourceProfileDir = (0, import_node_path4.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
1607
|
+
const metadata = buildPersistentProfileMetadata(
|
|
1608
|
+
resolvedSourceUserDataDir,
|
|
1609
|
+
profileDirectory
|
|
1610
|
+
);
|
|
1611
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetUserDataDir), { recursive: true });
|
|
1612
|
+
if (await isHealthyPersistentProfile(
|
|
1613
|
+
targetUserDataDir,
|
|
1614
|
+
resolvedSourceUserDataDir,
|
|
1615
|
+
profileDirectory
|
|
1616
|
+
) && !await isPersistentProfileWriteLocked(targetUserDataDir)) {
|
|
1617
|
+
return {
|
|
1618
|
+
created: false,
|
|
1619
|
+
userDataDir: targetUserDataDir
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
return await withPersistentProfileWriteAccess(targetUserDataDir, async () => {
|
|
1623
|
+
await recoverPersistentProfileBackup(targetUserDataDir);
|
|
1624
|
+
await cleanOrphanedOwnedDirs(
|
|
1625
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
1626
|
+
buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
|
|
1627
|
+
);
|
|
1628
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
const created = await createPersistentProfileClone(
|
|
1634
|
+
resolvedSourceUserDataDir,
|
|
1635
|
+
sourceProfileDir,
|
|
1636
|
+
targetUserDataDir,
|
|
1637
|
+
profileDirectory,
|
|
1638
|
+
metadata
|
|
1639
|
+
);
|
|
1640
|
+
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1641
|
+
return {
|
|
1642
|
+
created,
|
|
1643
|
+
userDataDir: targetUserDataDir
|
|
1644
|
+
};
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
async function clearPersistentProfileSingletons(userDataDir) {
|
|
1648
|
+
await Promise.all(
|
|
1649
|
+
[...CHROME_SINGLETON_ENTRIES].map(
|
|
1650
|
+
(entry) => (0, import_promises4.rm)((0, import_node_path4.join)(userDataDir, entry), {
|
|
1651
|
+
force: true,
|
|
1652
|
+
recursive: true
|
|
1653
|
+
}).catch(() => void 0)
|
|
1654
|
+
)
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
1658
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
1659
|
+
const sourceLabel = sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "user-data");
|
|
1660
|
+
const profileLabel = sanitizePathSegment(profileDirectory || "Default");
|
|
1661
|
+
return `${sourceLabel}-${profileLabel}-${hash}`;
|
|
1662
|
+
}
|
|
1663
|
+
function defaultPersistentProfilesRootDir() {
|
|
1664
|
+
return (0, import_node_path4.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
|
|
1665
|
+
}
|
|
1666
|
+
function sanitizePathSegment(value) {
|
|
1667
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
1668
|
+
return sanitized.replace(/^-|-$/g, "") || "profile";
|
|
1669
|
+
}
|
|
1670
|
+
function isProfileDirectory(userDataDir, entry) {
|
|
1671
|
+
return (0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, entry, "Preferences"));
|
|
1672
|
+
}
|
|
1673
|
+
async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
|
|
1674
|
+
let entries;
|
|
1675
|
+
try {
|
|
1676
|
+
entries = await (0, import_promises4.readdir)(sourceUserDataDir);
|
|
1677
|
+
} catch {
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
const copyTasks = [];
|
|
1681
|
+
for (const entry of entries) {
|
|
1682
|
+
if (COPY_SKIP_ENTRIES.has(entry)) continue;
|
|
1683
|
+
if (entry === targetProfileDirectory) continue;
|
|
1684
|
+
const sourcePath = (0, import_node_path4.join)(sourceUserDataDir, entry);
|
|
1685
|
+
const targetPath = (0, import_node_path4.join)(targetUserDataDir, entry);
|
|
1686
|
+
if ((0, import_node_fs3.existsSync)(targetPath)) continue;
|
|
1687
|
+
let entryStat;
|
|
1688
|
+
try {
|
|
1689
|
+
entryStat = await (0, import_promises4.stat)(sourcePath);
|
|
1690
|
+
} catch {
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
if (entryStat.isFile()) {
|
|
1694
|
+
copyTasks.push((0, import_promises4.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1695
|
+
} else if (entryStat.isDirectory()) {
|
|
1696
|
+
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1697
|
+
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1698
|
+
copyTasks.push(
|
|
1699
|
+
(0, import_promises4.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1700
|
+
() => void 0
|
|
1701
|
+
)
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
await Promise.all(copyTasks);
|
|
1706
|
+
}
|
|
1707
|
+
async function writePersistentProfileMetadata(userDataDir, metadata) {
|
|
1708
|
+
await (0, import_promises4.writeFile)(
|
|
1709
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE),
|
|
1710
|
+
JSON.stringify(metadata, null, 2)
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
|
|
1714
|
+
return {
|
|
1715
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1716
|
+
profileDirectory,
|
|
1717
|
+
source: sourceUserDataDir
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
1721
|
+
if ((0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
1722
|
+
return false;
|
|
1723
|
+
}
|
|
1724
|
+
const tempUserDataDir = await (0, import_promises4.mkdtemp)(
|
|
1725
|
+
buildPersistentProfileTempDirPrefix(targetUserDataDir)
|
|
1726
|
+
);
|
|
1727
|
+
let published = false;
|
|
1728
|
+
try {
|
|
1729
|
+
await materializePersistentProfileSnapshot(
|
|
1730
|
+
sourceUserDataDir,
|
|
1731
|
+
sourceProfileDir,
|
|
1732
|
+
tempUserDataDir,
|
|
1733
|
+
profileDirectory,
|
|
1734
|
+
metadata
|
|
1735
|
+
);
|
|
1736
|
+
try {
|
|
1737
|
+
await (0, import_promises4.rename)(tempUserDataDir, targetUserDataDir);
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
if (wasDirPublishedByAnotherProcess2(error, targetUserDataDir)) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
throw error;
|
|
1743
|
+
}
|
|
1744
|
+
published = true;
|
|
1745
|
+
return true;
|
|
1746
|
+
} finally {
|
|
1747
|
+
if (!published) {
|
|
1748
|
+
await (0, import_promises4.rm)(tempUserDataDir, {
|
|
1749
|
+
recursive: true,
|
|
1750
|
+
force: true
|
|
1751
|
+
}).catch(() => void 0);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
1756
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
1757
|
+
throw new Error(
|
|
1758
|
+
`Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
await (0, import_promises4.cp)(sourceProfileDir, (0, import_node_path4.join)(targetUserDataDir, profileDirectory), {
|
|
1762
|
+
recursive: true
|
|
1763
|
+
});
|
|
1764
|
+
await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
|
|
1765
|
+
await writePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1766
|
+
}
|
|
1767
|
+
async function readRuntimeProfileCreationMarker(userDataDir) {
|
|
1768
|
+
try {
|
|
1769
|
+
const raw = await (0, import_promises4.readFile)(
|
|
1770
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
|
|
1771
|
+
"utf8"
|
|
1772
|
+
);
|
|
1773
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
1774
|
+
} catch {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
async function ensurePersistentProfileMetadata(userDataDir, metadata) {
|
|
1779
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE))) {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
await writePersistentProfileMetadata(userDataDir, metadata);
|
|
1783
|
+
}
|
|
1784
|
+
async function recoverPersistentProfileBackup(targetUserDataDir) {
|
|
1785
|
+
const backupDirPaths = await listPersistentProfileBackupDirs(targetUserDataDir);
|
|
1786
|
+
if (backupDirPaths.length === 0) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
1790
|
+
const [latestBackupDirPath, ...staleBackupDirPaths] = backupDirPaths;
|
|
1791
|
+
await (0, import_promises4.rename)(latestBackupDirPath, targetUserDataDir);
|
|
1792
|
+
await Promise.all(
|
|
1793
|
+
staleBackupDirPaths.map(
|
|
1794
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
1795
|
+
recursive: true,
|
|
1796
|
+
force: true
|
|
1797
|
+
}).catch(() => void 0)
|
|
1798
|
+
)
|
|
1799
|
+
);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
await Promise.all(
|
|
1803
|
+
backupDirPaths.map(
|
|
1804
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
1805
|
+
recursive: true,
|
|
1806
|
+
force: true
|
|
1807
|
+
}).catch(() => void 0)
|
|
1808
|
+
)
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
async function listPersistentProfileBackupDirs(targetUserDataDir) {
|
|
1812
|
+
const profilesDir = (0, import_node_path4.dirname)(targetUserDataDir);
|
|
1813
|
+
let entries;
|
|
1814
|
+
try {
|
|
1815
|
+
entries = await (0, import_promises4.readdir)(profilesDir, {
|
|
1816
|
+
encoding: "utf8",
|
|
1817
|
+
withFileTypes: true
|
|
1818
|
+
});
|
|
1819
|
+
} catch {
|
|
1820
|
+
return [];
|
|
1821
|
+
}
|
|
1822
|
+
const backupDirNamePrefix = buildPersistentProfileBackupDirNamePrefix(targetUserDataDir);
|
|
1823
|
+
return entries.filter(
|
|
1824
|
+
(entry) => entry.isDirectory() && entry.name.startsWith(backupDirNamePrefix)
|
|
1825
|
+
).map((entry) => (0, import_node_path4.join)(profilesDir, entry.name)).sort((leftPath, rightPath) => rightPath.localeCompare(leftPath));
|
|
1826
|
+
}
|
|
1827
|
+
async function readPersistentProfileMetadata(userDataDir) {
|
|
1828
|
+
try {
|
|
1829
|
+
const raw = await (0, import_promises4.readFile)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
|
|
1830
|
+
const parsed = JSON.parse(raw);
|
|
1831
|
+
if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
return {
|
|
1835
|
+
createdAt: parsed.createdAt,
|
|
1836
|
+
profileDirectory: parsed.profileDirectory,
|
|
1837
|
+
source: parsed.source
|
|
1838
|
+
};
|
|
1839
|
+
} catch {
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
async function isHealthyPersistentProfile(userDataDir, expectedSourceUserDataDir, expectedProfileDirectory) {
|
|
1844
|
+
if (!(0, import_node_fs3.existsSync)(userDataDir) || !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, expectedProfileDirectory))) {
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1847
|
+
const metadata = await readPersistentProfileMetadata(userDataDir);
|
|
1848
|
+
return metadata?.source === expectedSourceUserDataDir && metadata.profileDirectory === expectedProfileDirectory;
|
|
1849
|
+
}
|
|
1850
|
+
function wasDirPublishedByAnotherProcess2(error, targetDirPath) {
|
|
1851
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1852
|
+
return (0, import_node_fs3.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
1853
|
+
}
|
|
1854
|
+
async function withPersistentProfileWriteAccess(targetUserDataDir, action) {
|
|
1855
|
+
const releaseWriteLock = await acquirePersistentProfileWriteLock(
|
|
1856
|
+
targetUserDataDir
|
|
1857
|
+
);
|
|
1858
|
+
try {
|
|
1859
|
+
await waitForRuntimeProfileCreationsToDrain(targetUserDataDir);
|
|
1860
|
+
await waitForSharedRealBrowserSessionToDrain(targetUserDataDir);
|
|
1861
|
+
return await action();
|
|
1862
|
+
} finally {
|
|
1863
|
+
await releaseWriteLock();
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
|
|
1867
|
+
return (0, import_node_path4.join)(
|
|
1868
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
1869
|
+
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-`
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
|
|
1873
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-tmp-`;
|
|
1874
|
+
}
|
|
1875
|
+
function buildPersistentProfileBackupDirNamePrefix(targetUserDataDir) {
|
|
1876
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-backup-`;
|
|
1877
|
+
}
|
|
1878
|
+
function buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir) {
|
|
1879
|
+
return (0, import_node_path4.join)(
|
|
1880
|
+
(0, import_node_path4.dirname)(persistentUserDataDir),
|
|
1881
|
+
`${(0, import_node_path4.basename)(persistentUserDataDir)}.creating`
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
function buildRuntimeProfileCreationRegistrationPath(persistentUserDataDir, runtimeUserDataDir) {
|
|
1885
|
+
const key = (0, import_node_crypto3.createHash)("sha256").update(runtimeUserDataDir).digest("hex").slice(0, 16);
|
|
1886
|
+
return (0, import_node_path4.join)(
|
|
1887
|
+
buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir),
|
|
1888
|
+
`${key}.json`
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
async function clearRuntimeProfileCreationState(runtimeUserDataDir, persistentUserDataDir) {
|
|
1892
|
+
await Promise.all([
|
|
1893
|
+
(0, import_promises4.rm)((0, import_node_path4.join)(runtimeUserDataDir, OPENSTEER_RUNTIME_CREATING_FILE), {
|
|
1894
|
+
force: true
|
|
1895
|
+
}).catch(() => void 0),
|
|
1896
|
+
(0, import_promises4.rm)(
|
|
1897
|
+
buildRuntimeProfileCreationRegistrationPath(
|
|
1898
|
+
persistentUserDataDir,
|
|
1899
|
+
runtimeUserDataDir
|
|
1900
|
+
),
|
|
1901
|
+
{
|
|
1902
|
+
force: true
|
|
1903
|
+
}
|
|
1904
|
+
).catch(() => void 0)
|
|
1905
|
+
]);
|
|
1906
|
+
}
|
|
1907
|
+
async function listRuntimeProfileCreationRegistrations(persistentUserDataDir) {
|
|
1908
|
+
const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
|
|
1909
|
+
persistentUserDataDir
|
|
1910
|
+
);
|
|
1911
|
+
let entries;
|
|
1912
|
+
try {
|
|
1913
|
+
entries = await (0, import_promises4.readdir)(registryDirPath, {
|
|
1914
|
+
encoding: "utf8",
|
|
1915
|
+
withFileTypes: true
|
|
1916
|
+
});
|
|
1917
|
+
} catch {
|
|
1918
|
+
return [];
|
|
1919
|
+
}
|
|
1920
|
+
return await Promise.all(
|
|
1921
|
+
entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
1922
|
+
const filePath = (0, import_node_path4.join)(registryDirPath, entry.name);
|
|
1923
|
+
return {
|
|
1924
|
+
filePath,
|
|
1925
|
+
marker: await readRuntimeProfileCreationRegistration(filePath)
|
|
1926
|
+
};
|
|
1927
|
+
})
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
async function readRuntimeProfileCreationRegistration(filePath) {
|
|
1931
|
+
try {
|
|
1932
|
+
const raw = await (0, import_promises4.readFile)(filePath, "utf8");
|
|
1933
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
1934
|
+
} catch {
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
|
|
1939
|
+
let entries;
|
|
1940
|
+
try {
|
|
1941
|
+
entries = await (0, import_promises4.readdir)(rootDir, {
|
|
1942
|
+
encoding: "utf8",
|
|
1943
|
+
withFileTypes: true
|
|
1944
|
+
});
|
|
1945
|
+
} catch {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
await Promise.all(
|
|
1949
|
+
entries.map(async (entry) => {
|
|
1950
|
+
if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
await (0, import_promises4.rm)((0, import_node_path4.join)(rootDir, entry.name), {
|
|
1957
|
+
recursive: true,
|
|
1958
|
+
force: true
|
|
1959
|
+
}).catch(() => void 0);
|
|
1960
|
+
})
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
|
|
1964
|
+
const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
|
|
1965
|
+
return owner ? await getProcessLiveness(owner) !== "dead" : false;
|
|
1966
|
+
}
|
|
1967
|
+
async function hasActiveRuntimeProfileCreations(persistentUserDataDir) {
|
|
1968
|
+
const registrations = await listRuntimeProfileCreationRegistrations(
|
|
1969
|
+
persistentUserDataDir
|
|
1970
|
+
);
|
|
1971
|
+
let hasLiveCreation = false;
|
|
1972
|
+
for (const registration of registrations) {
|
|
1973
|
+
const marker = registration.marker;
|
|
1974
|
+
if (!marker || marker.persistentUserDataDir !== persistentUserDataDir) {
|
|
1975
|
+
await (0, import_promises4.rm)(registration.filePath, {
|
|
1976
|
+
force: true
|
|
1977
|
+
}).catch(() => void 0);
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
const runtimeMarker = await readRuntimeProfileCreationMarker(
|
|
1981
|
+
marker.runtimeUserDataDir
|
|
1982
|
+
);
|
|
1983
|
+
if (!runtimeMarker || runtimeMarker.persistentUserDataDir !== persistentUserDataDir || runtimeMarker.runtimeUserDataDir !== marker.runtimeUserDataDir) {
|
|
1984
|
+
await clearRuntimeProfileCreationState(
|
|
1985
|
+
marker.runtimeUserDataDir,
|
|
1986
|
+
persistentUserDataDir
|
|
1987
|
+
);
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
if (await getProcessLiveness(runtimeMarker.creator) === "dead") {
|
|
1991
|
+
await clearRuntimeProfileCreationState(
|
|
1992
|
+
marker.runtimeUserDataDir,
|
|
1993
|
+
persistentUserDataDir
|
|
1994
|
+
);
|
|
1995
|
+
await (0, import_promises4.rm)(marker.runtimeUserDataDir, {
|
|
1996
|
+
recursive: true,
|
|
1997
|
+
force: true
|
|
1998
|
+
}).catch(() => void 0);
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
hasLiveCreation = true;
|
|
2002
|
+
}
|
|
2003
|
+
return hasLiveCreation;
|
|
2004
|
+
}
|
|
2005
|
+
async function waitForRuntimeProfileCreationsToDrain(persistentUserDataDir) {
|
|
2006
|
+
while (true) {
|
|
2007
|
+
if (!await hasActiveRuntimeProfileCreations(persistentUserDataDir)) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
function parseRuntimeProfileCreationMarker(value) {
|
|
2014
|
+
if (!value || typeof value !== "object") {
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
const parsed = value;
|
|
2018
|
+
const creator = parseProcessOwner(parsed.creator);
|
|
2019
|
+
const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
|
|
2020
|
+
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
2021
|
+
const runtimeUserDataDir = typeof parsed.runtimeUserDataDir === "string" ? parsed.runtimeUserDataDir : void 0;
|
|
2022
|
+
if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || runtimeUserDataDir === void 0) {
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
return {
|
|
2026
|
+
creator,
|
|
2027
|
+
persistentUserDataDir,
|
|
2028
|
+
profileDirectory,
|
|
2029
|
+
runtimeUserDataDir
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
|
|
2033
|
+
const remainder = ownedDirName.slice(ownedDirPrefix.length);
|
|
2034
|
+
const firstDashIndex = remainder.indexOf("-");
|
|
2035
|
+
const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
|
|
2036
|
+
if (firstDashIndex === -1 || secondDashIndex === -1) {
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
|
|
2040
|
+
const processStartedAtMs = Number.parseInt(
|
|
2041
|
+
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
2042
|
+
10
|
|
2043
|
+
);
|
|
2044
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
2045
|
+
return null;
|
|
2046
|
+
}
|
|
2047
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
return { pid, processStartedAtMs };
|
|
2051
|
+
}
|
|
2052
|
+
async function sleep4(ms) {
|
|
2053
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// src/browser/shared-real-browser-session.ts
|
|
2057
|
+
var import_node_crypto4 = require("crypto");
|
|
2058
|
+
var import_node_child_process3 = require("child_process");
|
|
2059
|
+
var import_promises5 = require("fs/promises");
|
|
2060
|
+
var import_node_net = require("net");
|
|
2061
|
+
var import_node_path5 = require("path");
|
|
2062
|
+
var import_playwright = require("playwright");
|
|
2063
|
+
var SHARED_SESSION_RETRY_DELAY_MS2 = 50;
|
|
2064
|
+
async function acquireSharedRealBrowserSession(options) {
|
|
2065
|
+
const reservation = await reserveSharedSessionClient(options);
|
|
2066
|
+
const sessionContext = await attachToSharedSession(reservation, options);
|
|
2067
|
+
let closed = false;
|
|
2068
|
+
return {
|
|
2069
|
+
browser: sessionContext.browser,
|
|
2070
|
+
context: sessionContext.context,
|
|
2071
|
+
page: sessionContext.page,
|
|
2072
|
+
close: async () => {
|
|
2073
|
+
if (closed) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
closed = true;
|
|
2077
|
+
await releaseSharedSessionClient(sessionContext);
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
|
|
2082
|
+
if (platformName === "win32") {
|
|
2083
|
+
return {
|
|
2084
|
+
detached: false,
|
|
2085
|
+
killStrategy: "taskkill",
|
|
2086
|
+
shouldUnref: true
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
if (platformName === "darwin") {
|
|
2090
|
+
return {
|
|
2091
|
+
detached: false,
|
|
2092
|
+
killStrategy: "process",
|
|
2093
|
+
shouldUnref: true
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
return {
|
|
2097
|
+
detached: true,
|
|
2098
|
+
killStrategy: "process-group",
|
|
2099
|
+
shouldUnref: true
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
async function reserveSharedSessionClient(options) {
|
|
2103
|
+
while (true) {
|
|
2104
|
+
const outcome = await withPersistentProfileControlLock(
|
|
2105
|
+
options.persistentProfile.userDataDir,
|
|
2106
|
+
async () => {
|
|
2107
|
+
if (await isPersistentProfileWriteLocked(
|
|
2108
|
+
options.persistentProfile.userDataDir
|
|
2109
|
+
)) {
|
|
2110
|
+
return { kind: "wait" };
|
|
2111
|
+
}
|
|
2112
|
+
if (await hasActiveRuntimeProfileCreations(
|
|
2113
|
+
options.persistentProfile.userDataDir
|
|
2114
|
+
)) {
|
|
2115
|
+
return { kind: "wait" };
|
|
2116
|
+
}
|
|
2117
|
+
return await withSharedSessionLock(
|
|
2118
|
+
options.persistentProfile.userDataDir,
|
|
2119
|
+
async () => {
|
|
2120
|
+
const state = await inspectSharedSessionState(options);
|
|
2121
|
+
if (state.kind === "wait") {
|
|
2122
|
+
return { kind: "wait" };
|
|
2123
|
+
}
|
|
2124
|
+
if (state.kind === "ready") {
|
|
2125
|
+
return {
|
|
2126
|
+
kind: "ready",
|
|
2127
|
+
reservation: await registerSharedSessionClient(
|
|
2128
|
+
options.persistentProfile.userDataDir,
|
|
2129
|
+
state.metadata
|
|
2130
|
+
)
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
return {
|
|
2134
|
+
kind: "launch",
|
|
2135
|
+
reservation: await launchSharedSession(options)
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
if (outcome.kind === "wait") {
|
|
2142
|
+
await sleep5(SHARED_SESSION_RETRY_DELAY_MS2);
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (outcome.kind === "ready") {
|
|
2146
|
+
return outcome.reservation;
|
|
2147
|
+
}
|
|
2148
|
+
try {
|
|
2149
|
+
await waitForSharedSessionReady(
|
|
2150
|
+
outcome.reservation.metadata,
|
|
2151
|
+
options.timeoutMs
|
|
2152
|
+
);
|
|
2153
|
+
} catch (error) {
|
|
2154
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
2155
|
+
throw error;
|
|
2156
|
+
}
|
|
2157
|
+
try {
|
|
2158
|
+
return await withSharedSessionLock(
|
|
2159
|
+
options.persistentProfile.userDataDir,
|
|
2160
|
+
async () => {
|
|
2161
|
+
const metadata = await readSharedSessionMetadata(
|
|
2162
|
+
options.persistentProfile.userDataDir
|
|
2163
|
+
);
|
|
2164
|
+
if (!metadata || metadata.sessionId !== outcome.reservation.metadata.sessionId || !processOwnersEqual(
|
|
2165
|
+
metadata.browserOwner,
|
|
2166
|
+
outcome.reservation.launchedBrowserOwner
|
|
2167
|
+
)) {
|
|
2168
|
+
throw new Error(
|
|
2169
|
+
"The shared real-browser session changed before launch finalized."
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
const readyMetadata = {
|
|
2173
|
+
...metadata,
|
|
2174
|
+
state: "ready"
|
|
2175
|
+
};
|
|
2176
|
+
await writeSharedSessionMetadata(
|
|
2177
|
+
options.persistentProfile.userDataDir,
|
|
2178
|
+
readyMetadata
|
|
2179
|
+
);
|
|
2180
|
+
return await registerSharedSessionClient(
|
|
2181
|
+
options.persistentProfile.userDataDir,
|
|
2182
|
+
readyMetadata
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
);
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
2188
|
+
throw error;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function attachToSharedSession(reservation, options) {
|
|
2193
|
+
let browser = null;
|
|
2194
|
+
let page = null;
|
|
2195
|
+
try {
|
|
2196
|
+
const browserWsUrl = await resolveCdpWebSocketUrl(
|
|
2197
|
+
buildSharedSessionDiscoveryUrl(reservation.metadata.debugPort),
|
|
2198
|
+
options.timeoutMs
|
|
2199
|
+
);
|
|
2200
|
+
browser = await import_playwright.chromium.connectOverCDP(browserWsUrl, {
|
|
2201
|
+
timeout: options.timeoutMs
|
|
2202
|
+
});
|
|
2203
|
+
const context = getPrimaryBrowserContext(browser);
|
|
2204
|
+
page = await getSharedSessionPage(context, reservation.reuseExistingPage);
|
|
2205
|
+
if (options.initialUrl) {
|
|
2206
|
+
await page.goto(options.initialUrl, {
|
|
2207
|
+
timeout: options.timeoutMs,
|
|
2208
|
+
waitUntil: "domcontentloaded"
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
return {
|
|
2212
|
+
browser,
|
|
2213
|
+
clientId: reservation.client.clientId,
|
|
2214
|
+
context,
|
|
2215
|
+
page,
|
|
2216
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
2217
|
+
sessionId: reservation.metadata.sessionId
|
|
2218
|
+
};
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
if (page) {
|
|
2221
|
+
await page.close().catch(() => void 0);
|
|
2222
|
+
}
|
|
2223
|
+
if (browser) {
|
|
2224
|
+
await browser.close().catch(() => void 0);
|
|
2225
|
+
}
|
|
2226
|
+
await cleanupFailedSharedSessionAttach({
|
|
2227
|
+
clientId: reservation.client.clientId,
|
|
2228
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
2229
|
+
sessionId: reservation.metadata.sessionId
|
|
2230
|
+
});
|
|
2231
|
+
throw error;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
async function releaseSharedSessionClient(context) {
|
|
2235
|
+
const releasePlan = await prepareSharedSessionCloseIfIdle(
|
|
2236
|
+
context.persistentUserDataDir,
|
|
2237
|
+
context.clientId,
|
|
2238
|
+
context.sessionId
|
|
2239
|
+
);
|
|
2240
|
+
if (releasePlan.closeBrowser) {
|
|
2241
|
+
await closeSharedSessionBrowser(
|
|
2242
|
+
context.persistentUserDataDir,
|
|
2243
|
+
releasePlan,
|
|
2244
|
+
context.browser
|
|
2245
|
+
);
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
await context.page.close().catch(() => void 0);
|
|
2249
|
+
await context.browser.close().catch(() => void 0);
|
|
2250
|
+
}
|
|
2251
|
+
async function inspectSharedSessionState(options) {
|
|
2252
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
2253
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
2254
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
2255
|
+
if (!metadata) {
|
|
2256
|
+
if (liveClients.length > 0) {
|
|
2257
|
+
throw new Error(
|
|
2258
|
+
`Shared real-browser session metadata for "${persistentUserDataDir}" is missing while clients are still attached.`
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
2262
|
+
force: true,
|
|
2263
|
+
recursive: true
|
|
2264
|
+
}).catch(() => void 0);
|
|
2265
|
+
return { kind: "missing" };
|
|
2266
|
+
}
|
|
2267
|
+
assertSharedSessionCompatibility(metadata, options);
|
|
2268
|
+
const browserState = await getProcessLiveness(metadata.browserOwner);
|
|
2269
|
+
if (browserState === "dead") {
|
|
2270
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
2271
|
+
force: true,
|
|
2272
|
+
recursive: true
|
|
2273
|
+
}).catch(() => void 0);
|
|
2274
|
+
return { kind: "missing" };
|
|
2275
|
+
}
|
|
2276
|
+
if (metadata.state === "ready") {
|
|
2277
|
+
return {
|
|
2278
|
+
kind: "ready",
|
|
2279
|
+
metadata
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
const stateOwnerState = await getProcessLiveness(metadata.stateOwner);
|
|
2283
|
+
if (stateOwnerState === "dead") {
|
|
2284
|
+
const recoveredMetadata = {
|
|
2285
|
+
...metadata,
|
|
2286
|
+
state: "ready"
|
|
2287
|
+
};
|
|
2288
|
+
await writeSharedSessionMetadata(persistentUserDataDir, recoveredMetadata);
|
|
2289
|
+
return {
|
|
2290
|
+
kind: "ready",
|
|
2291
|
+
metadata: recoveredMetadata
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
return { kind: "wait" };
|
|
2295
|
+
}
|
|
2296
|
+
async function launchSharedSession(options) {
|
|
2297
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
2298
|
+
await clearPersistentProfileSingletons(persistentUserDataDir);
|
|
2299
|
+
const debugPort = await reserveDebugPort();
|
|
2300
|
+
const launchArgs = buildRealBrowserLaunchArgs({
|
|
2301
|
+
debugPort,
|
|
2302
|
+
headless: options.headless,
|
|
2303
|
+
profileDirectory: options.profileDirectory,
|
|
2304
|
+
userDataDir: persistentUserDataDir
|
|
2305
|
+
});
|
|
2306
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
2307
|
+
const processHandle = (0, import_node_child_process3.spawn)(options.executablePath, launchArgs, {
|
|
2308
|
+
detached: processPolicy.detached,
|
|
2309
|
+
stdio: "ignore"
|
|
2310
|
+
});
|
|
2311
|
+
if (processPolicy.shouldUnref) {
|
|
2312
|
+
processHandle.unref();
|
|
2313
|
+
}
|
|
2314
|
+
try {
|
|
2315
|
+
const browserOwner = await waitForSpawnedProcessOwner(
|
|
2316
|
+
processHandle.pid,
|
|
2317
|
+
options.timeoutMs
|
|
2318
|
+
);
|
|
2319
|
+
const metadata = {
|
|
2320
|
+
browserOwner,
|
|
2321
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2322
|
+
debugPort,
|
|
2323
|
+
executablePath: options.executablePath,
|
|
2324
|
+
headless: options.headless,
|
|
2325
|
+
persistentUserDataDir,
|
|
2326
|
+
profileDirectory: options.profileDirectory,
|
|
2327
|
+
sessionId: (0, import_node_crypto4.randomUUID)(),
|
|
2328
|
+
state: "launching",
|
|
2329
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
2330
|
+
};
|
|
2331
|
+
await writeSharedSessionMetadata(persistentUserDataDir, metadata);
|
|
2332
|
+
return {
|
|
2333
|
+
launchedBrowserOwner: browserOwner,
|
|
2334
|
+
metadata
|
|
2335
|
+
};
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
await killSpawnedBrowserProcess(processHandle);
|
|
2338
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
2339
|
+
force: true,
|
|
2340
|
+
recursive: true
|
|
2341
|
+
}).catch(() => void 0);
|
|
2342
|
+
throw error;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
async function cleanupFailedSharedSessionLaunch(reservation) {
|
|
2346
|
+
const shouldPreserveLiveBrowser = await withSharedSessionLock(
|
|
2347
|
+
reservation.metadata.persistentUserDataDir,
|
|
2348
|
+
async () => {
|
|
2349
|
+
const metadata = await readSharedSessionMetadata(
|
|
2350
|
+
reservation.metadata.persistentUserDataDir
|
|
2351
|
+
);
|
|
2352
|
+
if (metadata && metadata.sessionId === reservation.metadata.sessionId && processOwnersEqual(
|
|
2353
|
+
metadata.browserOwner,
|
|
2354
|
+
reservation.launchedBrowserOwner
|
|
2355
|
+
)) {
|
|
2356
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
2357
|
+
const readyMetadata = {
|
|
2358
|
+
...metadata,
|
|
2359
|
+
state: "ready"
|
|
2360
|
+
};
|
|
2361
|
+
await writeSharedSessionMetadata(
|
|
2362
|
+
reservation.metadata.persistentUserDataDir,
|
|
2363
|
+
readyMetadata
|
|
2364
|
+
);
|
|
2365
|
+
return true;
|
|
2366
|
+
}
|
|
2367
|
+
await (0, import_promises5.rm)(
|
|
2368
|
+
buildSharedSessionDirPath(
|
|
2369
|
+
reservation.metadata.persistentUserDataDir
|
|
2370
|
+
),
|
|
2371
|
+
{
|
|
2372
|
+
force: true,
|
|
2373
|
+
recursive: true
|
|
2374
|
+
}
|
|
2375
|
+
).catch(() => void 0);
|
|
2376
|
+
}
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
);
|
|
2380
|
+
if (shouldPreserveLiveBrowser) {
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
|
|
2384
|
+
await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
|
|
2385
|
+
}
|
|
2386
|
+
async function cleanupFailedSharedSessionAttach(options) {
|
|
2387
|
+
const closePlan = await prepareSharedSessionCloseIfIdle(
|
|
2388
|
+
options.persistentUserDataDir,
|
|
2389
|
+
options.clientId,
|
|
2390
|
+
options.sessionId
|
|
2391
|
+
);
|
|
2392
|
+
if (!closePlan.closeBrowser) {
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
|
|
2396
|
+
}
|
|
2397
|
+
async function waitForSharedSessionReady(metadata, timeoutMs) {
|
|
2398
|
+
await resolveCdpWebSocketUrl(
|
|
2399
|
+
buildSharedSessionDiscoveryUrl(metadata.debugPort),
|
|
2400
|
+
timeoutMs
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
function buildRealBrowserLaunchArgs(options) {
|
|
2404
|
+
const args = [
|
|
2405
|
+
`--user-data-dir=${options.userDataDir}`,
|
|
2406
|
+
`--profile-directory=${options.profileDirectory}`,
|
|
2407
|
+
`--remote-debugging-port=${options.debugPort}`,
|
|
2408
|
+
"--disable-blink-features=AutomationControlled"
|
|
2409
|
+
];
|
|
2410
|
+
if (options.headless) {
|
|
2411
|
+
args.push("--headless=new");
|
|
2412
|
+
}
|
|
2413
|
+
return args;
|
|
2414
|
+
}
|
|
2415
|
+
async function requestBrowserShutdown(browser) {
|
|
2416
|
+
let session2 = null;
|
|
2417
|
+
try {
|
|
2418
|
+
session2 = await browser.newBrowserCDPSession();
|
|
2419
|
+
await session2.send("Browser.close");
|
|
2420
|
+
} catch {
|
|
2421
|
+
} finally {
|
|
2422
|
+
await session2?.detach().catch(() => void 0);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
async function killOwnedBrowserProcess(owner) {
|
|
2426
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
await killOwnedBrowserProcessByPid(owner.pid);
|
|
2430
|
+
}
|
|
2431
|
+
async function killSpawnedBrowserProcess(processHandle) {
|
|
2432
|
+
const pid = processHandle.pid;
|
|
2433
|
+
if (!pid || processHandle.exitCode !== null) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
await killOwnedBrowserProcessByPid(pid);
|
|
2437
|
+
await waitForPidToExit(pid, 2e3);
|
|
2438
|
+
}
|
|
2439
|
+
async function killOwnedBrowserProcessByPid(pid) {
|
|
2440
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
2441
|
+
if (processPolicy.killStrategy === "taskkill") {
|
|
2442
|
+
await new Promise((resolve) => {
|
|
2443
|
+
const killer = (0, import_node_child_process3.spawn)(
|
|
2444
|
+
"taskkill",
|
|
2445
|
+
["/pid", String(pid), "/t", "/f"],
|
|
2446
|
+
{
|
|
2447
|
+
stdio: "ignore"
|
|
2448
|
+
}
|
|
2449
|
+
);
|
|
2450
|
+
killer.on("error", () => resolve());
|
|
2451
|
+
killer.on("exit", () => resolve());
|
|
2452
|
+
});
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
if (processPolicy.killStrategy === "process-group") {
|
|
2456
|
+
try {
|
|
2457
|
+
process.kill(-pid, "SIGKILL");
|
|
2458
|
+
return;
|
|
2459
|
+
} catch {
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
try {
|
|
2463
|
+
process.kill(pid, "SIGKILL");
|
|
2464
|
+
} catch {
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
async function waitForProcessToExit(owner, timeoutMs) {
|
|
2468
|
+
const deadline = Date.now() + timeoutMs;
|
|
2469
|
+
while (Date.now() < deadline) {
|
|
2470
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
await sleep5(50);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
async function waitForPidToExit(pid, timeoutMs) {
|
|
2477
|
+
const deadline = Date.now() + timeoutMs;
|
|
2478
|
+
while (Date.now() < deadline) {
|
|
2479
|
+
if (!isProcessRunning(pid)) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
await sleep5(50);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
async function waitForSpawnedProcessOwner(pid, timeoutMs) {
|
|
2486
|
+
if (!pid || pid <= 0) {
|
|
2487
|
+
throw new Error("Chrome did not expose a child process id.");
|
|
2488
|
+
}
|
|
2489
|
+
const deadline = Date.now() + timeoutMs;
|
|
2490
|
+
while (Date.now() < deadline) {
|
|
2491
|
+
const owner = await readProcessOwner(pid);
|
|
2492
|
+
if (owner) {
|
|
2493
|
+
return owner;
|
|
2494
|
+
}
|
|
2495
|
+
await sleep5(50);
|
|
2496
|
+
}
|
|
2497
|
+
throw new Error(
|
|
2498
|
+
`Chrome process ${pid} did not report a stable process start time.`
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
async function withSharedSessionLock(persistentUserDataDir, action) {
|
|
2502
|
+
return await withDirLock(
|
|
2503
|
+
buildSharedSessionLockPath(persistentUserDataDir),
|
|
2504
|
+
action
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
async function registerSharedSessionClient(persistentUserDataDir, metadata) {
|
|
2508
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
2509
|
+
const client = buildSharedSessionClientRegistration();
|
|
2510
|
+
await (0, import_promises5.mkdir)(buildSharedSessionClientsDirPath(persistentUserDataDir), {
|
|
2511
|
+
recursive: true
|
|
2512
|
+
});
|
|
2513
|
+
await (0, import_promises5.writeFile)(
|
|
2514
|
+
buildSharedSessionClientPath(persistentUserDataDir, client.clientId),
|
|
2515
|
+
JSON.stringify(client, null, 2),
|
|
2516
|
+
{
|
|
2517
|
+
flag: "wx"
|
|
2518
|
+
}
|
|
2519
|
+
);
|
|
2520
|
+
return {
|
|
2521
|
+
client,
|
|
2522
|
+
metadata,
|
|
2523
|
+
reuseExistingPage: liveClients.length === 0
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
async function removeSharedSessionClientRegistration(persistentUserDataDir, clientId) {
|
|
2527
|
+
await (0, import_promises5.rm)(buildSharedSessionClientPath(persistentUserDataDir, clientId), {
|
|
2528
|
+
force: true
|
|
2529
|
+
}).catch(() => void 0);
|
|
2530
|
+
}
|
|
2531
|
+
async function listLiveSharedSessionClients(persistentUserDataDir) {
|
|
2532
|
+
const clientsDirPath = buildSharedSessionClientsDirPath(persistentUserDataDir);
|
|
2533
|
+
let entries;
|
|
2534
|
+
try {
|
|
2535
|
+
entries = await (0, import_promises5.readdir)(clientsDirPath, {
|
|
2536
|
+
encoding: "utf8",
|
|
2537
|
+
withFileTypes: true
|
|
2538
|
+
});
|
|
2539
|
+
} catch {
|
|
2540
|
+
return [];
|
|
2541
|
+
}
|
|
2542
|
+
const liveClients = [];
|
|
2543
|
+
for (const entry of entries) {
|
|
2544
|
+
if (!entry.isFile()) {
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
const filePath = (0, import_node_path5.join)(clientsDirPath, entry.name);
|
|
2548
|
+
const registration = await readSharedSessionClientRegistration(filePath);
|
|
2549
|
+
if (!registration) {
|
|
2550
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
if (await getProcessLiveness(registration.owner) === "dead") {
|
|
2554
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
liveClients.push(registration);
|
|
2558
|
+
}
|
|
2559
|
+
return liveClients;
|
|
2560
|
+
}
|
|
2561
|
+
async function readSharedSessionClientRegistration(filePath) {
|
|
2562
|
+
try {
|
|
2563
|
+
const raw = await (0, import_promises5.readFile)(filePath, "utf8");
|
|
2564
|
+
return parseSharedSessionClientRegistration(JSON.parse(raw));
|
|
2565
|
+
} catch {
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
function buildSharedSessionClientRegistration() {
|
|
2570
|
+
return {
|
|
2571
|
+
clientId: (0, import_node_crypto4.randomUUID)(),
|
|
2572
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2573
|
+
owner: CURRENT_PROCESS_OWNER
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function parseSharedSessionClientRegistration(value) {
|
|
2577
|
+
if (!value || typeof value !== "object") {
|
|
2578
|
+
return null;
|
|
2579
|
+
}
|
|
2580
|
+
const parsed = value;
|
|
2581
|
+
const owner = parseProcessOwner(parsed.owner);
|
|
2582
|
+
if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
|
|
2583
|
+
return null;
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
clientId: parsed.clientId,
|
|
2587
|
+
createdAt: parsed.createdAt,
|
|
2588
|
+
owner
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function assertSharedSessionCompatibility(metadata, options) {
|
|
2592
|
+
if (metadata.executablePath !== options.executablePath) {
|
|
2593
|
+
throw new Error(
|
|
2594
|
+
`Chrome profile "${options.profileDirectory}" is already running with executable "${metadata.executablePath}", not "${options.executablePath}".`
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
if (metadata.headless !== options.headless) {
|
|
2598
|
+
throw new Error(
|
|
2599
|
+
`Chrome profile "${options.profileDirectory}" is already running with headless=${metadata.headless}, not ${options.headless}.`
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
async function prepareSharedSessionCloseIfIdle(persistentUserDataDir, clientId, sessionId) {
|
|
2604
|
+
return await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
2605
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
2606
|
+
await removeSharedSessionClientRegistration(
|
|
2607
|
+
persistentUserDataDir,
|
|
2608
|
+
clientId
|
|
2609
|
+
);
|
|
2610
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
2611
|
+
return {
|
|
2612
|
+
closeBrowser: false,
|
|
2613
|
+
sessionId
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
2617
|
+
persistentUserDataDir
|
|
2618
|
+
);
|
|
2619
|
+
if (liveClients.length > 0) {
|
|
2620
|
+
return {
|
|
2621
|
+
closeBrowser: false,
|
|
2622
|
+
sessionId: metadata.sessionId
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
const closingMetadata = {
|
|
2626
|
+
...metadata,
|
|
2627
|
+
state: "closing",
|
|
2628
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
2629
|
+
};
|
|
2630
|
+
await writeSharedSessionMetadata(
|
|
2631
|
+
persistentUserDataDir,
|
|
2632
|
+
closingMetadata
|
|
2633
|
+
);
|
|
2634
|
+
return {
|
|
2635
|
+
browserOwner: closingMetadata.browserOwner,
|
|
2636
|
+
closeBrowser: true,
|
|
2637
|
+
sessionId: closingMetadata.sessionId
|
|
2638
|
+
};
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
async function closeSharedSessionBrowser(persistentUserDataDir, closePlan, browser) {
|
|
2642
|
+
if (browser) {
|
|
2643
|
+
await requestBrowserShutdown(browser);
|
|
2644
|
+
await waitForProcessToExit(closePlan.browserOwner, 1e3);
|
|
2645
|
+
}
|
|
2646
|
+
if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
|
|
2647
|
+
await killOwnedBrowserProcess(closePlan.browserOwner);
|
|
2648
|
+
await waitForProcessToExit(closePlan.browserOwner, 2e3);
|
|
2649
|
+
}
|
|
2650
|
+
await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
|
|
2651
|
+
}
|
|
2652
|
+
async function finalizeSharedSessionClose(persistentUserDataDir, sessionId) {
|
|
2653
|
+
await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
2654
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
2655
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
2659
|
+
persistentUserDataDir
|
|
2660
|
+
);
|
|
2661
|
+
if (liveClients.length > 0) {
|
|
2662
|
+
const readyMetadata = {
|
|
2663
|
+
...metadata,
|
|
2664
|
+
state: "ready"
|
|
2665
|
+
};
|
|
2666
|
+
await writeSharedSessionMetadata(
|
|
2667
|
+
persistentUserDataDir,
|
|
2668
|
+
readyMetadata
|
|
2669
|
+
);
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
2673
|
+
const readyMetadata = {
|
|
2674
|
+
...metadata,
|
|
2675
|
+
state: "ready"
|
|
2676
|
+
};
|
|
2677
|
+
await writeSharedSessionMetadata(
|
|
2678
|
+
persistentUserDataDir,
|
|
2679
|
+
readyMetadata
|
|
2680
|
+
);
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
2684
|
+
force: true,
|
|
2685
|
+
recursive: true
|
|
2686
|
+
}).catch(() => void 0);
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
function getPrimaryBrowserContext(browser) {
|
|
2690
|
+
const contexts = browser.contexts();
|
|
2691
|
+
if (contexts.length === 0) {
|
|
2692
|
+
throw new Error(
|
|
2693
|
+
"Connection succeeded but no browser contexts were exposed."
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
return contexts[0];
|
|
2697
|
+
}
|
|
2698
|
+
async function getSharedSessionPage(context, reuseExistingPage) {
|
|
2699
|
+
if (reuseExistingPage) {
|
|
2700
|
+
return await getExistingPageOrCreate(context);
|
|
2701
|
+
}
|
|
2702
|
+
return await context.newPage();
|
|
2703
|
+
}
|
|
2704
|
+
async function getExistingPageOrCreate(context) {
|
|
2705
|
+
const existingPage = context.pages()[0];
|
|
2706
|
+
if (existingPage) {
|
|
2707
|
+
return existingPage;
|
|
2708
|
+
}
|
|
2709
|
+
return await context.newPage();
|
|
2710
|
+
}
|
|
2711
|
+
function buildSharedSessionDiscoveryUrl(debugPort) {
|
|
2712
|
+
return `http://127.0.0.1:${debugPort}`;
|
|
2713
|
+
}
|
|
2714
|
+
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
2715
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
2716
|
+
return cdpUrl;
|
|
2717
|
+
}
|
|
2718
|
+
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
2719
|
+
const deadline = Date.now() + timeoutMs;
|
|
2720
|
+
let lastError = "CDP discovery did not respond.";
|
|
2721
|
+
while (Date.now() < deadline) {
|
|
2722
|
+
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
2723
|
+
try {
|
|
2724
|
+
const response = await fetch(versionUrl, {
|
|
2725
|
+
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
2726
|
+
});
|
|
2727
|
+
if (!response.ok) {
|
|
2728
|
+
lastError = `${response.status} ${response.statusText}`;
|
|
2729
|
+
} else {
|
|
2730
|
+
const payload = await response.json();
|
|
2731
|
+
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
2732
|
+
if (wsUrl && wsUrl.trim()) {
|
|
2733
|
+
return wsUrl;
|
|
2734
|
+
}
|
|
2735
|
+
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
1137
2736
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
})
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
|
|
1146
|
-
const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
|
|
1147
|
-
if (!owner) {
|
|
1148
|
-
return false;
|
|
1149
|
-
}
|
|
1150
|
-
if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
|
|
1151
|
-
return true;
|
|
2737
|
+
} catch (error) {
|
|
2738
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2739
|
+
}
|
|
2740
|
+
await sleep5(100);
|
|
1152
2741
|
}
|
|
1153
|
-
|
|
2742
|
+
throw new Error(
|
|
2743
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2744
|
+
);
|
|
1154
2745
|
}
|
|
1155
|
-
function
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
2746
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
2747
|
+
let parsed;
|
|
2748
|
+
try {
|
|
2749
|
+
parsed = new URL(cdpUrl);
|
|
2750
|
+
} catch {
|
|
2751
|
+
throw new Error(
|
|
2752
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
2753
|
+
);
|
|
1161
2754
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
1165
|
-
10
|
|
1166
|
-
);
|
|
1167
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1168
|
-
return null;
|
|
2755
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
2756
|
+
return parsed;
|
|
1169
2757
|
}
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
2758
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2759
|
+
throw new Error(
|
|
2760
|
+
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
2761
|
+
);
|
|
1172
2762
|
}
|
|
1173
|
-
|
|
2763
|
+
const normalized = new URL(parsed.toString());
|
|
2764
|
+
normalized.pathname = "/json/version";
|
|
2765
|
+
normalized.search = "";
|
|
2766
|
+
normalized.hash = "";
|
|
2767
|
+
return normalized;
|
|
1174
2768
|
}
|
|
1175
|
-
function
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
2769
|
+
async function reserveDebugPort() {
|
|
2770
|
+
return await new Promise((resolve, reject) => {
|
|
2771
|
+
const server2 = (0, import_node_net.createServer)();
|
|
2772
|
+
server2.unref();
|
|
2773
|
+
server2.on("error", reject);
|
|
2774
|
+
server2.listen(0, "127.0.0.1", () => {
|
|
2775
|
+
const address = server2.address();
|
|
2776
|
+
if (!address || typeof address === "string") {
|
|
2777
|
+
server2.close();
|
|
2778
|
+
reject(new Error("Failed to reserve a local debug port."));
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
server2.close((error) => {
|
|
2782
|
+
if (error) {
|
|
2783
|
+
reject(error);
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
resolve(address.port);
|
|
2787
|
+
});
|
|
2788
|
+
});
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
async function sleep5(ms) {
|
|
2792
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1183
2793
|
}
|
|
1184
2794
|
|
|
1185
2795
|
// src/browser/pool.ts
|
|
1186
2796
|
var BrowserPool = class {
|
|
1187
2797
|
browser = null;
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
managedUserDataDir = null;
|
|
1191
|
-
persistentProfile = false;
|
|
2798
|
+
activeSessionClose = null;
|
|
2799
|
+
closeInFlight = null;
|
|
1192
2800
|
defaults;
|
|
1193
2801
|
constructor(defaults = {}) {
|
|
1194
2802
|
this.defaults = defaults;
|
|
1195
2803
|
}
|
|
1196
2804
|
async launch(options = {}) {
|
|
1197
|
-
if (this.browser || this.
|
|
2805
|
+
if (this.browser || this.activeSessionClose) {
|
|
1198
2806
|
await this.close();
|
|
1199
2807
|
}
|
|
1200
2808
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -1241,30 +2849,26 @@ var BrowserPool = class {
|
|
|
1241
2849
|
return this.launchSandbox(options);
|
|
1242
2850
|
}
|
|
1243
2851
|
async close() {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
this.
|
|
1250
|
-
this.cdpProxy = null;
|
|
1251
|
-
this.launchedProcess = null;
|
|
1252
|
-
this.managedUserDataDir = null;
|
|
1253
|
-
this.persistentProfile = false;
|
|
2852
|
+
if (this.closeInFlight) {
|
|
2853
|
+
await this.closeInFlight;
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
const closeOperation = this.closeCurrent();
|
|
2857
|
+
this.closeInFlight = closeOperation;
|
|
1254
2858
|
try {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
2859
|
+
await closeOperation;
|
|
2860
|
+
this.browser = null;
|
|
2861
|
+
this.activeSessionClose = null;
|
|
1258
2862
|
} finally {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
}
|
|
2863
|
+
this.closeInFlight = null;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
async closeCurrent() {
|
|
2867
|
+
if (this.activeSessionClose) {
|
|
2868
|
+
await this.activeSessionClose();
|
|
2869
|
+
return;
|
|
1267
2870
|
}
|
|
2871
|
+
await this.browser?.close().catch(() => void 0);
|
|
1268
2872
|
}
|
|
1269
2873
|
async connectToRunning(cdpUrl, timeout) {
|
|
1270
2874
|
let browser = null;
|
|
@@ -1279,11 +2883,14 @@ var BrowserPool = class {
|
|
|
1279
2883
|
}
|
|
1280
2884
|
cdpProxy = new CDPProxy(browserWsUrl, targetId);
|
|
1281
2885
|
const proxyWsUrl = await cdpProxy.start();
|
|
1282
|
-
browser = await
|
|
2886
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
1283
2887
|
timeout: timeout ?? 3e4
|
|
1284
2888
|
});
|
|
1285
2889
|
this.browser = browser;
|
|
1286
|
-
this.
|
|
2890
|
+
this.activeSessionClose = async () => {
|
|
2891
|
+
await browser?.close().catch(() => void 0);
|
|
2892
|
+
cdpProxy?.close();
|
|
2893
|
+
};
|
|
1287
2894
|
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
1288
2895
|
return { browser, context, page, isExternal: true };
|
|
1289
2896
|
} catch (error) {
|
|
@@ -1292,7 +2899,7 @@ var BrowserPool = class {
|
|
|
1292
2899
|
}
|
|
1293
2900
|
cdpProxy?.close();
|
|
1294
2901
|
this.browser = null;
|
|
1295
|
-
this.
|
|
2902
|
+
this.activeSessionClose = null;
|
|
1296
2903
|
throw error;
|
|
1297
2904
|
}
|
|
1298
2905
|
}
|
|
@@ -1312,55 +2919,29 @@ var BrowserPool = class {
|
|
|
1312
2919
|
sourceUserDataDir,
|
|
1313
2920
|
profileDirectory
|
|
1314
2921
|
);
|
|
1315
|
-
await
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
2922
|
+
const sharedSession = await acquireSharedRealBrowserSession({
|
|
2923
|
+
executablePath,
|
|
2924
|
+
headless: resolveLaunchHeadless(
|
|
2925
|
+
"real",
|
|
2926
|
+
options.headless,
|
|
2927
|
+
this.defaults.headless
|
|
2928
|
+
),
|
|
2929
|
+
initialUrl: options.initialUrl,
|
|
2930
|
+
persistentProfile,
|
|
1324
2931
|
profileDirectory,
|
|
1325
|
-
|
|
1326
|
-
headless
|
|
2932
|
+
timeoutMs: options.timeout ?? 3e4
|
|
1327
2933
|
});
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
`http://127.0.0.1:${debugPort}`,
|
|
1337
|
-
options.timeout ?? 3e4
|
|
1338
|
-
);
|
|
1339
|
-
browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
|
|
1340
|
-
timeout: options.timeout ?? 3e4
|
|
1341
|
-
});
|
|
1342
|
-
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
1343
|
-
browser
|
|
1344
|
-
);
|
|
1345
|
-
if (options.initialUrl) {
|
|
1346
|
-
await page.goto(options.initialUrl, {
|
|
1347
|
-
waitUntil: "domcontentloaded",
|
|
1348
|
-
timeout: options.timeout ?? 3e4
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
this.browser = browser;
|
|
1352
|
-
this.launchedProcess = processHandle;
|
|
1353
|
-
this.managedUserDataDir = persistentProfile.userDataDir;
|
|
1354
|
-
this.persistentProfile = true;
|
|
1355
|
-
return { browser, context, page, isExternal: false };
|
|
1356
|
-
} catch (error) {
|
|
1357
|
-
await browser?.close().catch(() => void 0);
|
|
1358
|
-
await killProcessTree(processHandle);
|
|
1359
|
-
throw error;
|
|
1360
|
-
}
|
|
2934
|
+
this.browser = sharedSession.browser;
|
|
2935
|
+
this.activeSessionClose = sharedSession.close;
|
|
2936
|
+
return {
|
|
2937
|
+
browser: sharedSession.browser,
|
|
2938
|
+
context: sharedSession.context,
|
|
2939
|
+
page: sharedSession.page,
|
|
2940
|
+
isExternal: false
|
|
2941
|
+
};
|
|
1361
2942
|
}
|
|
1362
2943
|
async launchSandbox(options) {
|
|
1363
|
-
const browser = await
|
|
2944
|
+
const browser = await import_playwright2.chromium.launch({
|
|
1364
2945
|
headless: resolveLaunchHeadless(
|
|
1365
2946
|
"chromium",
|
|
1366
2947
|
options.headless,
|
|
@@ -1373,11 +2954,14 @@ var BrowserPool = class {
|
|
|
1373
2954
|
const context = await browser.newContext(options.context || {});
|
|
1374
2955
|
const page = await context.newPage();
|
|
1375
2956
|
this.browser = browser;
|
|
2957
|
+
this.activeSessionClose = async () => {
|
|
2958
|
+
await browser.close().catch(() => void 0);
|
|
2959
|
+
};
|
|
1376
2960
|
return { browser, context, page, isExternal: false };
|
|
1377
2961
|
}
|
|
1378
2962
|
};
|
|
1379
2963
|
async function pickBrowserContextAndPage(browser) {
|
|
1380
|
-
const context =
|
|
2964
|
+
const context = getPrimaryBrowserContext2(browser);
|
|
1381
2965
|
const page = await getAttachedPageOrCreate(context);
|
|
1382
2966
|
return { context, page };
|
|
1383
2967
|
}
|
|
@@ -1390,11 +2974,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
|
1390
2974
|
}
|
|
1391
2975
|
return mode === "real";
|
|
1392
2976
|
}
|
|
1393
|
-
async function createOwnedBrowserContextAndPage(browser) {
|
|
1394
|
-
const context = getPrimaryBrowserContext(browser);
|
|
1395
|
-
const page = await getExistingPageOrCreate(context);
|
|
1396
|
-
return { context, page };
|
|
1397
|
-
}
|
|
1398
2977
|
async function getAttachedPageOrCreate(context) {
|
|
1399
2978
|
const pages = context.pages();
|
|
1400
2979
|
const inspectablePage = pages.find(
|
|
@@ -1409,14 +2988,7 @@ async function getAttachedPageOrCreate(context) {
|
|
|
1409
2988
|
}
|
|
1410
2989
|
return await context.newPage();
|
|
1411
2990
|
}
|
|
1412
|
-
|
|
1413
|
-
const existingPage = context.pages()[0];
|
|
1414
|
-
if (existingPage) {
|
|
1415
|
-
return existingPage;
|
|
1416
|
-
}
|
|
1417
|
-
return await context.newPage();
|
|
1418
|
-
}
|
|
1419
|
-
function getPrimaryBrowserContext(browser) {
|
|
2991
|
+
function getPrimaryBrowserContext2(browser) {
|
|
1420
2992
|
const contexts = browser.contexts();
|
|
1421
2993
|
if (contexts.length === 0) {
|
|
1422
2994
|
throw new Error(
|
|
@@ -1435,125 +3007,6 @@ function safePageUrl(page) {
|
|
|
1435
3007
|
return "";
|
|
1436
3008
|
}
|
|
1437
3009
|
}
|
|
1438
|
-
function normalizeDiscoveryUrl(cdpUrl) {
|
|
1439
|
-
let parsed;
|
|
1440
|
-
try {
|
|
1441
|
-
parsed = new URL(cdpUrl);
|
|
1442
|
-
} catch {
|
|
1443
|
-
throw new Error(
|
|
1444
|
-
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
1445
|
-
);
|
|
1446
|
-
}
|
|
1447
|
-
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
1448
|
-
return parsed;
|
|
1449
|
-
}
|
|
1450
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1451
|
-
throw new Error(
|
|
1452
|
-
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
1453
|
-
);
|
|
1454
|
-
}
|
|
1455
|
-
const normalized = new URL(parsed.toString());
|
|
1456
|
-
normalized.pathname = "/json/version";
|
|
1457
|
-
normalized.search = "";
|
|
1458
|
-
normalized.hash = "";
|
|
1459
|
-
return normalized;
|
|
1460
|
-
}
|
|
1461
|
-
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
1462
|
-
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
1463
|
-
return cdpUrl;
|
|
1464
|
-
}
|
|
1465
|
-
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
1466
|
-
const deadline = Date.now() + timeoutMs;
|
|
1467
|
-
let lastError = "CDP discovery did not respond.";
|
|
1468
|
-
while (Date.now() < deadline) {
|
|
1469
|
-
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
1470
|
-
try {
|
|
1471
|
-
const response = await fetch(versionUrl, {
|
|
1472
|
-
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
1473
|
-
});
|
|
1474
|
-
if (!response.ok) {
|
|
1475
|
-
lastError = `${response.status} ${response.statusText}`;
|
|
1476
|
-
} else {
|
|
1477
|
-
const payload = await response.json();
|
|
1478
|
-
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
1479
|
-
if (wsUrl && wsUrl.trim()) {
|
|
1480
|
-
return wsUrl;
|
|
1481
|
-
}
|
|
1482
|
-
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
1483
|
-
}
|
|
1484
|
-
} catch (error) {
|
|
1485
|
-
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
1486
|
-
}
|
|
1487
|
-
await sleep(100);
|
|
1488
|
-
}
|
|
1489
|
-
throw new Error(
|
|
1490
|
-
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
1491
|
-
);
|
|
1492
|
-
}
|
|
1493
|
-
async function reserveDebugPort() {
|
|
1494
|
-
return await new Promise((resolve, reject) => {
|
|
1495
|
-
const server2 = (0, import_node_net.createServer)();
|
|
1496
|
-
server2.unref();
|
|
1497
|
-
server2.on("error", reject);
|
|
1498
|
-
server2.listen(0, "127.0.0.1", () => {
|
|
1499
|
-
const address = server2.address();
|
|
1500
|
-
if (!address || typeof address === "string") {
|
|
1501
|
-
server2.close();
|
|
1502
|
-
reject(new Error("Failed to reserve a local debug port."));
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
server2.close((error) => {
|
|
1506
|
-
if (error) {
|
|
1507
|
-
reject(error);
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
resolve(address.port);
|
|
1511
|
-
});
|
|
1512
|
-
});
|
|
1513
|
-
});
|
|
1514
|
-
}
|
|
1515
|
-
function buildRealBrowserLaunchArgs(options) {
|
|
1516
|
-
const args = [
|
|
1517
|
-
`--user-data-dir=${options.userDataDir}`,
|
|
1518
|
-
`--profile-directory=${options.profileDirectory}`,
|
|
1519
|
-
`--remote-debugging-port=${options.debugPort}`,
|
|
1520
|
-
"--disable-blink-features=AutomationControlled"
|
|
1521
|
-
];
|
|
1522
|
-
if (options.headless) {
|
|
1523
|
-
args.push("--headless=new");
|
|
1524
|
-
}
|
|
1525
|
-
return args;
|
|
1526
|
-
}
|
|
1527
|
-
async function killProcessTree(processHandle) {
|
|
1528
|
-
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
1529
|
-
return;
|
|
1530
|
-
}
|
|
1531
|
-
if (process.platform === "win32") {
|
|
1532
|
-
await new Promise((resolve) => {
|
|
1533
|
-
const killer = (0, import_node_child_process.spawn)(
|
|
1534
|
-
"taskkill",
|
|
1535
|
-
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
1536
|
-
{
|
|
1537
|
-
stdio: "ignore"
|
|
1538
|
-
}
|
|
1539
|
-
);
|
|
1540
|
-
killer.on("error", () => resolve());
|
|
1541
|
-
killer.on("exit", () => resolve());
|
|
1542
|
-
});
|
|
1543
|
-
return;
|
|
1544
|
-
}
|
|
1545
|
-
try {
|
|
1546
|
-
process.kill(-processHandle.pid, "SIGKILL");
|
|
1547
|
-
} catch {
|
|
1548
|
-
try {
|
|
1549
|
-
processHandle.kill("SIGKILL");
|
|
1550
|
-
} catch {
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
async function sleep(ms) {
|
|
1555
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1556
|
-
}
|
|
1557
3010
|
|
|
1558
3011
|
// src/config.ts
|
|
1559
3012
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
@@ -1860,9 +3313,9 @@ function resolveNamespaceDir(rootDir, namespace) {
|
|
|
1860
3313
|
const selectorsRoot = import_path2.default.resolve(rootDir, ".opensteer", "selectors");
|
|
1861
3314
|
const normalizedNamespace = normalizeNamespace(namespace);
|
|
1862
3315
|
const namespaceDir = import_path2.default.resolve(selectorsRoot, normalizedNamespace);
|
|
1863
|
-
const
|
|
1864
|
-
if (
|
|
1865
|
-
if (
|
|
3316
|
+
const relative2 = import_path2.default.relative(selectorsRoot, namespaceDir);
|
|
3317
|
+
if (relative2 === "" || relative2 === ".") return namespaceDir;
|
|
3318
|
+
if (relative2.startsWith("..") || import_path2.default.isAbsolute(relative2)) {
|
|
1866
3319
|
throw new Error(
|
|
1867
3320
|
`Namespace "${namespace}" resolves outside selectors root.`
|
|
1868
3321
|
);
|
|
@@ -2419,8 +3872,8 @@ function resolveNamespace(config, rootDir) {
|
|
|
2419
3872
|
}
|
|
2420
3873
|
const caller = getCallerFilePath();
|
|
2421
3874
|
if (!caller) return normalizeNamespace("default");
|
|
2422
|
-
const
|
|
2423
|
-
const cleaned =
|
|
3875
|
+
const relative2 = import_path3.default.relative(rootDir, caller);
|
|
3876
|
+
const cleaned = relative2.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
|
|
2424
3877
|
return normalizeNamespace(cleaned || "default");
|
|
2425
3878
|
}
|
|
2426
3879
|
function getCallerFilePath() {
|
|
@@ -2825,7 +4278,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2825
4278
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
2826
4279
|
Math.max(0, deadline - Date.now())
|
|
2827
4280
|
);
|
|
2828
|
-
await
|
|
4281
|
+
await sleep6(retryDelay);
|
|
2829
4282
|
}
|
|
2830
4283
|
}
|
|
2831
4284
|
}
|
|
@@ -2858,7 +4311,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2858
4311
|
() => ({ kind: "resolved" }),
|
|
2859
4312
|
(error) => ({ kind: "rejected", error })
|
|
2860
4313
|
);
|
|
2861
|
-
const timeoutPromise =
|
|
4314
|
+
const timeoutPromise = sleep6(
|
|
2862
4315
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
2863
4316
|
).then(() => ({ kind: "timeout" }));
|
|
2864
4317
|
const result = await Promise.race([
|
|
@@ -3000,7 +4453,7 @@ function isIgnorableFrameError(error) {
|
|
|
3000
4453
|
const message = error.message;
|
|
3001
4454
|
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
3002
4455
|
}
|
|
3003
|
-
function
|
|
4456
|
+
function sleep6(ms) {
|
|
3004
4457
|
return new Promise((resolve) => {
|
|
3005
4458
|
setTimeout(resolve, ms);
|
|
3006
4459
|
});
|
|
@@ -7251,7 +8704,7 @@ async function closeTab(context, activePage, index) {
|
|
|
7251
8704
|
}
|
|
7252
8705
|
|
|
7253
8706
|
// src/actions/cookies.ts
|
|
7254
|
-
var
|
|
8707
|
+
var import_promises6 = require("fs/promises");
|
|
7255
8708
|
async function getCookies(context, url) {
|
|
7256
8709
|
return context.cookies(url ? [url] : void 0);
|
|
7257
8710
|
}
|
|
@@ -7263,10 +8716,10 @@ async function clearCookies(context) {
|
|
|
7263
8716
|
}
|
|
7264
8717
|
async function exportCookies(context, filePath, url) {
|
|
7265
8718
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
7266
|
-
await (0,
|
|
8719
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
7267
8720
|
}
|
|
7268
8721
|
async function importCookies(context, filePath) {
|
|
7269
|
-
const raw = await (0,
|
|
8722
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
7270
8723
|
const cookies = JSON.parse(raw);
|
|
7271
8724
|
await context.addCookies(cookies);
|
|
7272
8725
|
}
|
|
@@ -7577,7 +9030,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7577
9030
|
this.idleSince = 0;
|
|
7578
9031
|
}
|
|
7579
9032
|
const remaining = Math.max(1, options.deadline - now);
|
|
7580
|
-
await
|
|
9033
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
7581
9034
|
}
|
|
7582
9035
|
}
|
|
7583
9036
|
handleRequestStarted = (request) => {
|
|
@@ -7622,7 +9075,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7622
9075
|
return false;
|
|
7623
9076
|
}
|
|
7624
9077
|
};
|
|
7625
|
-
async function
|
|
9078
|
+
async function sleep7(ms) {
|
|
7626
9079
|
await new Promise((resolve) => {
|
|
7627
9080
|
setTimeout(resolve, ms);
|
|
7628
9081
|
});
|
|
@@ -9324,13 +10777,13 @@ function dedupeNewest(entries) {
|
|
|
9324
10777
|
}
|
|
9325
10778
|
|
|
9326
10779
|
// src/cloud/cdp-client.ts
|
|
9327
|
-
var
|
|
10780
|
+
var import_playwright3 = require("playwright");
|
|
9328
10781
|
var CloudCdpClient = class {
|
|
9329
10782
|
async connect(args) {
|
|
9330
10783
|
const endpoint = withTokenQuery(args.wsUrl, args.token);
|
|
9331
10784
|
let browser;
|
|
9332
10785
|
try {
|
|
9333
|
-
browser = await
|
|
10786
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
9334
10787
|
} catch (error) {
|
|
9335
10788
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
9336
10789
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -11147,7 +12600,7 @@ async function executeAgentAction(page, action) {
|
|
|
11147
12600
|
}
|
|
11148
12601
|
case "wait": {
|
|
11149
12602
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
11150
|
-
await
|
|
12603
|
+
await sleep8(ms);
|
|
11151
12604
|
return;
|
|
11152
12605
|
}
|
|
11153
12606
|
case "goto": {
|
|
@@ -11312,7 +12765,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
11312
12765
|
}
|
|
11313
12766
|
}
|
|
11314
12767
|
}
|
|
11315
|
-
function
|
|
12768
|
+
function sleep8(ms) {
|
|
11316
12769
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11317
12770
|
}
|
|
11318
12771
|
|
|
@@ -11343,7 +12796,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11343
12796
|
if (isMutatingAgentAction(action)) {
|
|
11344
12797
|
this.onMutatingAction?.(action);
|
|
11345
12798
|
}
|
|
11346
|
-
await
|
|
12799
|
+
await sleep9(this.config.waitBetweenActionsMs);
|
|
11347
12800
|
});
|
|
11348
12801
|
try {
|
|
11349
12802
|
const result = await this.client.execute({
|
|
@@ -11405,7 +12858,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11405
12858
|
await this.cursorController.preview({ x, y }, "agent");
|
|
11406
12859
|
}
|
|
11407
12860
|
};
|
|
11408
|
-
function
|
|
12861
|
+
function sleep9(ms) {
|
|
11409
12862
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11410
12863
|
}
|
|
11411
12864
|
|
|
@@ -11841,7 +13294,7 @@ var CursorController = class {
|
|
|
11841
13294
|
for (const step of motion.points) {
|
|
11842
13295
|
await this.renderer.move(step, this.style);
|
|
11843
13296
|
if (motion.stepDelayMs > 0) {
|
|
11844
|
-
await
|
|
13297
|
+
await sleep10(motion.stepDelayMs);
|
|
11845
13298
|
}
|
|
11846
13299
|
}
|
|
11847
13300
|
if (shouldPulse(intent)) {
|
|
@@ -11999,7 +13452,7 @@ function clamp2(value, min, max) {
|
|
|
11999
13452
|
function shouldPulse(intent) {
|
|
12000
13453
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
12001
13454
|
}
|
|
12002
|
-
function
|
|
13455
|
+
function sleep10(ms) {
|
|
12003
13456
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12004
13457
|
}
|
|
12005
13458
|
|
|
@@ -12473,15 +13926,22 @@ var Opensteer = class _Opensteer {
|
|
|
12473
13926
|
}
|
|
12474
13927
|
return;
|
|
12475
13928
|
}
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
13929
|
+
let closedOwnedBrowser = false;
|
|
13930
|
+
try {
|
|
13931
|
+
if (this.ownsBrowser) {
|
|
13932
|
+
await this.pool.close();
|
|
13933
|
+
closedOwnedBrowser = true;
|
|
13934
|
+
}
|
|
13935
|
+
} finally {
|
|
13936
|
+
this.browser = null;
|
|
13937
|
+
this.pageRef = null;
|
|
13938
|
+
this.contextRef = null;
|
|
13939
|
+
if (!this.ownsBrowser || closedOwnedBrowser) {
|
|
13940
|
+
this.ownsBrowser = false;
|
|
13941
|
+
}
|
|
13942
|
+
if (this.cursorController) {
|
|
13943
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
13944
|
+
}
|
|
12485
13945
|
}
|
|
12486
13946
|
}
|
|
12487
13947
|
async syncLocalSelectorCacheToCloud() {
|
|
@@ -14825,7 +16285,7 @@ function getMetadataPath(session2) {
|
|
|
14825
16285
|
}
|
|
14826
16286
|
|
|
14827
16287
|
// src/cli/commands.ts
|
|
14828
|
-
var
|
|
16288
|
+
var import_promises7 = require("fs/promises");
|
|
14829
16289
|
var commands = {
|
|
14830
16290
|
async navigate(opensteer, args) {
|
|
14831
16291
|
const url = args.url;
|
|
@@ -14879,7 +16339,7 @@ var commands = {
|
|
|
14879
16339
|
fullPage: args.fullPage,
|
|
14880
16340
|
type
|
|
14881
16341
|
});
|
|
14882
|
-
await (0,
|
|
16342
|
+
await (0, import_promises7.writeFile)(file, buffer);
|
|
14883
16343
|
return { file };
|
|
14884
16344
|
},
|
|
14885
16345
|
async click(opensteer, args) {
|