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/index.cjs
CHANGED
|
@@ -475,6 +475,7 @@ __export(index_exports, {
|
|
|
475
475
|
createCuaClient: () => createCuaClient,
|
|
476
476
|
createEmptyRegistry: () => createEmptyRegistry,
|
|
477
477
|
createExtractCallback: () => createExtractCallback,
|
|
478
|
+
createIsolatedRuntimeProfile: () => createIsolatedRuntimeProfile,
|
|
478
479
|
createResolveCallback: () => createResolveCallback,
|
|
479
480
|
createTab: () => createTab,
|
|
480
481
|
detectChromePaths: () => detectChromePaths,
|
|
@@ -490,8 +491,10 @@ __export(index_exports, {
|
|
|
490
491
|
getElementValue: () => getElementValue,
|
|
491
492
|
getModelProvider: () => getModelProvider,
|
|
492
493
|
getOrCreatePersistentProfile: () => getOrCreatePersistentProfile,
|
|
494
|
+
getOwnedRealBrowserProcessPolicy: () => getOwnedRealBrowserProcessPolicy,
|
|
493
495
|
getPageHtml: () => getPageHtml,
|
|
494
496
|
getPageTitle: () => getPageTitle,
|
|
497
|
+
hasActiveRuntimeProfileCreations: () => hasActiveRuntimeProfileCreations,
|
|
495
498
|
importCookies: () => importCookies,
|
|
496
499
|
isCloudActionMethod: () => isCloudActionMethod,
|
|
497
500
|
isCloudErrorCode: () => isCloudErrorCode,
|
|
@@ -507,6 +510,7 @@ __export(index_exports, {
|
|
|
507
510
|
performInput: () => performInput,
|
|
508
511
|
performScroll: () => performScroll,
|
|
509
512
|
performSelect: () => performSelect,
|
|
513
|
+
persistIsolatedRuntimeProfile: () => persistIsolatedRuntimeProfile,
|
|
510
514
|
planSnappyCursorMotion: () => planSnappyCursorMotion,
|
|
511
515
|
prepareSnapshot: () => prepareSnapshot,
|
|
512
516
|
pressKey: () => pressKey,
|
|
@@ -529,10 +533,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
529
533
|
var import_crypto = require("crypto");
|
|
530
534
|
|
|
531
535
|
// src/browser/pool.ts
|
|
532
|
-
var
|
|
533
|
-
var import_promises2 = require("fs/promises");
|
|
534
|
-
var import_node_net = require("net");
|
|
535
|
-
var import_playwright = require("playwright");
|
|
536
|
+
var import_playwright2 = require("playwright");
|
|
536
537
|
|
|
537
538
|
// src/browser/cdp-proxy.ts
|
|
538
539
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -1025,281 +1026,2644 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
|
|
|
1025
1026
|
}
|
|
1026
1027
|
|
|
1027
1028
|
// src/browser/persistent-profile.ts
|
|
1029
|
+
var import_node_crypto3 = require("crypto");
|
|
1030
|
+
var import_node_child_process2 = require("child_process");
|
|
1031
|
+
var import_node_fs3 = require("fs");
|
|
1032
|
+
var import_promises4 = require("fs/promises");
|
|
1033
|
+
var import_node_os = require("os");
|
|
1034
|
+
var import_node_path4 = require("path");
|
|
1035
|
+
var import_node_util2 = require("util");
|
|
1036
|
+
|
|
1037
|
+
// src/browser/persistent-profile-coordination.ts
|
|
1038
|
+
var import_node_path2 = require("path");
|
|
1039
|
+
|
|
1040
|
+
// src/browser/dir-lock.ts
|
|
1028
1041
|
var import_node_crypto = require("crypto");
|
|
1029
1042
|
var import_node_fs = require("fs");
|
|
1030
|
-
var
|
|
1031
|
-
var import_node_os = require("os");
|
|
1043
|
+
var import_promises2 = require("fs/promises");
|
|
1032
1044
|
var import_node_path = require("path");
|
|
1033
|
-
|
|
1045
|
+
|
|
1046
|
+
// src/browser/process-owner.ts
|
|
1047
|
+
var import_node_child_process = require("child_process");
|
|
1048
|
+
var import_promises = require("fs/promises");
|
|
1049
|
+
var import_node_util = require("util");
|
|
1050
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
1034
1051
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
1035
1052
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
1036
|
-
var
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
var
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
"hyphen-data",
|
|
1057
|
-
"OnDeviceHeadSuggestModel",
|
|
1058
|
-
"OptimizationGuidePredictionModels",
|
|
1059
|
-
"Segmentation Platform",
|
|
1060
|
-
"SmartCardDeviceNames",
|
|
1061
|
-
"WidevineCdm",
|
|
1062
|
-
"pnacl"
|
|
1063
|
-
]);
|
|
1064
|
-
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
1065
|
-
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1066
|
-
const targetUserDataDir = (0, import_node_path.join)(
|
|
1067
|
-
expandHome(profilesRootDir),
|
|
1068
|
-
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
1069
|
-
);
|
|
1070
|
-
const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
1071
|
-
const metadata = buildPersistentProfileMetadata(
|
|
1072
|
-
resolvedSourceUserDataDir,
|
|
1073
|
-
profileDirectory
|
|
1074
|
-
);
|
|
1075
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
|
|
1076
|
-
await cleanOrphanedTempDirs(
|
|
1077
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
1078
|
-
(0, import_node_path.basename)(targetUserDataDir)
|
|
1079
|
-
);
|
|
1080
|
-
if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
1081
|
-
throw new Error(
|
|
1082
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1083
|
-
);
|
|
1053
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
1054
|
+
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
1055
|
+
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
1056
|
+
var CURRENT_PROCESS_OWNER = {
|
|
1057
|
+
pid: process.pid,
|
|
1058
|
+
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
1059
|
+
};
|
|
1060
|
+
var linuxClockTicksPerSecondPromise = null;
|
|
1061
|
+
function parseProcessOwner(value) {
|
|
1062
|
+
if (!value || typeof value !== "object") {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
const parsed = value;
|
|
1066
|
+
const pid = Number(parsed.pid);
|
|
1067
|
+
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
1068
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
1072
|
+
return null;
|
|
1084
1073
|
}
|
|
1085
|
-
const created = await createPersistentProfileClone(
|
|
1086
|
-
resolvedSourceUserDataDir,
|
|
1087
|
-
sourceProfileDir,
|
|
1088
|
-
targetUserDataDir,
|
|
1089
|
-
profileDirectory,
|
|
1090
|
-
metadata
|
|
1091
|
-
);
|
|
1092
|
-
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1093
1074
|
return {
|
|
1094
|
-
|
|
1095
|
-
|
|
1075
|
+
pid,
|
|
1076
|
+
processStartedAtMs
|
|
1096
1077
|
};
|
|
1097
1078
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
recursive: true
|
|
1104
|
-
}).catch(() => void 0)
|
|
1105
|
-
)
|
|
1106
|
-
);
|
|
1079
|
+
function processOwnersEqual(left, right) {
|
|
1080
|
+
if (!left || !right) {
|
|
1081
|
+
return left === right;
|
|
1082
|
+
}
|
|
1083
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
1107
1084
|
}
|
|
1108
|
-
function
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1085
|
+
async function getProcessLiveness(owner) {
|
|
1086
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
1087
|
+
owner.processStartedAtMs,
|
|
1088
|
+
PROCESS_STARTED_AT_MS
|
|
1089
|
+
)) {
|
|
1090
|
+
return "live";
|
|
1091
|
+
}
|
|
1092
|
+
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
1093
|
+
if (typeof startedAtMs === "number") {
|
|
1094
|
+
return hasMatchingProcessStartTime(
|
|
1095
|
+
owner.processStartedAtMs,
|
|
1096
|
+
startedAtMs
|
|
1097
|
+
) ? "live" : "dead";
|
|
1098
|
+
}
|
|
1099
|
+
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
1113
1100
|
}
|
|
1114
|
-
function
|
|
1115
|
-
|
|
1101
|
+
function isProcessRunning(pid) {
|
|
1102
|
+
try {
|
|
1103
|
+
process.kill(pid, 0);
|
|
1104
|
+
return true;
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1107
|
+
return code !== "ESRCH";
|
|
1108
|
+
}
|
|
1116
1109
|
}
|
|
1117
|
-
function
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1110
|
+
async function readProcessOwner(pid) {
|
|
1111
|
+
const processStartedAtMs = await readProcessStartedAtMs(pid);
|
|
1112
|
+
if (processStartedAtMs === null) {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
pid,
|
|
1117
|
+
processStartedAtMs
|
|
1118
|
+
};
|
|
1120
1119
|
}
|
|
1121
|
-
function
|
|
1122
|
-
return
|
|
1120
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1121
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
1123
1122
|
}
|
|
1124
|
-
async function
|
|
1125
|
-
|
|
1123
|
+
async function readProcessStartedAtMs(pid) {
|
|
1124
|
+
if (pid <= 0) {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
if (process.platform === "linux") {
|
|
1128
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
1129
|
+
}
|
|
1130
|
+
if (process.platform === "win32") {
|
|
1131
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
1132
|
+
}
|
|
1133
|
+
return await readPsProcessStartedAtMs(pid);
|
|
1134
|
+
}
|
|
1135
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
1136
|
+
let statRaw;
|
|
1126
1137
|
try {
|
|
1127
|
-
|
|
1138
|
+
statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
1128
1139
|
} catch {
|
|
1129
|
-
return;
|
|
1140
|
+
return null;
|
|
1130
1141
|
}
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (entry === targetProfileDirectory) continue;
|
|
1135
|
-
const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
|
|
1136
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
|
|
1137
|
-
if ((0, import_node_fs.existsSync)(targetPath)) continue;
|
|
1138
|
-
let entryStat;
|
|
1139
|
-
try {
|
|
1140
|
-
entryStat = await (0, import_promises.stat)(sourcePath);
|
|
1141
|
-
} catch {
|
|
1142
|
-
continue;
|
|
1143
|
-
}
|
|
1144
|
-
if (entryStat.isFile()) {
|
|
1145
|
-
copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1146
|
-
} else if (entryStat.isDirectory()) {
|
|
1147
|
-
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1148
|
-
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1149
|
-
copyTasks.push(
|
|
1150
|
-
(0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1151
|
-
() => void 0
|
|
1152
|
-
)
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1142
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
1143
|
+
if (startTicks === null) {
|
|
1144
|
+
return null;
|
|
1155
1145
|
}
|
|
1156
|
-
await Promise.all(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1146
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
1147
|
+
readLinuxBootTimeMs(),
|
|
1148
|
+
readLinuxClockTicksPerSecond()
|
|
1149
|
+
]);
|
|
1150
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
return Math.floor(
|
|
1154
|
+
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
1162
1155
|
);
|
|
1163
1156
|
}
|
|
1164
|
-
function
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1157
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1158
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1159
|
+
if (closingParenIndex === -1) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
1163
|
+
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
1164
|
+
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
1170
1165
|
}
|
|
1171
|
-
async function
|
|
1172
|
-
|
|
1173
|
-
|
|
1166
|
+
async function readLinuxBootTimeMs() {
|
|
1167
|
+
try {
|
|
1168
|
+
const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
|
|
1169
|
+
const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
|
|
1170
|
+
if (!bootTimeLine) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
const bootTimeSeconds = Number.parseInt(
|
|
1174
|
+
bootTimeLine.slice("btime ".length),
|
|
1175
|
+
10
|
|
1176
|
+
);
|
|
1177
|
+
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
1178
|
+
} catch {
|
|
1179
|
+
return null;
|
|
1174
1180
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
)
|
|
1178
|
-
|
|
1181
|
+
}
|
|
1182
|
+
async function readLinuxClockTicksPerSecond() {
|
|
1183
|
+
if (!linuxClockTicksPerSecondPromise) {
|
|
1184
|
+
linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
|
|
1185
|
+
encoding: "utf8",
|
|
1186
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1187
|
+
}).then(({ stdout }) => {
|
|
1188
|
+
const value = Number.parseInt(stdout.trim(), 10);
|
|
1189
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
1190
|
+
}).catch(() => null);
|
|
1191
|
+
}
|
|
1192
|
+
return await linuxClockTicksPerSecondPromise;
|
|
1193
|
+
}
|
|
1194
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
1179
1195
|
try {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1196
|
+
const { stdout } = await execFileAsync(
|
|
1197
|
+
"powershell.exe",
|
|
1198
|
+
[
|
|
1199
|
+
"-NoProfile",
|
|
1200
|
+
"-Command",
|
|
1201
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString("o")`
|
|
1202
|
+
],
|
|
1203
|
+
{
|
|
1204
|
+
encoding: "utf8",
|
|
1205
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1206
|
+
}
|
|
1187
1207
|
);
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1208
|
+
const isoTimestamp = stdout.trim();
|
|
1209
|
+
if (!isoTimestamp) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
const startedAtMs = Date.parse(isoTimestamp);
|
|
1213
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
1214
|
+
} catch {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
async function readPsProcessStartedAtMs(pid) {
|
|
1219
|
+
try {
|
|
1220
|
+
const { stdout } = await execFileAsync(
|
|
1221
|
+
"ps",
|
|
1222
|
+
["-o", "lstart=", "-p", String(pid)],
|
|
1223
|
+
{
|
|
1224
|
+
encoding: "utf8",
|
|
1225
|
+
env: PS_COMMAND_ENV,
|
|
1226
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1194
1227
|
}
|
|
1195
|
-
|
|
1228
|
+
);
|
|
1229
|
+
const startedAt = stdout.trim();
|
|
1230
|
+
if (!startedAt) {
|
|
1231
|
+
return null;
|
|
1196
1232
|
}
|
|
1197
|
-
|
|
1198
|
-
return
|
|
1233
|
+
const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
|
|
1234
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
1235
|
+
} catch {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/browser/dir-lock.ts
|
|
1241
|
+
var LOCK_OWNER_FILE = "owner.json";
|
|
1242
|
+
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
1243
|
+
var LOCK_RETRY_DELAY_MS = 50;
|
|
1244
|
+
async function withDirLock(lockDirPath, action) {
|
|
1245
|
+
const releaseLock = await acquireDirLock(lockDirPath);
|
|
1246
|
+
try {
|
|
1247
|
+
return await action();
|
|
1199
1248
|
} finally {
|
|
1200
|
-
|
|
1201
|
-
|
|
1249
|
+
await releaseLock();
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
async function acquireDirLock(lockDirPath) {
|
|
1253
|
+
while (true) {
|
|
1254
|
+
const releaseLock = await tryAcquireDirLock(lockDirPath);
|
|
1255
|
+
if (releaseLock) {
|
|
1256
|
+
return releaseLock;
|
|
1257
|
+
}
|
|
1258
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
async function tryAcquireDirLock(lockDirPath) {
|
|
1262
|
+
await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
|
|
1263
|
+
while (true) {
|
|
1264
|
+
const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
1265
|
+
try {
|
|
1266
|
+
await (0, import_promises2.mkdir)(tempLockDirPath);
|
|
1267
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
|
|
1268
|
+
try {
|
|
1269
|
+
await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
|
|
1270
|
+
break;
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
1273
|
+
throw error;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
} finally {
|
|
1277
|
+
await (0, import_promises2.rm)(tempLockDirPath, {
|
|
1202
1278
|
recursive: true,
|
|
1203
1279
|
force: true
|
|
1204
1280
|
}).catch(() => void 0);
|
|
1205
1281
|
}
|
|
1282
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1283
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
return null;
|
|
1206
1287
|
}
|
|
1288
|
+
return async () => {
|
|
1289
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
1290
|
+
recursive: true,
|
|
1291
|
+
force: true
|
|
1292
|
+
}).catch(() => void 0);
|
|
1293
|
+
};
|
|
1207
1294
|
}
|
|
1208
|
-
async function
|
|
1209
|
-
if ((0, import_node_fs.existsSync)(
|
|
1210
|
-
return;
|
|
1295
|
+
async function isDirLockHeld(lockDirPath) {
|
|
1296
|
+
if (!(0, import_node_fs.existsSync)(lockDirPath)) {
|
|
1297
|
+
return false;
|
|
1211
1298
|
}
|
|
1212
|
-
await
|
|
1299
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1300
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
return (0, import_node_fs.existsSync)(lockDirPath);
|
|
1213
1304
|
}
|
|
1214
|
-
function
|
|
1215
|
-
|
|
1216
|
-
return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
1305
|
+
function getErrorCode(error) {
|
|
1306
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1217
1307
|
}
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1308
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
1309
|
+
const code = getErrorCode(error);
|
|
1310
|
+
return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
1311
|
+
}
|
|
1312
|
+
async function writeLockOwner(lockDirPath, owner) {
|
|
1313
|
+
await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
|
|
1314
|
+
}
|
|
1315
|
+
async function readLockOwner(lockDirPath) {
|
|
1316
|
+
return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
|
|
1317
|
+
}
|
|
1318
|
+
async function readLockParticipant(filePath) {
|
|
1319
|
+
return (await readLockParticipantRecord(filePath)).owner;
|
|
1320
|
+
}
|
|
1321
|
+
async function readLockParticipantRecord(filePath) {
|
|
1322
|
+
try {
|
|
1323
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf8");
|
|
1324
|
+
const owner = parseProcessOwner(JSON.parse(raw));
|
|
1325
|
+
return {
|
|
1326
|
+
exists: true,
|
|
1327
|
+
owner
|
|
1328
|
+
};
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
return {
|
|
1331
|
+
exists: getErrorCode(error) !== "ENOENT",
|
|
1332
|
+
owner: null
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
1337
|
+
return await readLockParticipantRecord(
|
|
1338
|
+
(0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
1222
1339
|
);
|
|
1223
1340
|
}
|
|
1224
|
-
async function
|
|
1225
|
-
|
|
1341
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
1342
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
let reclaimed = false;
|
|
1226
1346
|
try {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1347
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1348
|
+
if (!processOwnersEqual(owner, expectedOwner)) {
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
1355
|
+
recursive: true,
|
|
1356
|
+
force: true
|
|
1357
|
+
}).catch(() => void 0);
|
|
1358
|
+
reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
|
|
1359
|
+
return reclaimed;
|
|
1360
|
+
} finally {
|
|
1361
|
+
if (!reclaimed) {
|
|
1362
|
+
await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
|
|
1363
|
+
recursive: true,
|
|
1364
|
+
force: true
|
|
1365
|
+
}).catch(() => void 0);
|
|
1366
|
+
}
|
|
1233
1367
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1368
|
+
}
|
|
1369
|
+
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
1370
|
+
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
1371
|
+
while (true) {
|
|
1372
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
1373
|
+
try {
|
|
1374
|
+
await (0, import_promises2.mkdir)(tempReclaimerDirPath);
|
|
1375
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
|
|
1376
|
+
try {
|
|
1377
|
+
await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
|
|
1378
|
+
return true;
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
1384
|
+
throw error;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
const code = getErrorCode(error);
|
|
1389
|
+
if (code === "ENOENT") {
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
throw error;
|
|
1393
|
+
} finally {
|
|
1394
|
+
await (0, import_promises2.rm)(tempReclaimerDirPath, {
|
|
1395
|
+
recursive: true,
|
|
1396
|
+
force: true
|
|
1397
|
+
}).catch(() => void 0);
|
|
1398
|
+
}
|
|
1399
|
+
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
1400
|
+
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
await (0, import_promises2.rm)(reclaimerDirPath, {
|
|
1407
|
+
recursive: true,
|
|
1408
|
+
force: true
|
|
1409
|
+
}).catch(() => void 0);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
1413
|
+
return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
1414
|
+
}
|
|
1415
|
+
async function sleep(ms) {
|
|
1416
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// src/browser/persistent-profile-coordination.ts
|
|
1420
|
+
var PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS = 50;
|
|
1421
|
+
async function withPersistentProfileControlLock(targetUserDataDir, action) {
|
|
1422
|
+
return await withDirLock(
|
|
1423
|
+
buildPersistentProfileControlLockDirPath(targetUserDataDir),
|
|
1424
|
+
action
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
async function acquirePersistentProfileWriteLock(targetUserDataDir) {
|
|
1428
|
+
const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
|
|
1429
|
+
const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
|
|
1430
|
+
targetUserDataDir
|
|
1431
|
+
);
|
|
1432
|
+
while (true) {
|
|
1433
|
+
let releaseWriteLock = null;
|
|
1434
|
+
const releaseControlLock = await acquireDirLock(controlLockDirPath);
|
|
1435
|
+
try {
|
|
1436
|
+
releaseWriteLock = await tryAcquireDirLock(writeLockDirPath);
|
|
1437
|
+
} finally {
|
|
1438
|
+
await releaseControlLock();
|
|
1439
|
+
}
|
|
1440
|
+
if (releaseWriteLock) {
|
|
1441
|
+
return releaseWriteLock;
|
|
1442
|
+
}
|
|
1443
|
+
await sleep2(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async function isPersistentProfileWriteLocked(targetUserDataDir) {
|
|
1447
|
+
return await isDirLockHeld(
|
|
1448
|
+
buildPersistentProfileWriteLockDirPath(targetUserDataDir)
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
|
|
1452
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
|
|
1453
|
+
}
|
|
1454
|
+
function buildPersistentProfileControlLockDirPath(targetUserDataDir) {
|
|
1455
|
+
return (0, import_node_path2.join)(
|
|
1456
|
+
(0, import_node_path2.dirname)(targetUserDataDir),
|
|
1457
|
+
`${(0, import_node_path2.basename)(targetUserDataDir)}.control.lock`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
async function sleep2(ms) {
|
|
1461
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/browser/shared-real-browser-session-state.ts
|
|
1465
|
+
var import_node_crypto2 = require("crypto");
|
|
1466
|
+
var import_node_fs2 = require("fs");
|
|
1467
|
+
var import_promises3 = require("fs/promises");
|
|
1468
|
+
var import_node_path3 = require("path");
|
|
1469
|
+
var SHARED_SESSION_METADATA_FILE = "session.json";
|
|
1470
|
+
var SHARED_SESSION_CLIENTS_DIR = "clients";
|
|
1471
|
+
var SHARED_SESSION_RETRY_DELAY_MS = 50;
|
|
1472
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_PREFIX = `${SHARED_SESSION_METADATA_FILE}.`;
|
|
1473
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX = ".tmp";
|
|
1474
|
+
function buildSharedSessionDirPath(persistentUserDataDir) {
|
|
1475
|
+
return (0, import_node_path3.join)(
|
|
1476
|
+
(0, import_node_path3.dirname)(persistentUserDataDir),
|
|
1477
|
+
`${(0, import_node_path3.basename)(persistentUserDataDir)}.session`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
function buildSharedSessionLockPath(persistentUserDataDir) {
|
|
1481
|
+
return `${buildSharedSessionDirPath(persistentUserDataDir)}.lock`;
|
|
1482
|
+
}
|
|
1483
|
+
function buildSharedSessionClientsDirPath(persistentUserDataDir) {
|
|
1484
|
+
return (0, import_node_path3.join)(
|
|
1485
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
1486
|
+
SHARED_SESSION_CLIENTS_DIR
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
function buildSharedSessionClientPath(persistentUserDataDir, clientId) {
|
|
1490
|
+
return (0, import_node_path3.join)(
|
|
1491
|
+
buildSharedSessionClientsDirPath(persistentUserDataDir),
|
|
1492
|
+
`${clientId}.json`
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
async function readSharedSessionMetadata(persistentUserDataDir) {
|
|
1496
|
+
return (await readSharedSessionMetadataRecord(persistentUserDataDir)).metadata;
|
|
1497
|
+
}
|
|
1498
|
+
async function writeSharedSessionMetadata(persistentUserDataDir, metadata) {
|
|
1499
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
1500
|
+
const metadataPath = buildSharedSessionMetadataPath(persistentUserDataDir);
|
|
1501
|
+
const tempPath = buildSharedSessionMetadataTempPath(sessionDirPath);
|
|
1502
|
+
await (0, import_promises3.mkdir)(sessionDirPath, { recursive: true });
|
|
1503
|
+
try {
|
|
1504
|
+
await (0, import_promises3.writeFile)(tempPath, JSON.stringify(metadata, null, 2));
|
|
1505
|
+
await (0, import_promises3.rename)(tempPath, metadataPath);
|
|
1506
|
+
} finally {
|
|
1507
|
+
await (0, import_promises3.rm)(tempPath, { force: true }).catch(() => void 0);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
async function hasLiveSharedRealBrowserSession(persistentUserDataDir) {
|
|
1511
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
1512
|
+
const metadataRecord = await readSharedSessionMetadataRecord(
|
|
1513
|
+
persistentUserDataDir
|
|
1514
|
+
);
|
|
1515
|
+
if (!metadataRecord.exists) {
|
|
1516
|
+
return await hasLiveSharedSessionPublisherOrClients(sessionDirPath);
|
|
1517
|
+
}
|
|
1518
|
+
if (!metadataRecord.metadata) {
|
|
1519
|
+
return true;
|
|
1520
|
+
}
|
|
1521
|
+
if (await getProcessLiveness(metadataRecord.metadata.browserOwner) === "dead") {
|
|
1522
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
1523
|
+
force: true,
|
|
1524
|
+
recursive: true
|
|
1525
|
+
}).catch(() => void 0);
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
return true;
|
|
1529
|
+
}
|
|
1530
|
+
async function waitForSharedRealBrowserSessionToDrain(persistentUserDataDir) {
|
|
1531
|
+
while (true) {
|
|
1532
|
+
if (!await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
await sleep3(SHARED_SESSION_RETRY_DELAY_MS);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async function readSharedSessionMetadataRecord(persistentUserDataDir) {
|
|
1539
|
+
try {
|
|
1540
|
+
const raw = await (0, import_promises3.readFile)(
|
|
1541
|
+
buildSharedSessionMetadataPath(persistentUserDataDir),
|
|
1542
|
+
"utf8"
|
|
1543
|
+
);
|
|
1544
|
+
return {
|
|
1545
|
+
exists: true,
|
|
1546
|
+
metadata: parseSharedSessionMetadata(JSON.parse(raw))
|
|
1547
|
+
};
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
return {
|
|
1550
|
+
exists: getErrorCode2(error) !== "ENOENT",
|
|
1551
|
+
metadata: null
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async function hasLiveSharedSessionPublisherOrClients(sessionDirPath) {
|
|
1556
|
+
if (!(0, import_node_fs2.existsSync)(sessionDirPath)) {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
let entries;
|
|
1560
|
+
try {
|
|
1561
|
+
entries = await readDirNames(sessionDirPath);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
1564
|
+
}
|
|
1565
|
+
let hasUnknownEntries = false;
|
|
1566
|
+
for (const entry of entries) {
|
|
1567
|
+
if (entry === SHARED_SESSION_METADATA_FILE) {
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
if (entry === SHARED_SESSION_CLIENTS_DIR) {
|
|
1571
|
+
if (await hasDirectoryEntries((0, import_node_path3.join)(sessionDirPath, entry))) {
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
const owner = parseSharedSessionMetadataTempOwner(entry);
|
|
1577
|
+
if (!owner) {
|
|
1578
|
+
if (isSharedSessionMetadataTempFile(entry)) {
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
hasUnknownEntries = true;
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
if (await getProcessLiveness(owner) !== "dead") {
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (hasUnknownEntries) {
|
|
1589
|
+
return true;
|
|
1590
|
+
}
|
|
1591
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
1592
|
+
force: true,
|
|
1593
|
+
recursive: true
|
|
1594
|
+
}).catch(() => void 0);
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
function buildSharedSessionMetadataPath(persistentUserDataDir) {
|
|
1598
|
+
return (0, import_node_path3.join)(
|
|
1599
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
1600
|
+
SHARED_SESSION_METADATA_FILE
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
function buildSharedSessionMetadataTempPath(sessionDirPath) {
|
|
1604
|
+
return (0, import_node_path3.join)(
|
|
1605
|
+
sessionDirPath,
|
|
1606
|
+
[
|
|
1607
|
+
SHARED_SESSION_METADATA_FILE,
|
|
1608
|
+
CURRENT_PROCESS_OWNER.pid,
|
|
1609
|
+
CURRENT_PROCESS_OWNER.processStartedAtMs,
|
|
1610
|
+
(0, import_node_crypto2.randomUUID)(),
|
|
1611
|
+
"tmp"
|
|
1612
|
+
].join(".")
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
function parseSharedSessionMetadata(value) {
|
|
1616
|
+
if (!value || typeof value !== "object") {
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
const parsed = value;
|
|
1620
|
+
const browserOwner = parseProcessOwner(parsed.browserOwner);
|
|
1621
|
+
const stateOwner = parseProcessOwner(parsed.stateOwner);
|
|
1622
|
+
const state = parsed.state === "launching" || parsed.state === "ready" || parsed.state === "closing" ? parsed.state : null;
|
|
1623
|
+
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) {
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
return {
|
|
1627
|
+
browserOwner,
|
|
1628
|
+
createdAt: parsed.createdAt,
|
|
1629
|
+
debugPort: parsed.debugPort,
|
|
1630
|
+
executablePath: parsed.executablePath,
|
|
1631
|
+
headless: parsed.headless,
|
|
1632
|
+
persistentUserDataDir: parsed.persistentUserDataDir,
|
|
1633
|
+
profileDirectory: parsed.profileDirectory,
|
|
1634
|
+
sessionId: parsed.sessionId,
|
|
1635
|
+
state,
|
|
1636
|
+
stateOwner
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function parseSharedSessionMetadataTempOwner(entryName) {
|
|
1640
|
+
if (!isSharedSessionMetadataTempFile(entryName)) {
|
|
1641
|
+
return null;
|
|
1642
|
+
}
|
|
1643
|
+
const segments = entryName.split(".");
|
|
1644
|
+
if (segments.length < 5) {
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
return parseProcessOwner({
|
|
1648
|
+
pid: Number.parseInt(segments[2] ?? "", 10),
|
|
1649
|
+
processStartedAtMs: Number.parseInt(segments[3] ?? "", 10)
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function isSharedSessionMetadataTempFile(entryName) {
|
|
1653
|
+
return entryName.startsWith(SHARED_SESSION_METADATA_TEMP_FILE_PREFIX) && entryName.endsWith(SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX);
|
|
1654
|
+
}
|
|
1655
|
+
function getErrorCode2(error) {
|
|
1656
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1657
|
+
}
|
|
1658
|
+
async function hasDirectoryEntries(dirPath) {
|
|
1659
|
+
try {
|
|
1660
|
+
return (await readDirNames(dirPath)).length > 0;
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function readDirNames(dirPath) {
|
|
1666
|
+
return await (0, import_promises3.readdir)(dirPath, { encoding: "utf8" });
|
|
1667
|
+
}
|
|
1668
|
+
async function sleep3(ms) {
|
|
1669
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// src/browser/persistent-profile.ts
|
|
1673
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
1674
|
+
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
1675
|
+
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
1676
|
+
var OPENSTEER_RUNTIME_CREATING_FILE = ".opensteer-runtime-creating.json";
|
|
1677
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
|
|
1678
|
+
var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
|
|
1679
|
+
var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
|
|
1680
|
+
"SingletonCookie",
|
|
1681
|
+
"SingletonLock",
|
|
1682
|
+
"SingletonSocket",
|
|
1683
|
+
"DevToolsActivePort",
|
|
1684
|
+
"lockfile"
|
|
1685
|
+
]);
|
|
1686
|
+
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
1687
|
+
...CHROME_SINGLETON_ENTRIES,
|
|
1688
|
+
OPENSTEER_META_FILE,
|
|
1689
|
+
OPENSTEER_RUNTIME_META_FILE,
|
|
1690
|
+
OPENSTEER_RUNTIME_CREATING_FILE
|
|
1691
|
+
]);
|
|
1692
|
+
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1693
|
+
"Crash Reports",
|
|
1694
|
+
"Crashpad",
|
|
1695
|
+
"BrowserMetrics",
|
|
1696
|
+
"GrShaderCache",
|
|
1697
|
+
"ShaderCache",
|
|
1698
|
+
"GraphiteDawnCache",
|
|
1699
|
+
"component_crx_cache",
|
|
1700
|
+
"Crowd Deny",
|
|
1701
|
+
"hyphen-data",
|
|
1702
|
+
"OnDeviceHeadSuggestModel",
|
|
1703
|
+
"OptimizationGuidePredictionModels",
|
|
1704
|
+
"Segmentation Platform",
|
|
1705
|
+
"SmartCardDeviceNames",
|
|
1706
|
+
"WidevineCdm",
|
|
1707
|
+
"pnacl"
|
|
1708
|
+
]);
|
|
1709
|
+
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
1710
|
+
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1711
|
+
const targetUserDataDir = (0, import_node_path4.join)(
|
|
1712
|
+
expandHome(profilesRootDir),
|
|
1713
|
+
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
1714
|
+
);
|
|
1715
|
+
const sourceProfileDir = (0, import_node_path4.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
1716
|
+
const metadata = buildPersistentProfileMetadata(
|
|
1717
|
+
resolvedSourceUserDataDir,
|
|
1718
|
+
profileDirectory
|
|
1719
|
+
);
|
|
1720
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetUserDataDir), { recursive: true });
|
|
1721
|
+
if (await isHealthyPersistentProfile(
|
|
1722
|
+
targetUserDataDir,
|
|
1723
|
+
resolvedSourceUserDataDir,
|
|
1724
|
+
profileDirectory
|
|
1725
|
+
) && !await isPersistentProfileWriteLocked(targetUserDataDir)) {
|
|
1726
|
+
return {
|
|
1727
|
+
created: false,
|
|
1728
|
+
userDataDir: targetUserDataDir
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
return await withPersistentProfileWriteAccess(targetUserDataDir, async () => {
|
|
1732
|
+
await recoverPersistentProfileBackup(targetUserDataDir);
|
|
1733
|
+
await cleanOrphanedOwnedDirs(
|
|
1734
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
1735
|
+
buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
|
|
1736
|
+
);
|
|
1737
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
1738
|
+
throw new Error(
|
|
1739
|
+
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
const created = await createPersistentProfileClone(
|
|
1743
|
+
resolvedSourceUserDataDir,
|
|
1744
|
+
sourceProfileDir,
|
|
1745
|
+
targetUserDataDir,
|
|
1746
|
+
profileDirectory,
|
|
1747
|
+
metadata
|
|
1748
|
+
);
|
|
1749
|
+
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1750
|
+
return {
|
|
1751
|
+
created,
|
|
1752
|
+
userDataDir: targetUserDataDir
|
|
1753
|
+
};
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
async function clearPersistentProfileSingletons(userDataDir) {
|
|
1757
|
+
await Promise.all(
|
|
1758
|
+
[...CHROME_SINGLETON_ENTRIES].map(
|
|
1759
|
+
(entry) => (0, import_promises4.rm)((0, import_node_path4.join)(userDataDir, entry), {
|
|
1760
|
+
force: true,
|
|
1761
|
+
recursive: true
|
|
1762
|
+
}).catch(() => void 0)
|
|
1763
|
+
)
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
|
|
1767
|
+
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1768
|
+
const runtimeRootDir = expandHome(runtimesRootDir);
|
|
1769
|
+
await (0, import_promises4.mkdir)(runtimeRootDir, { recursive: true });
|
|
1770
|
+
const sourceMetadata = await requirePersistentProfileMetadata(
|
|
1771
|
+
resolvedSourceUserDataDir
|
|
1772
|
+
);
|
|
1773
|
+
const runtimeProfile = await reserveRuntimeProfileCreation(
|
|
1774
|
+
resolvedSourceUserDataDir,
|
|
1775
|
+
runtimeRootDir,
|
|
1776
|
+
sourceMetadata.profileDirectory
|
|
1777
|
+
);
|
|
1778
|
+
try {
|
|
1779
|
+
await cleanOrphanedRuntimeProfileDirs(
|
|
1780
|
+
runtimeRootDir,
|
|
1781
|
+
buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
|
|
1782
|
+
);
|
|
1783
|
+
await copyUserDataDirSnapshot(
|
|
1784
|
+
resolvedSourceUserDataDir,
|
|
1785
|
+
runtimeProfile.userDataDir
|
|
1786
|
+
);
|
|
1787
|
+
const currentSourceMetadata = await readPersistentProfileMetadata(
|
|
1788
|
+
resolvedSourceUserDataDir
|
|
1789
|
+
);
|
|
1790
|
+
await writeRuntimeProfileMetadata(
|
|
1791
|
+
runtimeProfile.userDataDir,
|
|
1792
|
+
await buildRuntimeProfileMetadata(
|
|
1793
|
+
runtimeProfile.userDataDir,
|
|
1794
|
+
resolvedSourceUserDataDir,
|
|
1795
|
+
currentSourceMetadata?.profileDirectory ?? sourceMetadata.profileDirectory
|
|
1796
|
+
)
|
|
1797
|
+
);
|
|
1798
|
+
await clearRuntimeProfileCreationState(
|
|
1799
|
+
runtimeProfile.userDataDir,
|
|
1800
|
+
resolvedSourceUserDataDir
|
|
1801
|
+
);
|
|
1802
|
+
return {
|
|
1803
|
+
persistentUserDataDir: resolvedSourceUserDataDir,
|
|
1804
|
+
userDataDir: runtimeProfile.userDataDir
|
|
1805
|
+
};
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
await clearRuntimeProfileCreationState(
|
|
1808
|
+
runtimeProfile.userDataDir,
|
|
1809
|
+
resolvedSourceUserDataDir
|
|
1810
|
+
);
|
|
1811
|
+
await (0, import_promises4.rm)(runtimeProfile.userDataDir, {
|
|
1812
|
+
recursive: true,
|
|
1813
|
+
force: true
|
|
1814
|
+
}).catch(() => void 0);
|
|
1815
|
+
throw error;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
|
|
1819
|
+
const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
|
|
1820
|
+
const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
|
|
1821
|
+
let claimedRuntimeUserDataDir = null;
|
|
1822
|
+
try {
|
|
1823
|
+
await withPersistentProfileWriteAccess(
|
|
1824
|
+
resolvedPersistentUserDataDir,
|
|
1825
|
+
async () => {
|
|
1826
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(resolvedPersistentUserDataDir), {
|
|
1827
|
+
recursive: true
|
|
1828
|
+
});
|
|
1829
|
+
await recoverPersistentProfileBackup(resolvedPersistentUserDataDir);
|
|
1830
|
+
await cleanOrphanedOwnedDirs(
|
|
1831
|
+
(0, import_node_path4.dirname)(resolvedPersistentUserDataDir),
|
|
1832
|
+
buildPersistentProfileTempDirNamePrefix(
|
|
1833
|
+
resolvedPersistentUserDataDir
|
|
1834
|
+
)
|
|
1835
|
+
);
|
|
1836
|
+
const metadata = await requirePersistentProfileMetadata(
|
|
1837
|
+
resolvedPersistentUserDataDir
|
|
1838
|
+
);
|
|
1839
|
+
claimedRuntimeUserDataDir = await claimRuntimeProfileForPersist(
|
|
1840
|
+
resolvedRuntimeUserDataDir
|
|
1841
|
+
);
|
|
1842
|
+
const runtimeMetadata = await requireRuntimeProfileMetadata(
|
|
1843
|
+
claimedRuntimeUserDataDir,
|
|
1844
|
+
resolvedPersistentUserDataDir,
|
|
1845
|
+
metadata.profileDirectory,
|
|
1846
|
+
resolvedRuntimeUserDataDir
|
|
1847
|
+
);
|
|
1848
|
+
await mergePersistentProfileSnapshot(
|
|
1849
|
+
claimedRuntimeUserDataDir,
|
|
1850
|
+
resolvedPersistentUserDataDir,
|
|
1851
|
+
metadata,
|
|
1852
|
+
runtimeMetadata
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
if (claimedRuntimeUserDataDir) {
|
|
1858
|
+
try {
|
|
1859
|
+
await restoreClaimedRuntimeProfile(
|
|
1860
|
+
claimedRuntimeUserDataDir,
|
|
1861
|
+
resolvedRuntimeUserDataDir
|
|
1862
|
+
);
|
|
1863
|
+
} catch (restoreError) {
|
|
1864
|
+
throw new AggregateError(
|
|
1865
|
+
[error, restoreError],
|
|
1866
|
+
`Failed to restore runtime profile "${resolvedRuntimeUserDataDir}" after persistence failed.`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
throw error;
|
|
1871
|
+
}
|
|
1872
|
+
if (claimedRuntimeUserDataDir) {
|
|
1873
|
+
await (0, import_promises4.rm)(claimedRuntimeUserDataDir, {
|
|
1874
|
+
recursive: true,
|
|
1875
|
+
force: true
|
|
1876
|
+
}).catch(() => void 0);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
1880
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
1881
|
+
const sourceLabel = sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "user-data");
|
|
1882
|
+
const profileLabel = sanitizePathSegment(profileDirectory || "Default");
|
|
1883
|
+
return `${sourceLabel}-${profileLabel}-${hash}`;
|
|
1884
|
+
}
|
|
1885
|
+
function defaultPersistentProfilesRootDir() {
|
|
1886
|
+
return (0, import_node_path4.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
|
|
1887
|
+
}
|
|
1888
|
+
function defaultRuntimeProfilesRootDir() {
|
|
1889
|
+
return (0, import_node_path4.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-runtimes");
|
|
1890
|
+
}
|
|
1891
|
+
function sanitizePathSegment(value) {
|
|
1892
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
1893
|
+
return sanitized.replace(/^-|-$/g, "") || "profile";
|
|
1894
|
+
}
|
|
1895
|
+
function isProfileDirectory(userDataDir, entry) {
|
|
1896
|
+
return (0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, entry, "Preferences"));
|
|
1897
|
+
}
|
|
1898
|
+
async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
|
|
1899
|
+
await (0, import_promises4.cp)(sourceUserDataDir, targetUserDataDir, {
|
|
1900
|
+
recursive: true,
|
|
1901
|
+
filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
|
|
1905
|
+
const candidateRelativePath = (0, import_node_path4.relative)(userDataDir, candidatePath);
|
|
1906
|
+
if (!candidateRelativePath) {
|
|
1907
|
+
return true;
|
|
1908
|
+
}
|
|
1909
|
+
const segments = candidateRelativePath.split(import_node_path4.sep).filter(Boolean);
|
|
1910
|
+
if (segments.length !== 1) {
|
|
1911
|
+
return true;
|
|
1912
|
+
}
|
|
1913
|
+
return !COPY_SKIP_ENTRIES.has(segments[0]);
|
|
1914
|
+
}
|
|
1915
|
+
async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
|
|
1916
|
+
let entries;
|
|
1917
|
+
try {
|
|
1918
|
+
entries = await (0, import_promises4.readdir)(sourceUserDataDir);
|
|
1919
|
+
} catch {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
const copyTasks = [];
|
|
1923
|
+
for (const entry of entries) {
|
|
1924
|
+
if (COPY_SKIP_ENTRIES.has(entry)) continue;
|
|
1925
|
+
if (entry === targetProfileDirectory) continue;
|
|
1926
|
+
const sourcePath = (0, import_node_path4.join)(sourceUserDataDir, entry);
|
|
1927
|
+
const targetPath = (0, import_node_path4.join)(targetUserDataDir, entry);
|
|
1928
|
+
if ((0, import_node_fs3.existsSync)(targetPath)) continue;
|
|
1929
|
+
let entryStat;
|
|
1930
|
+
try {
|
|
1931
|
+
entryStat = await (0, import_promises4.stat)(sourcePath);
|
|
1932
|
+
} catch {
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
if (entryStat.isFile()) {
|
|
1936
|
+
copyTasks.push((0, import_promises4.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1937
|
+
} else if (entryStat.isDirectory()) {
|
|
1938
|
+
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1939
|
+
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1940
|
+
copyTasks.push(
|
|
1941
|
+
(0, import_promises4.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1942
|
+
() => void 0
|
|
1943
|
+
)
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
await Promise.all(copyTasks);
|
|
1948
|
+
}
|
|
1949
|
+
async function writePersistentProfileMetadata(userDataDir, metadata) {
|
|
1950
|
+
await (0, import_promises4.writeFile)(
|
|
1951
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE),
|
|
1952
|
+
JSON.stringify(metadata, null, 2)
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
|
|
1956
|
+
return {
|
|
1957
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1958
|
+
profileDirectory,
|
|
1959
|
+
source: sourceUserDataDir
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
1963
|
+
if ((0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
1964
|
+
return false;
|
|
1965
|
+
}
|
|
1966
|
+
const tempUserDataDir = await (0, import_promises4.mkdtemp)(
|
|
1967
|
+
buildPersistentProfileTempDirPrefix(targetUserDataDir)
|
|
1968
|
+
);
|
|
1969
|
+
let published = false;
|
|
1970
|
+
try {
|
|
1971
|
+
await materializePersistentProfileSnapshot(
|
|
1972
|
+
sourceUserDataDir,
|
|
1973
|
+
sourceProfileDir,
|
|
1974
|
+
tempUserDataDir,
|
|
1975
|
+
profileDirectory,
|
|
1976
|
+
metadata
|
|
1977
|
+
);
|
|
1978
|
+
try {
|
|
1979
|
+
await (0, import_promises4.rename)(tempUserDataDir, targetUserDataDir);
|
|
1980
|
+
} catch (error) {
|
|
1981
|
+
if (wasDirPublishedByAnotherProcess2(error, targetUserDataDir)) {
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
throw error;
|
|
1985
|
+
}
|
|
1986
|
+
published = true;
|
|
1987
|
+
return true;
|
|
1988
|
+
} finally {
|
|
1989
|
+
if (!published) {
|
|
1990
|
+
await (0, import_promises4.rm)(tempUserDataDir, {
|
|
1991
|
+
recursive: true,
|
|
1992
|
+
force: true
|
|
1993
|
+
}).catch(() => void 0);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
1998
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
1999
|
+
throw new Error(
|
|
2000
|
+
`Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
await (0, import_promises4.cp)(sourceProfileDir, (0, import_node_path4.join)(targetUserDataDir, profileDirectory), {
|
|
2004
|
+
recursive: true
|
|
2005
|
+
});
|
|
2006
|
+
await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
|
|
2007
|
+
await writePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
2008
|
+
}
|
|
2009
|
+
async function mergePersistentProfileSnapshot(runtimeUserDataDir, persistentUserDataDir, metadata, runtimeMetadata) {
|
|
2010
|
+
const tempUserDataDir = await (0, import_promises4.mkdtemp)(
|
|
2011
|
+
buildPersistentProfileTempDirPrefix(persistentUserDataDir)
|
|
2012
|
+
);
|
|
2013
|
+
let published = false;
|
|
2014
|
+
try {
|
|
2015
|
+
const baseEntries = deserializeSnapshotManifestEntries(
|
|
2016
|
+
runtimeMetadata.baseEntries
|
|
2017
|
+
);
|
|
2018
|
+
const currentEntries = await collectPersistentSnapshotEntries(
|
|
2019
|
+
persistentUserDataDir,
|
|
2020
|
+
metadata.profileDirectory
|
|
2021
|
+
);
|
|
2022
|
+
const runtimeEntries = await collectPersistentSnapshotEntries(
|
|
2023
|
+
runtimeUserDataDir,
|
|
2024
|
+
metadata.profileDirectory
|
|
2025
|
+
);
|
|
2026
|
+
const mergedEntries = resolveMergedSnapshotEntries(
|
|
2027
|
+
baseEntries,
|
|
2028
|
+
currentEntries,
|
|
2029
|
+
runtimeEntries
|
|
2030
|
+
);
|
|
2031
|
+
await materializeMergedPersistentProfileSnapshot(
|
|
2032
|
+
tempUserDataDir,
|
|
2033
|
+
currentEntries,
|
|
2034
|
+
runtimeEntries,
|
|
2035
|
+
mergedEntries
|
|
2036
|
+
);
|
|
2037
|
+
await writePersistentProfileMetadata(tempUserDataDir, metadata);
|
|
2038
|
+
await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
|
|
2039
|
+
published = true;
|
|
2040
|
+
} finally {
|
|
2041
|
+
if (!published) {
|
|
2042
|
+
await (0, import_promises4.rm)(tempUserDataDir, {
|
|
2043
|
+
recursive: true,
|
|
2044
|
+
force: true
|
|
2045
|
+
}).catch(() => void 0);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
async function buildRuntimeProfileMetadata(runtimeUserDataDir, persistentUserDataDir, profileDirectory) {
|
|
2050
|
+
const baseEntries = profileDirectory ? serializeSnapshotManifestEntries(
|
|
2051
|
+
await collectPersistentSnapshotEntries(
|
|
2052
|
+
runtimeUserDataDir,
|
|
2053
|
+
profileDirectory
|
|
2054
|
+
)
|
|
2055
|
+
) : {};
|
|
2056
|
+
return {
|
|
2057
|
+
baseEntries,
|
|
2058
|
+
creator: CURRENT_PROCESS_OWNER,
|
|
2059
|
+
persistentUserDataDir,
|
|
2060
|
+
profileDirectory
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
async function writeRuntimeProfileMetadata(userDataDir, metadata) {
|
|
2064
|
+
await (0, import_promises4.writeFile)(
|
|
2065
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
2066
|
+
JSON.stringify(metadata, null, 2)
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
async function writeRuntimeProfileCreationMarker(userDataDir, marker) {
|
|
2070
|
+
await (0, import_promises4.writeFile)(
|
|
2071
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
|
|
2072
|
+
JSON.stringify(marker, null, 2)
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
async function readRuntimeProfileMetadata(userDataDir) {
|
|
2076
|
+
try {
|
|
2077
|
+
const raw = await (0, import_promises4.readFile)(
|
|
2078
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
2079
|
+
"utf8"
|
|
2080
|
+
);
|
|
2081
|
+
const parsed = JSON.parse(raw);
|
|
2082
|
+
const creator = parseProcessOwner(parsed.creator);
|
|
2083
|
+
const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
|
|
2084
|
+
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
2085
|
+
if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
const baseEntries = deserializeSnapshotManifestEntries(
|
|
2089
|
+
parsed.baseEntries
|
|
2090
|
+
);
|
|
2091
|
+
return {
|
|
2092
|
+
baseEntries: Object.fromEntries(baseEntries),
|
|
2093
|
+
creator,
|
|
2094
|
+
persistentUserDataDir,
|
|
2095
|
+
profileDirectory
|
|
2096
|
+
};
|
|
2097
|
+
} catch {
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
async function readRuntimeProfileCreationMarker(userDataDir) {
|
|
2102
|
+
try {
|
|
2103
|
+
const raw = await (0, import_promises4.readFile)(
|
|
2104
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
|
|
2105
|
+
"utf8"
|
|
2106
|
+
);
|
|
2107
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
2108
|
+
} catch {
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
async function requireRuntimeProfileMetadata(userDataDir, expectedPersistentUserDataDir, expectedProfileDirectory, displayUserDataDir = userDataDir) {
|
|
2113
|
+
const metadata = await readRuntimeProfileMetadata(userDataDir);
|
|
2114
|
+
if (!metadata) {
|
|
2115
|
+
throw new Error(
|
|
2116
|
+
`Runtime profile metadata was not found for "${displayUserDataDir}".`
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
if (metadata.profileDirectory !== expectedProfileDirectory) {
|
|
2120
|
+
throw new Error(
|
|
2121
|
+
`Runtime profile "${displayUserDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
if (metadata.persistentUserDataDir !== expectedPersistentUserDataDir) {
|
|
2125
|
+
throw new Error(
|
|
2126
|
+
`Runtime profile "${displayUserDataDir}" does not belong to persistent profile "${expectedPersistentUserDataDir}".`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
return metadata;
|
|
2130
|
+
}
|
|
2131
|
+
async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
|
|
2132
|
+
let rootEntries;
|
|
2133
|
+
try {
|
|
2134
|
+
rootEntries = await (0, import_promises4.readdir)(userDataDir, {
|
|
2135
|
+
encoding: "utf8",
|
|
2136
|
+
withFileTypes: true
|
|
2137
|
+
});
|
|
2138
|
+
} catch {
|
|
2139
|
+
return /* @__PURE__ */ new Map();
|
|
2140
|
+
}
|
|
2141
|
+
rootEntries.sort((left, right) => left.name.localeCompare(right.name));
|
|
2142
|
+
const collected = /* @__PURE__ */ new Map();
|
|
2143
|
+
for (const entry of rootEntries) {
|
|
2144
|
+
if (!shouldIncludePersistentRootEntry(
|
|
2145
|
+
userDataDir,
|
|
2146
|
+
profileDirectory,
|
|
2147
|
+
entry.name
|
|
2148
|
+
)) {
|
|
2149
|
+
continue;
|
|
2150
|
+
}
|
|
2151
|
+
await collectSnapshotEntry(
|
|
2152
|
+
(0, import_node_path4.join)(userDataDir, entry.name),
|
|
2153
|
+
entry.name,
|
|
2154
|
+
collected
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
return collected;
|
|
2158
|
+
}
|
|
2159
|
+
function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
|
|
2160
|
+
if (entry === profileDirectory) {
|
|
2161
|
+
return true;
|
|
2162
|
+
}
|
|
2163
|
+
if (COPY_SKIP_ENTRIES.has(entry)) {
|
|
2164
|
+
return false;
|
|
2165
|
+
}
|
|
2166
|
+
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) {
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
return !isProfileDirectory(userDataDir, entry);
|
|
2170
|
+
}
|
|
2171
|
+
async function collectSnapshotEntry(sourcePath, relativePath, collected) {
|
|
2172
|
+
let entryStat;
|
|
2173
|
+
try {
|
|
2174
|
+
entryStat = await (0, import_promises4.stat)(sourcePath);
|
|
2175
|
+
} catch {
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
if (entryStat.isDirectory()) {
|
|
2179
|
+
collected.set(relativePath, {
|
|
2180
|
+
kind: "directory",
|
|
2181
|
+
hash: null,
|
|
2182
|
+
sourcePath
|
|
2183
|
+
});
|
|
2184
|
+
let children;
|
|
2185
|
+
try {
|
|
2186
|
+
children = await (0, import_promises4.readdir)(sourcePath, {
|
|
2187
|
+
encoding: "utf8",
|
|
2188
|
+
withFileTypes: true
|
|
2189
|
+
});
|
|
2190
|
+
} catch {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
2194
|
+
for (const child of children) {
|
|
2195
|
+
await collectSnapshotEntry(
|
|
2196
|
+
(0, import_node_path4.join)(sourcePath, child.name),
|
|
2197
|
+
(0, import_node_path4.join)(relativePath, child.name),
|
|
2198
|
+
collected
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
if (entryStat.isFile()) {
|
|
2204
|
+
collected.set(relativePath, {
|
|
2205
|
+
kind: "file",
|
|
2206
|
+
hash: await hashSnapshotFile(sourcePath, relativePath),
|
|
2207
|
+
sourcePath
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function serializeSnapshotManifestEntries(entries) {
|
|
2212
|
+
return Object.fromEntries(
|
|
2213
|
+
[...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
|
|
2214
|
+
relativePath,
|
|
2215
|
+
{
|
|
2216
|
+
kind: entry.kind,
|
|
2217
|
+
hash: entry.hash
|
|
2218
|
+
}
|
|
2219
|
+
])
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
function deserializeSnapshotManifestEntries(entries) {
|
|
2223
|
+
const manifestEntries = /* @__PURE__ */ new Map();
|
|
2224
|
+
for (const [relativePath, entry] of Object.entries(entries)) {
|
|
2225
|
+
if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
|
|
2226
|
+
throw new Error(
|
|
2227
|
+
`Runtime profile metadata for "${relativePath}" is invalid.`
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
manifestEntries.set(relativePath, {
|
|
2231
|
+
kind: entry.kind,
|
|
2232
|
+
hash: entry.hash
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
return manifestEntries;
|
|
2236
|
+
}
|
|
2237
|
+
function resolveMergedSnapshotEntries(baseEntries, currentEntries, runtimeEntries) {
|
|
2238
|
+
const mergedEntries = /* @__PURE__ */ new Map();
|
|
2239
|
+
const relativePaths = /* @__PURE__ */ new Set([
|
|
2240
|
+
...baseEntries.keys(),
|
|
2241
|
+
...currentEntries.keys(),
|
|
2242
|
+
...runtimeEntries.keys()
|
|
2243
|
+
]);
|
|
2244
|
+
for (const relativePath of [...relativePaths].sort(compareSnapshotPaths)) {
|
|
2245
|
+
mergedEntries.set(
|
|
2246
|
+
relativePath,
|
|
2247
|
+
resolveMergedSnapshotEntrySelection(
|
|
2248
|
+
relativePath,
|
|
2249
|
+
baseEntries.get(relativePath) ?? null,
|
|
2250
|
+
currentEntries.get(relativePath) ?? null,
|
|
2251
|
+
runtimeEntries.get(relativePath) ?? null
|
|
2252
|
+
)
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
return mergedEntries;
|
|
2256
|
+
}
|
|
2257
|
+
function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
|
|
2258
|
+
if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
|
|
2259
|
+
return currentEntry ? "current" : null;
|
|
2260
|
+
}
|
|
2261
|
+
if (snapshotEntriesEqual(currentEntry, baseEntry)) {
|
|
2262
|
+
return runtimeEntry ? "runtime" : null;
|
|
2263
|
+
}
|
|
2264
|
+
if (!baseEntry) {
|
|
2265
|
+
if (!currentEntry) {
|
|
2266
|
+
return runtimeEntry ? "runtime" : null;
|
|
2267
|
+
}
|
|
2268
|
+
if (!runtimeEntry) {
|
|
2269
|
+
return "current";
|
|
2270
|
+
}
|
|
2271
|
+
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
2272
|
+
return "current";
|
|
2273
|
+
}
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
if (!currentEntry && !runtimeEntry) {
|
|
2279
|
+
return null;
|
|
2280
|
+
}
|
|
2281
|
+
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
2282
|
+
return currentEntry ? "current" : null;
|
|
2283
|
+
}
|
|
2284
|
+
throw new Error(
|
|
2285
|
+
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
function snapshotEntriesEqual(left, right) {
|
|
2289
|
+
if (!left || !right) {
|
|
2290
|
+
return left === right;
|
|
2291
|
+
}
|
|
2292
|
+
return left.kind === right.kind && left.hash === right.hash;
|
|
2293
|
+
}
|
|
2294
|
+
async function materializeMergedPersistentProfileSnapshot(targetUserDataDir, currentEntries, runtimeEntries, mergedEntries) {
|
|
2295
|
+
const selectedEntries = [...mergedEntries.entries()].filter(([, selection]) => selection !== null).sort(
|
|
2296
|
+
([leftPath], [rightPath]) => compareSnapshotPaths(leftPath, rightPath)
|
|
2297
|
+
);
|
|
2298
|
+
for (const [relativePath, selection] of selectedEntries) {
|
|
2299
|
+
const entry = (selection === "current" ? currentEntries.get(relativePath) : runtimeEntries.get(relativePath)) ?? null;
|
|
2300
|
+
if (!entry) {
|
|
2301
|
+
continue;
|
|
2302
|
+
}
|
|
2303
|
+
const targetPath = (0, import_node_path4.join)(targetUserDataDir, relativePath);
|
|
2304
|
+
if (entry.kind === "directory") {
|
|
2305
|
+
await (0, import_promises4.mkdir)(targetPath, { recursive: true });
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetPath), { recursive: true });
|
|
2309
|
+
await (0, import_promises4.copyFile)(entry.sourcePath, targetPath);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
function compareSnapshotPaths(left, right) {
|
|
2313
|
+
const leftDepth = left.split(import_node_path4.sep).length;
|
|
2314
|
+
const rightDepth = right.split(import_node_path4.sep).length;
|
|
2315
|
+
if (leftDepth !== rightDepth) {
|
|
2316
|
+
return leftDepth - rightDepth;
|
|
2317
|
+
}
|
|
2318
|
+
return left.localeCompare(right);
|
|
2319
|
+
}
|
|
2320
|
+
async function hashSnapshotFile(filePath, relativePath) {
|
|
2321
|
+
const normalizedJson = await readNormalizedSnapshotJson(filePath, relativePath);
|
|
2322
|
+
if (normalizedJson !== null) {
|
|
2323
|
+
return (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(normalizedJson)).digest("hex");
|
|
2324
|
+
}
|
|
2325
|
+
return await hashFile(filePath);
|
|
2326
|
+
}
|
|
2327
|
+
async function readNormalizedSnapshotJson(filePath, relativePath) {
|
|
2328
|
+
const normalizer = SNAPSHOT_JSON_NORMALIZERS.get(relativePath);
|
|
2329
|
+
if (!normalizer) {
|
|
2330
|
+
return null;
|
|
2331
|
+
}
|
|
2332
|
+
try {
|
|
2333
|
+
const parsed = JSON.parse(await (0, import_promises4.readFile)(filePath, "utf8"));
|
|
2334
|
+
return normalizer(parsed);
|
|
2335
|
+
} catch {
|
|
2336
|
+
return null;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
var SNAPSHOT_JSON_NORMALIZERS = /* @__PURE__ */ new Map([["Local State", normalizeLocalStateSnapshotJson]]);
|
|
2340
|
+
function normalizeLocalStateSnapshotJson(value) {
|
|
2341
|
+
if (!isJsonRecord(value)) {
|
|
2342
|
+
return value;
|
|
2343
|
+
}
|
|
2344
|
+
const { user_experience_metrics: _ignored, ...rest } = value;
|
|
2345
|
+
return rest;
|
|
2346
|
+
}
|
|
2347
|
+
function isJsonRecord(value) {
|
|
2348
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2349
|
+
}
|
|
2350
|
+
async function hashFile(filePath) {
|
|
2351
|
+
return new Promise((resolve, reject) => {
|
|
2352
|
+
const hash = (0, import_node_crypto3.createHash)("sha256");
|
|
2353
|
+
const stream = (0, import_node_fs3.createReadStream)(filePath);
|
|
2354
|
+
stream.on("data", (chunk) => {
|
|
2355
|
+
hash.update(chunk);
|
|
2356
|
+
});
|
|
2357
|
+
stream.on("error", reject);
|
|
2358
|
+
stream.on("end", () => {
|
|
2359
|
+
resolve(hash.digest("hex"));
|
|
2360
|
+
});
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
async function ensurePersistentProfileMetadata(userDataDir, metadata) {
|
|
2364
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE))) {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
await writePersistentProfileMetadata(userDataDir, metadata);
|
|
2368
|
+
}
|
|
2369
|
+
async function recoverPersistentProfileBackup(targetUserDataDir) {
|
|
2370
|
+
const backupDirPaths = await listPersistentProfileBackupDirs(targetUserDataDir);
|
|
2371
|
+
if (backupDirPaths.length === 0) {
|
|
2372
|
+
return;
|
|
2373
|
+
}
|
|
2374
|
+
if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
2375
|
+
const [latestBackupDirPath, ...staleBackupDirPaths] = backupDirPaths;
|
|
2376
|
+
await (0, import_promises4.rename)(latestBackupDirPath, targetUserDataDir);
|
|
2377
|
+
await Promise.all(
|
|
2378
|
+
staleBackupDirPaths.map(
|
|
2379
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
2380
|
+
recursive: true,
|
|
2381
|
+
force: true
|
|
2382
|
+
}).catch(() => void 0)
|
|
2383
|
+
)
|
|
2384
|
+
);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
await Promise.all(
|
|
2388
|
+
backupDirPaths.map(
|
|
2389
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
2390
|
+
recursive: true,
|
|
2391
|
+
force: true
|
|
2392
|
+
}).catch(() => void 0)
|
|
2393
|
+
)
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
async function listPersistentProfileBackupDirs(targetUserDataDir) {
|
|
2397
|
+
const profilesDir = (0, import_node_path4.dirname)(targetUserDataDir);
|
|
2398
|
+
let entries;
|
|
2399
|
+
try {
|
|
2400
|
+
entries = await (0, import_promises4.readdir)(profilesDir, {
|
|
2401
|
+
encoding: "utf8",
|
|
2402
|
+
withFileTypes: true
|
|
2403
|
+
});
|
|
2404
|
+
} catch {
|
|
2405
|
+
return [];
|
|
2406
|
+
}
|
|
2407
|
+
const backupDirNamePrefix = buildPersistentProfileBackupDirNamePrefix(targetUserDataDir);
|
|
2408
|
+
return entries.filter(
|
|
2409
|
+
(entry) => entry.isDirectory() && entry.name.startsWith(backupDirNamePrefix)
|
|
2410
|
+
).map((entry) => (0, import_node_path4.join)(profilesDir, entry.name)).sort((leftPath, rightPath) => rightPath.localeCompare(leftPath));
|
|
2411
|
+
}
|
|
2412
|
+
async function readPersistentProfileMetadata(userDataDir) {
|
|
2413
|
+
try {
|
|
2414
|
+
const raw = await (0, import_promises4.readFile)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
|
|
2415
|
+
const parsed = JSON.parse(raw);
|
|
2416
|
+
if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
return {
|
|
2420
|
+
createdAt: parsed.createdAt,
|
|
2421
|
+
profileDirectory: parsed.profileDirectory,
|
|
2422
|
+
source: parsed.source
|
|
2423
|
+
};
|
|
2424
|
+
} catch {
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
async function requirePersistentProfileMetadata(userDataDir) {
|
|
2429
|
+
const metadata = await readPersistentProfileMetadata(userDataDir);
|
|
2430
|
+
if (!metadata) {
|
|
2431
|
+
throw new Error(
|
|
2432
|
+
`Persistent profile metadata was not found for "${userDataDir}".`
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
return metadata;
|
|
2436
|
+
}
|
|
2437
|
+
async function isHealthyPersistentProfile(userDataDir, expectedSourceUserDataDir, expectedProfileDirectory) {
|
|
2438
|
+
if (!(0, import_node_fs3.existsSync)(userDataDir) || !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, expectedProfileDirectory))) {
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
const metadata = await readPersistentProfileMetadata(userDataDir);
|
|
2442
|
+
return metadata?.source === expectedSourceUserDataDir && metadata.profileDirectory === expectedProfileDirectory;
|
|
2443
|
+
}
|
|
2444
|
+
function wasDirPublishedByAnotherProcess2(error, targetDirPath) {
|
|
2445
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2446
|
+
return (0, import_node_fs3.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2447
|
+
}
|
|
2448
|
+
async function replaceProfileDirectory(targetUserDataDir, replacementUserDataDir) {
|
|
2449
|
+
if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
2450
|
+
await (0, import_promises4.rename)(replacementUserDataDir, targetUserDataDir);
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
|
|
2454
|
+
let targetMovedToBackup = false;
|
|
2455
|
+
let replacementPublished = false;
|
|
2456
|
+
try {
|
|
2457
|
+
await (0, import_promises4.rename)(targetUserDataDir, backupUserDataDir);
|
|
2458
|
+
targetMovedToBackup = true;
|
|
2459
|
+
await (0, import_promises4.rename)(replacementUserDataDir, targetUserDataDir);
|
|
2460
|
+
replacementPublished = true;
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
if (targetMovedToBackup && !(0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
2463
|
+
await (0, import_promises4.rename)(backupUserDataDir, targetUserDataDir).catch(() => void 0);
|
|
2464
|
+
}
|
|
2465
|
+
throw error;
|
|
2466
|
+
} finally {
|
|
2467
|
+
if (replacementPublished && targetMovedToBackup && (0, import_node_fs3.existsSync)(backupUserDataDir)) {
|
|
2468
|
+
await (0, import_promises4.rm)(backupUserDataDir, {
|
|
2469
|
+
recursive: true,
|
|
2470
|
+
force: true
|
|
2471
|
+
}).catch(() => void 0);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
async function withPersistentProfileWriteAccess(targetUserDataDir, action) {
|
|
2476
|
+
const releaseWriteLock = await acquirePersistentProfileWriteLock(
|
|
2477
|
+
targetUserDataDir
|
|
2478
|
+
);
|
|
2479
|
+
try {
|
|
2480
|
+
await waitForRuntimeProfileCreationsToDrain(targetUserDataDir);
|
|
2481
|
+
await waitForSharedRealBrowserSessionToDrain(targetUserDataDir);
|
|
2482
|
+
return await action();
|
|
2483
|
+
} finally {
|
|
2484
|
+
await releaseWriteLock();
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
|
|
2488
|
+
return (0, import_node_path4.join)(
|
|
2489
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
2490
|
+
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-`
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
|
|
2494
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-tmp-`;
|
|
2495
|
+
}
|
|
2496
|
+
function buildPersistentProfileBackupDirPath(targetUserDataDir) {
|
|
2497
|
+
return (0, import_node_path4.join)(
|
|
2498
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
2499
|
+
`${buildPersistentProfileBackupDirNamePrefix(targetUserDataDir)}${Date.now()}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto3.randomUUID)()}`
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
function buildPersistentProfileBackupDirNamePrefix(targetUserDataDir) {
|
|
2503
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-backup-`;
|
|
2504
|
+
}
|
|
2505
|
+
function buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir) {
|
|
2506
|
+
return (0, import_node_path4.join)(
|
|
2507
|
+
(0, import_node_path4.dirname)(persistentUserDataDir),
|
|
2508
|
+
`${(0, import_node_path4.basename)(persistentUserDataDir)}.creating`
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
function buildRuntimeProfileCreationRegistrationPath(persistentUserDataDir, runtimeUserDataDir) {
|
|
2512
|
+
const key = (0, import_node_crypto3.createHash)("sha256").update(runtimeUserDataDir).digest("hex").slice(0, 16);
|
|
2513
|
+
return (0, import_node_path4.join)(
|
|
2514
|
+
buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir),
|
|
2515
|
+
`${key}.json`
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
function buildRuntimeProfileKey(sourceUserDataDir) {
|
|
2519
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
|
|
2520
|
+
return `${sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "profile")}-${hash}`;
|
|
2521
|
+
}
|
|
2522
|
+
function buildRuntimeProfileDirNamePrefix(sourceUserDataDir) {
|
|
2523
|
+
return `${buildRuntimeProfileKey(sourceUserDataDir)}-runtime-`;
|
|
2524
|
+
}
|
|
2525
|
+
function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
|
|
2526
|
+
return (0, import_node_path4.join)(
|
|
2527
|
+
runtimesRootDir,
|
|
2528
|
+
buildRuntimeProfileDirNamePrefix(sourceUserDataDir)
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
async function reserveRuntimeProfileCreation(persistentUserDataDir, runtimeRootDir, profileDirectory) {
|
|
2532
|
+
while (true) {
|
|
2533
|
+
let runtimeUserDataDir = null;
|
|
2534
|
+
await withPersistentProfileControlLock(
|
|
2535
|
+
persistentUserDataDir,
|
|
2536
|
+
async () => {
|
|
2537
|
+
if (await isPersistentProfileWriteLocked(persistentUserDataDir)) {
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
if (await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
const createdRuntimeUserDataDir = await (0, import_promises4.mkdtemp)(
|
|
2544
|
+
buildRuntimeProfileDirPrefix(
|
|
2545
|
+
runtimeRootDir,
|
|
2546
|
+
persistentUserDataDir
|
|
2547
|
+
)
|
|
2548
|
+
);
|
|
2549
|
+
runtimeUserDataDir = createdRuntimeUserDataDir;
|
|
2550
|
+
const marker = {
|
|
2551
|
+
creator: CURRENT_PROCESS_OWNER,
|
|
2552
|
+
persistentUserDataDir,
|
|
2553
|
+
profileDirectory,
|
|
2554
|
+
runtimeUserDataDir: createdRuntimeUserDataDir
|
|
2555
|
+
};
|
|
2556
|
+
await writeRuntimeProfileCreationMarker(
|
|
2557
|
+
createdRuntimeUserDataDir,
|
|
2558
|
+
marker
|
|
2559
|
+
);
|
|
2560
|
+
await writeRuntimeProfileCreationRegistration(marker);
|
|
2561
|
+
}
|
|
2562
|
+
);
|
|
2563
|
+
if (runtimeUserDataDir) {
|
|
2564
|
+
return {
|
|
2565
|
+
persistentUserDataDir,
|
|
2566
|
+
userDataDir: runtimeUserDataDir
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
async function clearRuntimeProfileCreationState(runtimeUserDataDir, persistentUserDataDir) {
|
|
2573
|
+
await Promise.all([
|
|
2574
|
+
(0, import_promises4.rm)((0, import_node_path4.join)(runtimeUserDataDir, OPENSTEER_RUNTIME_CREATING_FILE), {
|
|
2575
|
+
force: true
|
|
2576
|
+
}).catch(() => void 0),
|
|
2577
|
+
(0, import_promises4.rm)(
|
|
2578
|
+
buildRuntimeProfileCreationRegistrationPath(
|
|
2579
|
+
persistentUserDataDir,
|
|
2580
|
+
runtimeUserDataDir
|
|
2581
|
+
),
|
|
2582
|
+
{
|
|
2583
|
+
force: true
|
|
2584
|
+
}
|
|
2585
|
+
).catch(() => void 0)
|
|
2586
|
+
]);
|
|
2587
|
+
}
|
|
2588
|
+
async function writeRuntimeProfileCreationRegistration(marker) {
|
|
2589
|
+
const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
|
|
2590
|
+
marker.persistentUserDataDir
|
|
2591
|
+
);
|
|
2592
|
+
await (0, import_promises4.mkdir)(registryDirPath, { recursive: true });
|
|
2593
|
+
await (0, import_promises4.writeFile)(
|
|
2594
|
+
buildRuntimeProfileCreationRegistrationPath(
|
|
2595
|
+
marker.persistentUserDataDir,
|
|
2596
|
+
marker.runtimeUserDataDir
|
|
2597
|
+
),
|
|
2598
|
+
JSON.stringify(marker, null, 2)
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
async function listRuntimeProfileCreationRegistrations(persistentUserDataDir) {
|
|
2602
|
+
const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
|
|
2603
|
+
persistentUserDataDir
|
|
2604
|
+
);
|
|
2605
|
+
let entries;
|
|
2606
|
+
try {
|
|
2607
|
+
entries = await (0, import_promises4.readdir)(registryDirPath, {
|
|
2608
|
+
encoding: "utf8",
|
|
2609
|
+
withFileTypes: true
|
|
2610
|
+
});
|
|
2611
|
+
} catch {
|
|
2612
|
+
return [];
|
|
2613
|
+
}
|
|
2614
|
+
return await Promise.all(
|
|
2615
|
+
entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
2616
|
+
const filePath = (0, import_node_path4.join)(registryDirPath, entry.name);
|
|
2617
|
+
return {
|
|
2618
|
+
filePath,
|
|
2619
|
+
marker: await readRuntimeProfileCreationRegistration(filePath)
|
|
2620
|
+
};
|
|
2621
|
+
})
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
async function readRuntimeProfileCreationRegistration(filePath) {
|
|
2625
|
+
try {
|
|
2626
|
+
const raw = await (0, import_promises4.readFile)(filePath, "utf8");
|
|
2627
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
2628
|
+
} catch {
|
|
2629
|
+
return null;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
async function cleanOrphanedRuntimeProfileDirs(rootDir, runtimeDirNamePrefix) {
|
|
2633
|
+
let entries;
|
|
2634
|
+
try {
|
|
2635
|
+
entries = await (0, import_promises4.readdir)(rootDir, {
|
|
2636
|
+
encoding: "utf8",
|
|
2637
|
+
withFileTypes: true
|
|
2638
|
+
});
|
|
2639
|
+
} catch {
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
const liveProcessCommandLines = await listProcessCommandLines();
|
|
2643
|
+
await Promise.all(
|
|
2644
|
+
entries.map(async (entry) => {
|
|
2645
|
+
if (!entry.isDirectory() || !entry.name.startsWith(runtimeDirNamePrefix)) {
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const runtimeDirPath = (0, import_node_path4.join)(rootDir, entry.name);
|
|
2649
|
+
const creationMarker = await readRuntimeProfileCreationMarker(
|
|
2650
|
+
runtimeDirPath
|
|
2651
|
+
);
|
|
2652
|
+
if (await isRuntimeProfileDirInUse(
|
|
2653
|
+
runtimeDirPath,
|
|
2654
|
+
liveProcessCommandLines
|
|
2655
|
+
)) {
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
await (0, import_promises4.rm)(runtimeDirPath, {
|
|
2659
|
+
recursive: true,
|
|
2660
|
+
force: true
|
|
2661
|
+
}).catch(() => void 0);
|
|
2662
|
+
if (creationMarker) {
|
|
2663
|
+
await clearRuntimeProfileCreationState(
|
|
2664
|
+
runtimeDirPath,
|
|
2665
|
+
creationMarker.persistentUserDataDir
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
})
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
|
|
2672
|
+
let entries;
|
|
2673
|
+
try {
|
|
2674
|
+
entries = await (0, import_promises4.readdir)(rootDir, {
|
|
2675
|
+
encoding: "utf8",
|
|
2676
|
+
withFileTypes: true
|
|
2677
|
+
});
|
|
2678
|
+
} catch {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
await Promise.all(
|
|
2682
|
+
entries.map(async (entry) => {
|
|
2683
|
+
if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
await (0, import_promises4.rm)((0, import_node_path4.join)(rootDir, entry.name), {
|
|
2690
|
+
recursive: true,
|
|
2691
|
+
force: true
|
|
2692
|
+
}).catch(() => void 0);
|
|
2693
|
+
})
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
|
|
2697
|
+
const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
|
|
2698
|
+
return owner ? await getProcessLiveness(owner) !== "dead" : false;
|
|
2699
|
+
}
|
|
2700
|
+
async function isRuntimeProfileDirInUse(runtimeDirPath, liveProcessCommandLines) {
|
|
2701
|
+
const creationMarker = await readRuntimeProfileCreationMarker(runtimeDirPath);
|
|
2702
|
+
if (creationMarker && await getProcessLiveness(creationMarker.creator) !== "dead") {
|
|
2703
|
+
return true;
|
|
2704
|
+
}
|
|
2705
|
+
const metadata = await readRuntimeProfileMetadata(runtimeDirPath);
|
|
2706
|
+
if (metadata && await getProcessLiveness(metadata.creator) !== "dead") {
|
|
2707
|
+
return true;
|
|
2708
|
+
}
|
|
2709
|
+
return liveProcessCommandLines.some(
|
|
2710
|
+
(commandLine) => commandLineIncludesUserDataDir(commandLine, runtimeDirPath)
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
async function claimRuntimeProfileForPersist(runtimeUserDataDir) {
|
|
2714
|
+
while (true) {
|
|
2715
|
+
await waitForRuntimeProfileProcessesToDrain(runtimeUserDataDir);
|
|
2716
|
+
const claimedRuntimeUserDataDir = buildClaimedRuntimeProfileDirPath(runtimeUserDataDir);
|
|
2717
|
+
try {
|
|
2718
|
+
await (0, import_promises4.rename)(runtimeUserDataDir, claimedRuntimeUserDataDir);
|
|
2719
|
+
} catch (error) {
|
|
2720
|
+
const code = getErrorCode3(error);
|
|
2721
|
+
if (code === "ENOENT") {
|
|
2722
|
+
throw new Error(
|
|
2723
|
+
`Runtime profile "${runtimeUserDataDir}" was not found.`
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
if (code === "EACCES" || code === "EBUSY" || code === "EPERM") {
|
|
2727
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
throw error;
|
|
2731
|
+
}
|
|
2732
|
+
if (!await hasLiveProcessUsingUserDataDir(runtimeUserDataDir)) {
|
|
2733
|
+
return claimedRuntimeUserDataDir;
|
|
2734
|
+
}
|
|
2735
|
+
await (0, import_promises4.rename)(
|
|
2736
|
+
claimedRuntimeUserDataDir,
|
|
2737
|
+
runtimeUserDataDir
|
|
2738
|
+
).catch(() => void 0);
|
|
2739
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
async function restoreClaimedRuntimeProfile(claimedRuntimeUserDataDir, runtimeUserDataDir) {
|
|
2743
|
+
if (!(0, import_node_fs3.existsSync)(claimedRuntimeUserDataDir)) {
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
if ((0, import_node_fs3.existsSync)(runtimeUserDataDir)) {
|
|
2747
|
+
throw new Error(
|
|
2748
|
+
`Runtime profile "${runtimeUserDataDir}" was recreated before the failed persist could restore it from "${claimedRuntimeUserDataDir}".`
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2751
|
+
await (0, import_promises4.rename)(claimedRuntimeUserDataDir, runtimeUserDataDir);
|
|
2752
|
+
}
|
|
2753
|
+
function buildClaimedRuntimeProfileDirPath(runtimeUserDataDir) {
|
|
2754
|
+
return (0, import_node_path4.join)(
|
|
2755
|
+
(0, import_node_path4.dirname)(runtimeUserDataDir),
|
|
2756
|
+
`${(0, import_node_path4.basename)(runtimeUserDataDir)}-persisting-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto3.randomUUID)()}`
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
async function waitForRuntimeProfileProcessesToDrain(runtimeUserDataDir) {
|
|
2760
|
+
while (await hasLiveProcessUsingUserDataDir(runtimeUserDataDir)) {
|
|
2761
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
async function hasLiveProcessUsingUserDataDir(userDataDir) {
|
|
2765
|
+
const liveProcessCommandLines = await listProcessCommandLines();
|
|
2766
|
+
return liveProcessCommandLines.some(
|
|
2767
|
+
(commandLine) => commandLineIncludesUserDataDir(commandLine, userDataDir)
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
async function hasActiveRuntimeProfileCreations(persistentUserDataDir) {
|
|
2771
|
+
const registrations = await listRuntimeProfileCreationRegistrations(
|
|
2772
|
+
persistentUserDataDir
|
|
2773
|
+
);
|
|
2774
|
+
let hasLiveCreation = false;
|
|
2775
|
+
for (const registration of registrations) {
|
|
2776
|
+
const marker = registration.marker;
|
|
2777
|
+
if (!marker || marker.persistentUserDataDir !== persistentUserDataDir) {
|
|
2778
|
+
await (0, import_promises4.rm)(registration.filePath, {
|
|
2779
|
+
force: true
|
|
2780
|
+
}).catch(() => void 0);
|
|
2781
|
+
continue;
|
|
2782
|
+
}
|
|
2783
|
+
const runtimeMarker = await readRuntimeProfileCreationMarker(
|
|
2784
|
+
marker.runtimeUserDataDir
|
|
2785
|
+
);
|
|
2786
|
+
if (!runtimeMarker || runtimeMarker.persistentUserDataDir !== persistentUserDataDir || runtimeMarker.runtimeUserDataDir !== marker.runtimeUserDataDir) {
|
|
2787
|
+
await clearRuntimeProfileCreationState(
|
|
2788
|
+
marker.runtimeUserDataDir,
|
|
2789
|
+
persistentUserDataDir
|
|
2790
|
+
);
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
if (await getProcessLiveness(runtimeMarker.creator) === "dead") {
|
|
2794
|
+
await clearRuntimeProfileCreationState(
|
|
2795
|
+
marker.runtimeUserDataDir,
|
|
2796
|
+
persistentUserDataDir
|
|
2797
|
+
);
|
|
2798
|
+
await (0, import_promises4.rm)(marker.runtimeUserDataDir, {
|
|
2799
|
+
recursive: true,
|
|
2800
|
+
force: true
|
|
2801
|
+
}).catch(() => void 0);
|
|
2802
|
+
continue;
|
|
2803
|
+
}
|
|
2804
|
+
hasLiveCreation = true;
|
|
2805
|
+
}
|
|
2806
|
+
return hasLiveCreation;
|
|
2807
|
+
}
|
|
2808
|
+
async function waitForRuntimeProfileCreationsToDrain(persistentUserDataDir) {
|
|
2809
|
+
while (true) {
|
|
2810
|
+
if (!await hasActiveRuntimeProfileCreations(persistentUserDataDir)) {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
function parseRuntimeProfileCreationMarker(value) {
|
|
2817
|
+
if (!value || typeof value !== "object") {
|
|
2818
|
+
return null;
|
|
2819
|
+
}
|
|
2820
|
+
const parsed = value;
|
|
2821
|
+
const creator = parseProcessOwner(parsed.creator);
|
|
2822
|
+
const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
|
|
2823
|
+
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
2824
|
+
const runtimeUserDataDir = typeof parsed.runtimeUserDataDir === "string" ? parsed.runtimeUserDataDir : void 0;
|
|
2825
|
+
if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || runtimeUserDataDir === void 0) {
|
|
2826
|
+
return null;
|
|
2827
|
+
}
|
|
2828
|
+
return {
|
|
2829
|
+
creator,
|
|
2830
|
+
persistentUserDataDir,
|
|
2831
|
+
profileDirectory,
|
|
2832
|
+
runtimeUserDataDir
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
|
|
2836
|
+
const remainder = ownedDirName.slice(ownedDirPrefix.length);
|
|
2837
|
+
const firstDashIndex = remainder.indexOf("-");
|
|
2838
|
+
const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
|
|
2839
|
+
if (firstDashIndex === -1 || secondDashIndex === -1) {
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
|
|
2843
|
+
const processStartedAtMs = Number.parseInt(
|
|
2844
|
+
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
2845
|
+
10
|
|
2846
|
+
);
|
|
2847
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2850
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
2851
|
+
return null;
|
|
2852
|
+
}
|
|
2853
|
+
return { pid, processStartedAtMs };
|
|
2854
|
+
}
|
|
2855
|
+
async function listProcessCommandLines() {
|
|
2856
|
+
if (process.platform === "win32") {
|
|
2857
|
+
return await listWindowsProcessCommandLines();
|
|
2858
|
+
}
|
|
2859
|
+
return await listPsProcessCommandLines();
|
|
2860
|
+
}
|
|
2861
|
+
async function listPsProcessCommandLines() {
|
|
2862
|
+
try {
|
|
2863
|
+
const { stdout } = await execFileAsync2(
|
|
2864
|
+
"ps",
|
|
2865
|
+
["-axww", "-o", "command="],
|
|
2866
|
+
{
|
|
2867
|
+
env: PS_COMMAND_ENV2,
|
|
2868
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
|
|
2869
|
+
}
|
|
2870
|
+
);
|
|
2871
|
+
return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2872
|
+
} catch {
|
|
2873
|
+
return [];
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
async function listWindowsProcessCommandLines() {
|
|
2877
|
+
const script = [
|
|
2878
|
+
"$processes = Get-CimInstance Win32_Process | Select-Object CommandLine",
|
|
2879
|
+
"$processes | ConvertTo-Json -Compress"
|
|
2880
|
+
].join("; ");
|
|
2881
|
+
try {
|
|
2882
|
+
const { stdout } = await execFileAsync2(
|
|
2883
|
+
"powershell.exe",
|
|
2884
|
+
["-NoLogo", "-NoProfile", "-Command", script],
|
|
2885
|
+
{
|
|
2886
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
|
|
2887
|
+
}
|
|
2888
|
+
);
|
|
2889
|
+
const parsed = JSON.parse(stdout);
|
|
2890
|
+
const records = Array.isArray(parsed) ? parsed : [parsed];
|
|
2891
|
+
return records.map((record) => record?.CommandLine?.trim() ?? "").filter((commandLine) => commandLine.length > 0);
|
|
2892
|
+
} catch {
|
|
2893
|
+
return [];
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
function commandLineIncludesUserDataDir(commandLine, userDataDir) {
|
|
2897
|
+
const unquoted = `--user-data-dir=${userDataDir}`;
|
|
2898
|
+
const unquotedIndex = commandLine.indexOf(unquoted);
|
|
2899
|
+
if (unquotedIndex !== -1) {
|
|
2900
|
+
const after = commandLine[unquotedIndex + unquoted.length];
|
|
2901
|
+
if (after === void 0 || after === " " || after === " ") {
|
|
2902
|
+
return true;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
return [
|
|
2906
|
+
`--user-data-dir="${userDataDir}"`,
|
|
2907
|
+
`--user-data-dir='${userDataDir}'`
|
|
2908
|
+
].some((candidate) => commandLine.includes(candidate));
|
|
2909
|
+
}
|
|
2910
|
+
function getErrorCode3(error) {
|
|
2911
|
+
return typeof error === "object" && error !== null && "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? error.code : void 0;
|
|
2912
|
+
}
|
|
2913
|
+
async function sleep4(ms) {
|
|
2914
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// src/browser/shared-real-browser-session.ts
|
|
2918
|
+
var import_node_crypto4 = require("crypto");
|
|
2919
|
+
var import_node_child_process3 = require("child_process");
|
|
2920
|
+
var import_promises5 = require("fs/promises");
|
|
2921
|
+
var import_node_net = require("net");
|
|
2922
|
+
var import_node_path5 = require("path");
|
|
2923
|
+
var import_playwright = require("playwright");
|
|
2924
|
+
var SHARED_SESSION_RETRY_DELAY_MS2 = 50;
|
|
2925
|
+
async function acquireSharedRealBrowserSession(options) {
|
|
2926
|
+
const reservation = await reserveSharedSessionClient(options);
|
|
2927
|
+
const sessionContext = await attachToSharedSession(reservation, options);
|
|
2928
|
+
let closed = false;
|
|
2929
|
+
return {
|
|
2930
|
+
browser: sessionContext.browser,
|
|
2931
|
+
context: sessionContext.context,
|
|
2932
|
+
page: sessionContext.page,
|
|
2933
|
+
close: async () => {
|
|
2934
|
+
if (closed) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
closed = true;
|
|
2938
|
+
await releaseSharedSessionClient(sessionContext);
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
|
|
2943
|
+
if (platformName === "win32") {
|
|
2944
|
+
return {
|
|
2945
|
+
detached: false,
|
|
2946
|
+
killStrategy: "taskkill",
|
|
2947
|
+
shouldUnref: true
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
if (platformName === "darwin") {
|
|
2951
|
+
return {
|
|
2952
|
+
detached: false,
|
|
2953
|
+
killStrategy: "process",
|
|
2954
|
+
shouldUnref: true
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
return {
|
|
2958
|
+
detached: true,
|
|
2959
|
+
killStrategy: "process-group",
|
|
2960
|
+
shouldUnref: true
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
async function reserveSharedSessionClient(options) {
|
|
2964
|
+
while (true) {
|
|
2965
|
+
const outcome = await withPersistentProfileControlLock(
|
|
2966
|
+
options.persistentProfile.userDataDir,
|
|
2967
|
+
async () => {
|
|
2968
|
+
if (await isPersistentProfileWriteLocked(
|
|
2969
|
+
options.persistentProfile.userDataDir
|
|
2970
|
+
)) {
|
|
2971
|
+
return { kind: "wait" };
|
|
2972
|
+
}
|
|
2973
|
+
if (await hasActiveRuntimeProfileCreations(
|
|
2974
|
+
options.persistentProfile.userDataDir
|
|
2975
|
+
)) {
|
|
2976
|
+
return { kind: "wait" };
|
|
2977
|
+
}
|
|
2978
|
+
return await withSharedSessionLock(
|
|
2979
|
+
options.persistentProfile.userDataDir,
|
|
2980
|
+
async () => {
|
|
2981
|
+
const state = await inspectSharedSessionState(options);
|
|
2982
|
+
if (state.kind === "wait") {
|
|
2983
|
+
return { kind: "wait" };
|
|
2984
|
+
}
|
|
2985
|
+
if (state.kind === "ready") {
|
|
2986
|
+
return {
|
|
2987
|
+
kind: "ready",
|
|
2988
|
+
reservation: await registerSharedSessionClient(
|
|
2989
|
+
options.persistentProfile.userDataDir,
|
|
2990
|
+
state.metadata
|
|
2991
|
+
)
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
return {
|
|
2995
|
+
kind: "launch",
|
|
2996
|
+
reservation: await launchSharedSession(options)
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
3001
|
+
);
|
|
3002
|
+
if (outcome.kind === "wait") {
|
|
3003
|
+
await sleep5(SHARED_SESSION_RETRY_DELAY_MS2);
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
if (outcome.kind === "ready") {
|
|
3007
|
+
return outcome.reservation;
|
|
3008
|
+
}
|
|
3009
|
+
try {
|
|
3010
|
+
await waitForSharedSessionReady(
|
|
3011
|
+
outcome.reservation.metadata,
|
|
3012
|
+
options.timeoutMs
|
|
3013
|
+
);
|
|
3014
|
+
} catch (error) {
|
|
3015
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
3016
|
+
throw error;
|
|
3017
|
+
}
|
|
3018
|
+
try {
|
|
3019
|
+
return await withSharedSessionLock(
|
|
3020
|
+
options.persistentProfile.userDataDir,
|
|
3021
|
+
async () => {
|
|
3022
|
+
const metadata = await readSharedSessionMetadata(
|
|
3023
|
+
options.persistentProfile.userDataDir
|
|
3024
|
+
);
|
|
3025
|
+
if (!metadata || metadata.sessionId !== outcome.reservation.metadata.sessionId || !processOwnersEqual(
|
|
3026
|
+
metadata.browserOwner,
|
|
3027
|
+
outcome.reservation.launchedBrowserOwner
|
|
3028
|
+
)) {
|
|
3029
|
+
throw new Error(
|
|
3030
|
+
"The shared real-browser session changed before launch finalized."
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
const readyMetadata = {
|
|
3034
|
+
...metadata,
|
|
3035
|
+
state: "ready"
|
|
3036
|
+
};
|
|
3037
|
+
await writeSharedSessionMetadata(
|
|
3038
|
+
options.persistentProfile.userDataDir,
|
|
3039
|
+
readyMetadata
|
|
3040
|
+
);
|
|
3041
|
+
return await registerSharedSessionClient(
|
|
3042
|
+
options.persistentProfile.userDataDir,
|
|
3043
|
+
readyMetadata
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
);
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
3049
|
+
throw error;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
async function attachToSharedSession(reservation, options) {
|
|
3054
|
+
let browser = null;
|
|
3055
|
+
let page = null;
|
|
3056
|
+
try {
|
|
3057
|
+
const browserWsUrl = await resolveCdpWebSocketUrl(
|
|
3058
|
+
buildSharedSessionDiscoveryUrl(reservation.metadata.debugPort),
|
|
3059
|
+
options.timeoutMs
|
|
3060
|
+
);
|
|
3061
|
+
browser = await import_playwright.chromium.connectOverCDP(browserWsUrl, {
|
|
3062
|
+
timeout: options.timeoutMs
|
|
3063
|
+
});
|
|
3064
|
+
const context = getPrimaryBrowserContext(browser);
|
|
3065
|
+
page = await getSharedSessionPage(context, reservation.reuseExistingPage);
|
|
3066
|
+
if (options.initialUrl) {
|
|
3067
|
+
await page.goto(options.initialUrl, {
|
|
3068
|
+
timeout: options.timeoutMs,
|
|
3069
|
+
waitUntil: "domcontentloaded"
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
return {
|
|
3073
|
+
browser,
|
|
3074
|
+
clientId: reservation.client.clientId,
|
|
3075
|
+
context,
|
|
3076
|
+
page,
|
|
3077
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
3078
|
+
sessionId: reservation.metadata.sessionId
|
|
3079
|
+
};
|
|
3080
|
+
} catch (error) {
|
|
3081
|
+
if (page) {
|
|
3082
|
+
await page.close().catch(() => void 0);
|
|
3083
|
+
}
|
|
3084
|
+
if (browser) {
|
|
3085
|
+
await browser.close().catch(() => void 0);
|
|
3086
|
+
}
|
|
3087
|
+
await cleanupFailedSharedSessionAttach({
|
|
3088
|
+
clientId: reservation.client.clientId,
|
|
3089
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
3090
|
+
sessionId: reservation.metadata.sessionId
|
|
3091
|
+
});
|
|
3092
|
+
throw error;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
async function releaseSharedSessionClient(context) {
|
|
3096
|
+
const releasePlan = await prepareSharedSessionCloseIfIdle(
|
|
3097
|
+
context.persistentUserDataDir,
|
|
3098
|
+
context.clientId,
|
|
3099
|
+
context.sessionId
|
|
3100
|
+
);
|
|
3101
|
+
if (releasePlan.closeBrowser) {
|
|
3102
|
+
await closeSharedSessionBrowser(
|
|
3103
|
+
context.persistentUserDataDir,
|
|
3104
|
+
releasePlan,
|
|
3105
|
+
context.browser
|
|
3106
|
+
);
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
await context.page.close().catch(() => void 0);
|
|
3110
|
+
await context.browser.close().catch(() => void 0);
|
|
3111
|
+
}
|
|
3112
|
+
async function inspectSharedSessionState(options) {
|
|
3113
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
3114
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
3115
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3116
|
+
if (!metadata) {
|
|
3117
|
+
if (liveClients.length > 0) {
|
|
3118
|
+
throw new Error(
|
|
3119
|
+
`Shared real-browser session metadata for "${persistentUserDataDir}" is missing while clients are still attached.`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3123
|
+
force: true,
|
|
3124
|
+
recursive: true
|
|
3125
|
+
}).catch(() => void 0);
|
|
3126
|
+
return { kind: "missing" };
|
|
3127
|
+
}
|
|
3128
|
+
assertSharedSessionCompatibility(metadata, options);
|
|
3129
|
+
const browserState = await getProcessLiveness(metadata.browserOwner);
|
|
3130
|
+
if (browserState === "dead") {
|
|
3131
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3132
|
+
force: true,
|
|
3133
|
+
recursive: true
|
|
3134
|
+
}).catch(() => void 0);
|
|
3135
|
+
return { kind: "missing" };
|
|
3136
|
+
}
|
|
3137
|
+
if (metadata.state === "ready") {
|
|
3138
|
+
return {
|
|
3139
|
+
kind: "ready",
|
|
3140
|
+
metadata
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
const stateOwnerState = await getProcessLiveness(metadata.stateOwner);
|
|
3144
|
+
if (stateOwnerState === "dead") {
|
|
3145
|
+
const recoveredMetadata = {
|
|
3146
|
+
...metadata,
|
|
3147
|
+
state: "ready"
|
|
3148
|
+
};
|
|
3149
|
+
await writeSharedSessionMetadata(persistentUserDataDir, recoveredMetadata);
|
|
3150
|
+
return {
|
|
3151
|
+
kind: "ready",
|
|
3152
|
+
metadata: recoveredMetadata
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
return { kind: "wait" };
|
|
3156
|
+
}
|
|
3157
|
+
async function launchSharedSession(options) {
|
|
3158
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
3159
|
+
await clearPersistentProfileSingletons(persistentUserDataDir);
|
|
3160
|
+
const debugPort = await reserveDebugPort();
|
|
3161
|
+
const launchArgs = buildRealBrowserLaunchArgs({
|
|
3162
|
+
debugPort,
|
|
3163
|
+
headless: options.headless,
|
|
3164
|
+
profileDirectory: options.profileDirectory,
|
|
3165
|
+
userDataDir: persistentUserDataDir
|
|
3166
|
+
});
|
|
3167
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
3168
|
+
const processHandle = (0, import_node_child_process3.spawn)(options.executablePath, launchArgs, {
|
|
3169
|
+
detached: processPolicy.detached,
|
|
3170
|
+
stdio: "ignore"
|
|
3171
|
+
});
|
|
3172
|
+
if (processPolicy.shouldUnref) {
|
|
3173
|
+
processHandle.unref();
|
|
3174
|
+
}
|
|
3175
|
+
try {
|
|
3176
|
+
const browserOwner = await waitForSpawnedProcessOwner(
|
|
3177
|
+
processHandle.pid,
|
|
3178
|
+
options.timeoutMs
|
|
3179
|
+
);
|
|
3180
|
+
const metadata = {
|
|
3181
|
+
browserOwner,
|
|
3182
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3183
|
+
debugPort,
|
|
3184
|
+
executablePath: options.executablePath,
|
|
3185
|
+
headless: options.headless,
|
|
3186
|
+
persistentUserDataDir,
|
|
3187
|
+
profileDirectory: options.profileDirectory,
|
|
3188
|
+
sessionId: (0, import_node_crypto4.randomUUID)(),
|
|
3189
|
+
state: "launching",
|
|
3190
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
3191
|
+
};
|
|
3192
|
+
await writeSharedSessionMetadata(persistentUserDataDir, metadata);
|
|
3193
|
+
return {
|
|
3194
|
+
launchedBrowserOwner: browserOwner,
|
|
3195
|
+
metadata
|
|
3196
|
+
};
|
|
3197
|
+
} catch (error) {
|
|
3198
|
+
await killSpawnedBrowserProcess(processHandle);
|
|
3199
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3200
|
+
force: true,
|
|
3201
|
+
recursive: true
|
|
3202
|
+
}).catch(() => void 0);
|
|
3203
|
+
throw error;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
async function cleanupFailedSharedSessionLaunch(reservation) {
|
|
3207
|
+
const shouldPreserveLiveBrowser = await withSharedSessionLock(
|
|
3208
|
+
reservation.metadata.persistentUserDataDir,
|
|
3209
|
+
async () => {
|
|
3210
|
+
const metadata = await readSharedSessionMetadata(
|
|
3211
|
+
reservation.metadata.persistentUserDataDir
|
|
3212
|
+
);
|
|
3213
|
+
if (metadata && metadata.sessionId === reservation.metadata.sessionId && processOwnersEqual(
|
|
3214
|
+
metadata.browserOwner,
|
|
3215
|
+
reservation.launchedBrowserOwner
|
|
3216
|
+
)) {
|
|
3217
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
3218
|
+
const readyMetadata = {
|
|
3219
|
+
...metadata,
|
|
3220
|
+
state: "ready"
|
|
3221
|
+
};
|
|
3222
|
+
await writeSharedSessionMetadata(
|
|
3223
|
+
reservation.metadata.persistentUserDataDir,
|
|
3224
|
+
readyMetadata
|
|
3225
|
+
);
|
|
3226
|
+
return true;
|
|
3227
|
+
}
|
|
3228
|
+
await (0, import_promises5.rm)(
|
|
3229
|
+
buildSharedSessionDirPath(
|
|
3230
|
+
reservation.metadata.persistentUserDataDir
|
|
3231
|
+
),
|
|
3232
|
+
{
|
|
3233
|
+
force: true,
|
|
3234
|
+
recursive: true
|
|
3235
|
+
}
|
|
3236
|
+
).catch(() => void 0);
|
|
3237
|
+
}
|
|
3238
|
+
return false;
|
|
3239
|
+
}
|
|
3240
|
+
);
|
|
3241
|
+
if (shouldPreserveLiveBrowser) {
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
|
|
3245
|
+
await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
|
|
3246
|
+
}
|
|
3247
|
+
async function cleanupFailedSharedSessionAttach(options) {
|
|
3248
|
+
const closePlan = await prepareSharedSessionCloseIfIdle(
|
|
3249
|
+
options.persistentUserDataDir,
|
|
3250
|
+
options.clientId,
|
|
3251
|
+
options.sessionId
|
|
3252
|
+
);
|
|
3253
|
+
if (!closePlan.closeBrowser) {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
|
|
3257
|
+
}
|
|
3258
|
+
async function waitForSharedSessionReady(metadata, timeoutMs) {
|
|
3259
|
+
await resolveCdpWebSocketUrl(
|
|
3260
|
+
buildSharedSessionDiscoveryUrl(metadata.debugPort),
|
|
3261
|
+
timeoutMs
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
function buildRealBrowserLaunchArgs(options) {
|
|
3265
|
+
const args = [
|
|
3266
|
+
`--user-data-dir=${options.userDataDir}`,
|
|
3267
|
+
`--profile-directory=${options.profileDirectory}`,
|
|
3268
|
+
`--remote-debugging-port=${options.debugPort}`,
|
|
3269
|
+
"--disable-blink-features=AutomationControlled"
|
|
3270
|
+
];
|
|
3271
|
+
if (options.headless) {
|
|
3272
|
+
args.push("--headless=new");
|
|
3273
|
+
}
|
|
3274
|
+
return args;
|
|
3275
|
+
}
|
|
3276
|
+
async function requestBrowserShutdown(browser) {
|
|
3277
|
+
let session = null;
|
|
3278
|
+
try {
|
|
3279
|
+
session = await browser.newBrowserCDPSession();
|
|
3280
|
+
await session.send("Browser.close");
|
|
3281
|
+
} catch {
|
|
3282
|
+
} finally {
|
|
3283
|
+
await session?.detach().catch(() => void 0);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
async function killOwnedBrowserProcess(owner) {
|
|
3287
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
await killOwnedBrowserProcessByPid(owner.pid);
|
|
3291
|
+
}
|
|
3292
|
+
async function killSpawnedBrowserProcess(processHandle) {
|
|
3293
|
+
const pid = processHandle.pid;
|
|
3294
|
+
if (!pid || processHandle.exitCode !== null) {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
await killOwnedBrowserProcessByPid(pid);
|
|
3298
|
+
await waitForPidToExit(pid, 2e3);
|
|
3299
|
+
}
|
|
3300
|
+
async function killOwnedBrowserProcessByPid(pid) {
|
|
3301
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
3302
|
+
if (processPolicy.killStrategy === "taskkill") {
|
|
3303
|
+
await new Promise((resolve) => {
|
|
3304
|
+
const killer = (0, import_node_child_process3.spawn)(
|
|
3305
|
+
"taskkill",
|
|
3306
|
+
["/pid", String(pid), "/t", "/f"],
|
|
3307
|
+
{
|
|
3308
|
+
stdio: "ignore"
|
|
3309
|
+
}
|
|
3310
|
+
);
|
|
3311
|
+
killer.on("error", () => resolve());
|
|
3312
|
+
killer.on("exit", () => resolve());
|
|
3313
|
+
});
|
|
3314
|
+
return;
|
|
3315
|
+
}
|
|
3316
|
+
if (processPolicy.killStrategy === "process-group") {
|
|
3317
|
+
try {
|
|
3318
|
+
process.kill(-pid, "SIGKILL");
|
|
3319
|
+
return;
|
|
3320
|
+
} catch {
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
try {
|
|
3324
|
+
process.kill(pid, "SIGKILL");
|
|
3325
|
+
} catch {
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
async function waitForProcessToExit(owner, timeoutMs) {
|
|
3329
|
+
const deadline = Date.now() + timeoutMs;
|
|
3330
|
+
while (Date.now() < deadline) {
|
|
3331
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
await sleep5(50);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
async function waitForPidToExit(pid, timeoutMs) {
|
|
3338
|
+
const deadline = Date.now() + timeoutMs;
|
|
3339
|
+
while (Date.now() < deadline) {
|
|
3340
|
+
if (!isProcessRunning(pid)) {
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
await sleep5(50);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
async function waitForSpawnedProcessOwner(pid, timeoutMs) {
|
|
3347
|
+
if (!pid || pid <= 0) {
|
|
3348
|
+
throw new Error("Chrome did not expose a child process id.");
|
|
3349
|
+
}
|
|
3350
|
+
const deadline = Date.now() + timeoutMs;
|
|
3351
|
+
while (Date.now() < deadline) {
|
|
3352
|
+
const owner = await readProcessOwner(pid);
|
|
3353
|
+
if (owner) {
|
|
3354
|
+
return owner;
|
|
3355
|
+
}
|
|
3356
|
+
await sleep5(50);
|
|
3357
|
+
}
|
|
3358
|
+
throw new Error(
|
|
3359
|
+
`Chrome process ${pid} did not report a stable process start time.`
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3362
|
+
async function withSharedSessionLock(persistentUserDataDir, action) {
|
|
3363
|
+
return await withDirLock(
|
|
3364
|
+
buildSharedSessionLockPath(persistentUserDataDir),
|
|
3365
|
+
action
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
async function registerSharedSessionClient(persistentUserDataDir, metadata) {
|
|
3369
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
3370
|
+
const client = buildSharedSessionClientRegistration();
|
|
3371
|
+
await (0, import_promises5.mkdir)(buildSharedSessionClientsDirPath(persistentUserDataDir), {
|
|
3372
|
+
recursive: true
|
|
3373
|
+
});
|
|
3374
|
+
await (0, import_promises5.writeFile)(
|
|
3375
|
+
buildSharedSessionClientPath(persistentUserDataDir, client.clientId),
|
|
3376
|
+
JSON.stringify(client, null, 2),
|
|
3377
|
+
{
|
|
3378
|
+
flag: "wx"
|
|
3379
|
+
}
|
|
3380
|
+
);
|
|
3381
|
+
return {
|
|
3382
|
+
client,
|
|
3383
|
+
metadata,
|
|
3384
|
+
reuseExistingPage: liveClients.length === 0
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
async function removeSharedSessionClientRegistration(persistentUserDataDir, clientId) {
|
|
3388
|
+
await (0, import_promises5.rm)(buildSharedSessionClientPath(persistentUserDataDir, clientId), {
|
|
3389
|
+
force: true
|
|
3390
|
+
}).catch(() => void 0);
|
|
3391
|
+
}
|
|
3392
|
+
async function listLiveSharedSessionClients(persistentUserDataDir) {
|
|
3393
|
+
const clientsDirPath = buildSharedSessionClientsDirPath(persistentUserDataDir);
|
|
3394
|
+
let entries;
|
|
3395
|
+
try {
|
|
3396
|
+
entries = await (0, import_promises5.readdir)(clientsDirPath, {
|
|
3397
|
+
encoding: "utf8",
|
|
3398
|
+
withFileTypes: true
|
|
3399
|
+
});
|
|
3400
|
+
} catch {
|
|
3401
|
+
return [];
|
|
3402
|
+
}
|
|
3403
|
+
const liveClients = [];
|
|
3404
|
+
for (const entry of entries) {
|
|
3405
|
+
if (!entry.isFile()) {
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
const filePath = (0, import_node_path5.join)(clientsDirPath, entry.name);
|
|
3409
|
+
const registration = await readSharedSessionClientRegistration(filePath);
|
|
3410
|
+
if (!registration) {
|
|
3411
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
if (await getProcessLiveness(registration.owner) === "dead") {
|
|
3415
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
3416
|
+
continue;
|
|
3417
|
+
}
|
|
3418
|
+
liveClients.push(registration);
|
|
3419
|
+
}
|
|
3420
|
+
return liveClients;
|
|
3421
|
+
}
|
|
3422
|
+
async function readSharedSessionClientRegistration(filePath) {
|
|
3423
|
+
try {
|
|
3424
|
+
const raw = await (0, import_promises5.readFile)(filePath, "utf8");
|
|
3425
|
+
return parseSharedSessionClientRegistration(JSON.parse(raw));
|
|
3426
|
+
} catch {
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
function buildSharedSessionClientRegistration() {
|
|
3431
|
+
return {
|
|
3432
|
+
clientId: (0, import_node_crypto4.randomUUID)(),
|
|
3433
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3434
|
+
owner: CURRENT_PROCESS_OWNER
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
function parseSharedSessionClientRegistration(value) {
|
|
3438
|
+
if (!value || typeof value !== "object") {
|
|
3439
|
+
return null;
|
|
3440
|
+
}
|
|
3441
|
+
const parsed = value;
|
|
3442
|
+
const owner = parseProcessOwner(parsed.owner);
|
|
3443
|
+
if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
return {
|
|
3447
|
+
clientId: parsed.clientId,
|
|
3448
|
+
createdAt: parsed.createdAt,
|
|
3449
|
+
owner
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
function assertSharedSessionCompatibility(metadata, options) {
|
|
3453
|
+
if (metadata.executablePath !== options.executablePath) {
|
|
3454
|
+
throw new Error(
|
|
3455
|
+
`Chrome profile "${options.profileDirectory}" is already running with executable "${metadata.executablePath}", not "${options.executablePath}".`
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
if (metadata.headless !== options.headless) {
|
|
3459
|
+
throw new Error(
|
|
3460
|
+
`Chrome profile "${options.profileDirectory}" is already running with headless=${metadata.headless}, not ${options.headless}.`
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
async function prepareSharedSessionCloseIfIdle(persistentUserDataDir, clientId, sessionId) {
|
|
3465
|
+
return await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
3466
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3467
|
+
await removeSharedSessionClientRegistration(
|
|
3468
|
+
persistentUserDataDir,
|
|
3469
|
+
clientId
|
|
3470
|
+
);
|
|
3471
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
3472
|
+
return {
|
|
3473
|
+
closeBrowser: false,
|
|
3474
|
+
sessionId
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
3478
|
+
persistentUserDataDir
|
|
3479
|
+
);
|
|
3480
|
+
if (liveClients.length > 0) {
|
|
3481
|
+
return {
|
|
3482
|
+
closeBrowser: false,
|
|
3483
|
+
sessionId: metadata.sessionId
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3486
|
+
const closingMetadata = {
|
|
3487
|
+
...metadata,
|
|
3488
|
+
state: "closing",
|
|
3489
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
3490
|
+
};
|
|
3491
|
+
await writeSharedSessionMetadata(
|
|
3492
|
+
persistentUserDataDir,
|
|
3493
|
+
closingMetadata
|
|
3494
|
+
);
|
|
3495
|
+
return {
|
|
3496
|
+
browserOwner: closingMetadata.browserOwner,
|
|
3497
|
+
closeBrowser: true,
|
|
3498
|
+
sessionId: closingMetadata.sessionId
|
|
3499
|
+
};
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
async function closeSharedSessionBrowser(persistentUserDataDir, closePlan, browser) {
|
|
3503
|
+
if (browser) {
|
|
3504
|
+
await requestBrowserShutdown(browser);
|
|
3505
|
+
await waitForProcessToExit(closePlan.browserOwner, 1e3);
|
|
3506
|
+
}
|
|
3507
|
+
if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
|
|
3508
|
+
await killOwnedBrowserProcess(closePlan.browserOwner);
|
|
3509
|
+
await waitForProcessToExit(closePlan.browserOwner, 2e3);
|
|
3510
|
+
}
|
|
3511
|
+
await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
|
|
3512
|
+
}
|
|
3513
|
+
async function finalizeSharedSessionClose(persistentUserDataDir, sessionId) {
|
|
3514
|
+
await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
3515
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3516
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
3520
|
+
persistentUserDataDir
|
|
3521
|
+
);
|
|
3522
|
+
if (liveClients.length > 0) {
|
|
3523
|
+
const readyMetadata = {
|
|
3524
|
+
...metadata,
|
|
3525
|
+
state: "ready"
|
|
3526
|
+
};
|
|
3527
|
+
await writeSharedSessionMetadata(
|
|
3528
|
+
persistentUserDataDir,
|
|
3529
|
+
readyMetadata
|
|
3530
|
+
);
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
3534
|
+
const readyMetadata = {
|
|
3535
|
+
...metadata,
|
|
3536
|
+
state: "ready"
|
|
3537
|
+
};
|
|
3538
|
+
await writeSharedSessionMetadata(
|
|
3539
|
+
persistentUserDataDir,
|
|
3540
|
+
readyMetadata
|
|
3541
|
+
);
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3545
|
+
force: true,
|
|
3546
|
+
recursive: true
|
|
3547
|
+
}).catch(() => void 0);
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
function getPrimaryBrowserContext(browser) {
|
|
3551
|
+
const contexts = browser.contexts();
|
|
3552
|
+
if (contexts.length === 0) {
|
|
3553
|
+
throw new Error(
|
|
3554
|
+
"Connection succeeded but no browser contexts were exposed."
|
|
3555
|
+
);
|
|
3556
|
+
}
|
|
3557
|
+
return contexts[0];
|
|
3558
|
+
}
|
|
3559
|
+
async function getSharedSessionPage(context, reuseExistingPage) {
|
|
3560
|
+
if (reuseExistingPage) {
|
|
3561
|
+
return await getExistingPageOrCreate(context);
|
|
3562
|
+
}
|
|
3563
|
+
return await context.newPage();
|
|
3564
|
+
}
|
|
3565
|
+
async function getExistingPageOrCreate(context) {
|
|
3566
|
+
const existingPage = context.pages()[0];
|
|
3567
|
+
if (existingPage) {
|
|
3568
|
+
return existingPage;
|
|
3569
|
+
}
|
|
3570
|
+
return await context.newPage();
|
|
3571
|
+
}
|
|
3572
|
+
function buildSharedSessionDiscoveryUrl(debugPort) {
|
|
3573
|
+
return `http://127.0.0.1:${debugPort}`;
|
|
3574
|
+
}
|
|
3575
|
+
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
3576
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
3577
|
+
return cdpUrl;
|
|
3578
|
+
}
|
|
3579
|
+
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
3580
|
+
const deadline = Date.now() + timeoutMs;
|
|
3581
|
+
let lastError = "CDP discovery did not respond.";
|
|
3582
|
+
while (Date.now() < deadline) {
|
|
3583
|
+
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
3584
|
+
try {
|
|
3585
|
+
const response = await fetch(versionUrl, {
|
|
3586
|
+
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
3587
|
+
});
|
|
3588
|
+
if (!response.ok) {
|
|
3589
|
+
lastError = `${response.status} ${response.statusText}`;
|
|
3590
|
+
} else {
|
|
3591
|
+
const payload = await response.json();
|
|
3592
|
+
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
3593
|
+
if (wsUrl && wsUrl.trim()) {
|
|
3594
|
+
return wsUrl;
|
|
3595
|
+
}
|
|
3596
|
+
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
1242
3597
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
})
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
|
|
1251
|
-
const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
|
|
1252
|
-
if (!owner) {
|
|
1253
|
-
return false;
|
|
1254
|
-
}
|
|
1255
|
-
if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
|
|
1256
|
-
return true;
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
3600
|
+
}
|
|
3601
|
+
await sleep5(100);
|
|
1257
3602
|
}
|
|
1258
|
-
|
|
3603
|
+
throw new Error(
|
|
3604
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
3605
|
+
);
|
|
1259
3606
|
}
|
|
1260
|
-
function
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
3607
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
3608
|
+
let parsed;
|
|
3609
|
+
try {
|
|
3610
|
+
parsed = new URL(cdpUrl);
|
|
3611
|
+
} catch {
|
|
3612
|
+
throw new Error(
|
|
3613
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
3614
|
+
);
|
|
1266
3615
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
1270
|
-
10
|
|
1271
|
-
);
|
|
1272
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1273
|
-
return null;
|
|
3616
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
3617
|
+
return parsed;
|
|
1274
3618
|
}
|
|
1275
|
-
if (
|
|
1276
|
-
|
|
3619
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
3620
|
+
throw new Error(
|
|
3621
|
+
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
3622
|
+
);
|
|
1277
3623
|
}
|
|
1278
|
-
|
|
3624
|
+
const normalized = new URL(parsed.toString());
|
|
3625
|
+
normalized.pathname = "/json/version";
|
|
3626
|
+
normalized.search = "";
|
|
3627
|
+
normalized.hash = "";
|
|
3628
|
+
return normalized;
|
|
1279
3629
|
}
|
|
1280
|
-
function
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
3630
|
+
async function reserveDebugPort() {
|
|
3631
|
+
return await new Promise((resolve, reject) => {
|
|
3632
|
+
const server = (0, import_node_net.createServer)();
|
|
3633
|
+
server.unref();
|
|
3634
|
+
server.on("error", reject);
|
|
3635
|
+
server.listen(0, "127.0.0.1", () => {
|
|
3636
|
+
const address = server.address();
|
|
3637
|
+
if (!address || typeof address === "string") {
|
|
3638
|
+
server.close();
|
|
3639
|
+
reject(new Error("Failed to reserve a local debug port."));
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
server.close((error) => {
|
|
3643
|
+
if (error) {
|
|
3644
|
+
reject(error);
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
resolve(address.port);
|
|
3648
|
+
});
|
|
3649
|
+
});
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
async function sleep5(ms) {
|
|
3653
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1288
3654
|
}
|
|
1289
3655
|
|
|
1290
3656
|
// src/browser/pool.ts
|
|
1291
3657
|
var BrowserPool = class {
|
|
1292
3658
|
browser = null;
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
managedUserDataDir = null;
|
|
1296
|
-
persistentProfile = false;
|
|
3659
|
+
activeSessionClose = null;
|
|
3660
|
+
closeInFlight = null;
|
|
1297
3661
|
defaults;
|
|
1298
3662
|
constructor(defaults = {}) {
|
|
1299
3663
|
this.defaults = defaults;
|
|
1300
3664
|
}
|
|
1301
3665
|
async launch(options = {}) {
|
|
1302
|
-
if (this.browser || this.
|
|
3666
|
+
if (this.browser || this.activeSessionClose) {
|
|
1303
3667
|
await this.close();
|
|
1304
3668
|
}
|
|
1305
3669
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -1346,30 +3710,26 @@ var BrowserPool = class {
|
|
|
1346
3710
|
return this.launchSandbox(options);
|
|
1347
3711
|
}
|
|
1348
3712
|
async close() {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
const
|
|
1354
|
-
this.
|
|
1355
|
-
this.cdpProxy = null;
|
|
1356
|
-
this.launchedProcess = null;
|
|
1357
|
-
this.managedUserDataDir = null;
|
|
1358
|
-
this.persistentProfile = false;
|
|
3713
|
+
if (this.closeInFlight) {
|
|
3714
|
+
await this.closeInFlight;
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
const closeOperation = this.closeCurrent();
|
|
3718
|
+
this.closeInFlight = closeOperation;
|
|
1359
3719
|
try {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
3720
|
+
await closeOperation;
|
|
3721
|
+
this.browser = null;
|
|
3722
|
+
this.activeSessionClose = null;
|
|
1363
3723
|
} finally {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}
|
|
3724
|
+
this.closeInFlight = null;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
async closeCurrent() {
|
|
3728
|
+
if (this.activeSessionClose) {
|
|
3729
|
+
await this.activeSessionClose();
|
|
3730
|
+
return;
|
|
1372
3731
|
}
|
|
3732
|
+
await this.browser?.close().catch(() => void 0);
|
|
1373
3733
|
}
|
|
1374
3734
|
async connectToRunning(cdpUrl, timeout) {
|
|
1375
3735
|
let browser = null;
|
|
@@ -1384,11 +3744,14 @@ var BrowserPool = class {
|
|
|
1384
3744
|
}
|
|
1385
3745
|
cdpProxy = new CDPProxy(browserWsUrl, targetId);
|
|
1386
3746
|
const proxyWsUrl = await cdpProxy.start();
|
|
1387
|
-
browser = await
|
|
3747
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
1388
3748
|
timeout: timeout ?? 3e4
|
|
1389
3749
|
});
|
|
1390
3750
|
this.browser = browser;
|
|
1391
|
-
this.
|
|
3751
|
+
this.activeSessionClose = async () => {
|
|
3752
|
+
await browser?.close().catch(() => void 0);
|
|
3753
|
+
cdpProxy?.close();
|
|
3754
|
+
};
|
|
1392
3755
|
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
1393
3756
|
return { browser, context, page, isExternal: true };
|
|
1394
3757
|
} catch (error) {
|
|
@@ -1397,7 +3760,7 @@ var BrowserPool = class {
|
|
|
1397
3760
|
}
|
|
1398
3761
|
cdpProxy?.close();
|
|
1399
3762
|
this.browser = null;
|
|
1400
|
-
this.
|
|
3763
|
+
this.activeSessionClose = null;
|
|
1401
3764
|
throw error;
|
|
1402
3765
|
}
|
|
1403
3766
|
}
|
|
@@ -1417,55 +3780,29 @@ var BrowserPool = class {
|
|
|
1417
3780
|
sourceUserDataDir,
|
|
1418
3781
|
profileDirectory
|
|
1419
3782
|
);
|
|
1420
|
-
await
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
3783
|
+
const sharedSession = await acquireSharedRealBrowserSession({
|
|
3784
|
+
executablePath,
|
|
3785
|
+
headless: resolveLaunchHeadless(
|
|
3786
|
+
"real",
|
|
3787
|
+
options.headless,
|
|
3788
|
+
this.defaults.headless
|
|
3789
|
+
),
|
|
3790
|
+
initialUrl: options.initialUrl,
|
|
3791
|
+
persistentProfile,
|
|
1429
3792
|
profileDirectory,
|
|
1430
|
-
|
|
1431
|
-
headless
|
|
1432
|
-
});
|
|
1433
|
-
const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
|
|
1434
|
-
detached: process.platform !== "win32",
|
|
1435
|
-
stdio: "ignore"
|
|
3793
|
+
timeoutMs: options.timeout ?? 3e4
|
|
1436
3794
|
});
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
timeout: options.timeout ?? 3e4
|
|
1446
|
-
});
|
|
1447
|
-
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
1448
|
-
browser
|
|
1449
|
-
);
|
|
1450
|
-
if (options.initialUrl) {
|
|
1451
|
-
await page.goto(options.initialUrl, {
|
|
1452
|
-
waitUntil: "domcontentloaded",
|
|
1453
|
-
timeout: options.timeout ?? 3e4
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
this.browser = browser;
|
|
1457
|
-
this.launchedProcess = processHandle;
|
|
1458
|
-
this.managedUserDataDir = persistentProfile.userDataDir;
|
|
1459
|
-
this.persistentProfile = true;
|
|
1460
|
-
return { browser, context, page, isExternal: false };
|
|
1461
|
-
} catch (error) {
|
|
1462
|
-
await browser?.close().catch(() => void 0);
|
|
1463
|
-
await killProcessTree(processHandle);
|
|
1464
|
-
throw error;
|
|
1465
|
-
}
|
|
3795
|
+
this.browser = sharedSession.browser;
|
|
3796
|
+
this.activeSessionClose = sharedSession.close;
|
|
3797
|
+
return {
|
|
3798
|
+
browser: sharedSession.browser,
|
|
3799
|
+
context: sharedSession.context,
|
|
3800
|
+
page: sharedSession.page,
|
|
3801
|
+
isExternal: false
|
|
3802
|
+
};
|
|
1466
3803
|
}
|
|
1467
3804
|
async launchSandbox(options) {
|
|
1468
|
-
const browser = await
|
|
3805
|
+
const browser = await import_playwright2.chromium.launch({
|
|
1469
3806
|
headless: resolveLaunchHeadless(
|
|
1470
3807
|
"chromium",
|
|
1471
3808
|
options.headless,
|
|
@@ -1478,11 +3815,14 @@ var BrowserPool = class {
|
|
|
1478
3815
|
const context = await browser.newContext(options.context || {});
|
|
1479
3816
|
const page = await context.newPage();
|
|
1480
3817
|
this.browser = browser;
|
|
3818
|
+
this.activeSessionClose = async () => {
|
|
3819
|
+
await browser.close().catch(() => void 0);
|
|
3820
|
+
};
|
|
1481
3821
|
return { browser, context, page, isExternal: false };
|
|
1482
3822
|
}
|
|
1483
3823
|
};
|
|
1484
3824
|
async function pickBrowserContextAndPage(browser) {
|
|
1485
|
-
const context =
|
|
3825
|
+
const context = getPrimaryBrowserContext2(browser);
|
|
1486
3826
|
const page = await getAttachedPageOrCreate(context);
|
|
1487
3827
|
return { context, page };
|
|
1488
3828
|
}
|
|
@@ -1495,11 +3835,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
|
1495
3835
|
}
|
|
1496
3836
|
return mode === "real";
|
|
1497
3837
|
}
|
|
1498
|
-
async function createOwnedBrowserContextAndPage(browser) {
|
|
1499
|
-
const context = getPrimaryBrowserContext(browser);
|
|
1500
|
-
const page = await getExistingPageOrCreate(context);
|
|
1501
|
-
return { context, page };
|
|
1502
|
-
}
|
|
1503
3838
|
async function getAttachedPageOrCreate(context) {
|
|
1504
3839
|
const pages = context.pages();
|
|
1505
3840
|
const inspectablePage = pages.find(
|
|
@@ -1514,14 +3849,7 @@ async function getAttachedPageOrCreate(context) {
|
|
|
1514
3849
|
}
|
|
1515
3850
|
return await context.newPage();
|
|
1516
3851
|
}
|
|
1517
|
-
|
|
1518
|
-
const existingPage = context.pages()[0];
|
|
1519
|
-
if (existingPage) {
|
|
1520
|
-
return existingPage;
|
|
1521
|
-
}
|
|
1522
|
-
return await context.newPage();
|
|
1523
|
-
}
|
|
1524
|
-
function getPrimaryBrowserContext(browser) {
|
|
3852
|
+
function getPrimaryBrowserContext2(browser) {
|
|
1525
3853
|
const contexts = browser.contexts();
|
|
1526
3854
|
if (contexts.length === 0) {
|
|
1527
3855
|
throw new Error(
|
|
@@ -1540,125 +3868,6 @@ function safePageUrl(page) {
|
|
|
1540
3868
|
return "";
|
|
1541
3869
|
}
|
|
1542
3870
|
}
|
|
1543
|
-
function normalizeDiscoveryUrl(cdpUrl) {
|
|
1544
|
-
let parsed;
|
|
1545
|
-
try {
|
|
1546
|
-
parsed = new URL(cdpUrl);
|
|
1547
|
-
} catch {
|
|
1548
|
-
throw new Error(
|
|
1549
|
-
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
1553
|
-
return parsed;
|
|
1554
|
-
}
|
|
1555
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1556
|
-
throw new Error(
|
|
1557
|
-
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
1558
|
-
);
|
|
1559
|
-
}
|
|
1560
|
-
const normalized = new URL(parsed.toString());
|
|
1561
|
-
normalized.pathname = "/json/version";
|
|
1562
|
-
normalized.search = "";
|
|
1563
|
-
normalized.hash = "";
|
|
1564
|
-
return normalized;
|
|
1565
|
-
}
|
|
1566
|
-
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
1567
|
-
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
1568
|
-
return cdpUrl;
|
|
1569
|
-
}
|
|
1570
|
-
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
1571
|
-
const deadline = Date.now() + timeoutMs;
|
|
1572
|
-
let lastError = "CDP discovery did not respond.";
|
|
1573
|
-
while (Date.now() < deadline) {
|
|
1574
|
-
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
1575
|
-
try {
|
|
1576
|
-
const response = await fetch(versionUrl, {
|
|
1577
|
-
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
1578
|
-
});
|
|
1579
|
-
if (!response.ok) {
|
|
1580
|
-
lastError = `${response.status} ${response.statusText}`;
|
|
1581
|
-
} else {
|
|
1582
|
-
const payload = await response.json();
|
|
1583
|
-
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
1584
|
-
if (wsUrl && wsUrl.trim()) {
|
|
1585
|
-
return wsUrl;
|
|
1586
|
-
}
|
|
1587
|
-
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
1588
|
-
}
|
|
1589
|
-
} catch (error) {
|
|
1590
|
-
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
1591
|
-
}
|
|
1592
|
-
await sleep(100);
|
|
1593
|
-
}
|
|
1594
|
-
throw new Error(
|
|
1595
|
-
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
async function reserveDebugPort() {
|
|
1599
|
-
return await new Promise((resolve, reject) => {
|
|
1600
|
-
const server = (0, import_node_net.createServer)();
|
|
1601
|
-
server.unref();
|
|
1602
|
-
server.on("error", reject);
|
|
1603
|
-
server.listen(0, "127.0.0.1", () => {
|
|
1604
|
-
const address = server.address();
|
|
1605
|
-
if (!address || typeof address === "string") {
|
|
1606
|
-
server.close();
|
|
1607
|
-
reject(new Error("Failed to reserve a local debug port."));
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
server.close((error) => {
|
|
1611
|
-
if (error) {
|
|
1612
|
-
reject(error);
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
resolve(address.port);
|
|
1616
|
-
});
|
|
1617
|
-
});
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
function buildRealBrowserLaunchArgs(options) {
|
|
1621
|
-
const args = [
|
|
1622
|
-
`--user-data-dir=${options.userDataDir}`,
|
|
1623
|
-
`--profile-directory=${options.profileDirectory}`,
|
|
1624
|
-
`--remote-debugging-port=${options.debugPort}`,
|
|
1625
|
-
"--disable-blink-features=AutomationControlled"
|
|
1626
|
-
];
|
|
1627
|
-
if (options.headless) {
|
|
1628
|
-
args.push("--headless=new");
|
|
1629
|
-
}
|
|
1630
|
-
return args;
|
|
1631
|
-
}
|
|
1632
|
-
async function killProcessTree(processHandle) {
|
|
1633
|
-
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
if (process.platform === "win32") {
|
|
1637
|
-
await new Promise((resolve) => {
|
|
1638
|
-
const killer = (0, import_node_child_process.spawn)(
|
|
1639
|
-
"taskkill",
|
|
1640
|
-
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
1641
|
-
{
|
|
1642
|
-
stdio: "ignore"
|
|
1643
|
-
}
|
|
1644
|
-
);
|
|
1645
|
-
killer.on("error", () => resolve());
|
|
1646
|
-
killer.on("exit", () => resolve());
|
|
1647
|
-
});
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
try {
|
|
1651
|
-
process.kill(-processHandle.pid, "SIGKILL");
|
|
1652
|
-
} catch {
|
|
1653
|
-
try {
|
|
1654
|
-
processHandle.kill("SIGKILL");
|
|
1655
|
-
} catch {
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
async function sleep(ms) {
|
|
1660
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1661
|
-
}
|
|
1662
3871
|
|
|
1663
3872
|
// src/config.ts
|
|
1664
3873
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
@@ -1965,9 +4174,9 @@ function resolveNamespaceDir(rootDir, namespace) {
|
|
|
1965
4174
|
const selectorsRoot = import_path2.default.resolve(rootDir, ".opensteer", "selectors");
|
|
1966
4175
|
const normalizedNamespace = normalizeNamespace(namespace);
|
|
1967
4176
|
const namespaceDir = import_path2.default.resolve(selectorsRoot, normalizedNamespace);
|
|
1968
|
-
const
|
|
1969
|
-
if (
|
|
1970
|
-
if (
|
|
4177
|
+
const relative2 = import_path2.default.relative(selectorsRoot, namespaceDir);
|
|
4178
|
+
if (relative2 === "" || relative2 === ".") return namespaceDir;
|
|
4179
|
+
if (relative2.startsWith("..") || import_path2.default.isAbsolute(relative2)) {
|
|
1971
4180
|
throw new Error(
|
|
1972
4181
|
`Namespace "${namespace}" resolves outside selectors root.`
|
|
1973
4182
|
);
|
|
@@ -2524,8 +4733,8 @@ function resolveNamespace(config, rootDir) {
|
|
|
2524
4733
|
}
|
|
2525
4734
|
const caller = getCallerFilePath();
|
|
2526
4735
|
if (!caller) return normalizeNamespace("default");
|
|
2527
|
-
const
|
|
2528
|
-
const cleaned =
|
|
4736
|
+
const relative2 = import_path3.default.relative(rootDir, caller);
|
|
4737
|
+
const cleaned = relative2.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
|
|
2529
4738
|
return normalizeNamespace(cleaned || "default");
|
|
2530
4739
|
}
|
|
2531
4740
|
function getCallerFilePath() {
|
|
@@ -2930,7 +5139,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2930
5139
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
2931
5140
|
Math.max(0, deadline - Date.now())
|
|
2932
5141
|
);
|
|
2933
|
-
await
|
|
5142
|
+
await sleep6(retryDelay);
|
|
2934
5143
|
}
|
|
2935
5144
|
}
|
|
2936
5145
|
}
|
|
@@ -2963,7 +5172,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2963
5172
|
() => ({ kind: "resolved" }),
|
|
2964
5173
|
(error) => ({ kind: "rejected", error })
|
|
2965
5174
|
);
|
|
2966
|
-
const timeoutPromise =
|
|
5175
|
+
const timeoutPromise = sleep6(
|
|
2967
5176
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
2968
5177
|
).then(() => ({ kind: "timeout" }));
|
|
2969
5178
|
const result = await Promise.race([
|
|
@@ -3105,7 +5314,7 @@ function isIgnorableFrameError(error) {
|
|
|
3105
5314
|
const message = error.message;
|
|
3106
5315
|
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");
|
|
3107
5316
|
}
|
|
3108
|
-
function
|
|
5317
|
+
function sleep6(ms) {
|
|
3109
5318
|
return new Promise((resolve) => {
|
|
3110
5319
|
setTimeout(resolve, ms);
|
|
3111
5320
|
});
|
|
@@ -7366,7 +9575,7 @@ async function closeTab(context, activePage, index) {
|
|
|
7366
9575
|
}
|
|
7367
9576
|
|
|
7368
9577
|
// src/actions/cookies.ts
|
|
7369
|
-
var
|
|
9578
|
+
var import_promises6 = require("fs/promises");
|
|
7370
9579
|
async function getCookies(context, url) {
|
|
7371
9580
|
return context.cookies(url ? [url] : void 0);
|
|
7372
9581
|
}
|
|
@@ -7378,10 +9587,10 @@ async function clearCookies(context) {
|
|
|
7378
9587
|
}
|
|
7379
9588
|
async function exportCookies(context, filePath, url) {
|
|
7380
9589
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
7381
|
-
await (0,
|
|
9590
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
7382
9591
|
}
|
|
7383
9592
|
async function importCookies(context, filePath) {
|
|
7384
|
-
const raw = await (0,
|
|
9593
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
7385
9594
|
const cookies = JSON.parse(raw);
|
|
7386
9595
|
await context.addCookies(cookies);
|
|
7387
9596
|
}
|
|
@@ -7692,7 +9901,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7692
9901
|
this.idleSince = 0;
|
|
7693
9902
|
}
|
|
7694
9903
|
const remaining = Math.max(1, options.deadline - now);
|
|
7695
|
-
await
|
|
9904
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
7696
9905
|
}
|
|
7697
9906
|
}
|
|
7698
9907
|
handleRequestStarted = (request) => {
|
|
@@ -7737,7 +9946,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7737
9946
|
return false;
|
|
7738
9947
|
}
|
|
7739
9948
|
};
|
|
7740
|
-
async function
|
|
9949
|
+
async function sleep7(ms) {
|
|
7741
9950
|
await new Promise((resolve) => {
|
|
7742
9951
|
setTimeout(resolve, ms);
|
|
7743
9952
|
});
|
|
@@ -9442,13 +11651,13 @@ function dedupeNewest(entries) {
|
|
|
9442
11651
|
}
|
|
9443
11652
|
|
|
9444
11653
|
// src/cloud/cdp-client.ts
|
|
9445
|
-
var
|
|
11654
|
+
var import_playwright3 = require("playwright");
|
|
9446
11655
|
var CloudCdpClient = class {
|
|
9447
11656
|
async connect(args) {
|
|
9448
11657
|
const endpoint = withTokenQuery(args.wsUrl, args.token);
|
|
9449
11658
|
let browser;
|
|
9450
11659
|
try {
|
|
9451
|
-
browser = await
|
|
11660
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
9452
11661
|
} catch (error) {
|
|
9453
11662
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
9454
11663
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -11265,7 +13474,7 @@ async function executeAgentAction(page, action) {
|
|
|
11265
13474
|
}
|
|
11266
13475
|
case "wait": {
|
|
11267
13476
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
11268
|
-
await
|
|
13477
|
+
await sleep8(ms);
|
|
11269
13478
|
return;
|
|
11270
13479
|
}
|
|
11271
13480
|
case "goto": {
|
|
@@ -11430,7 +13639,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
11430
13639
|
}
|
|
11431
13640
|
}
|
|
11432
13641
|
}
|
|
11433
|
-
function
|
|
13642
|
+
function sleep8(ms) {
|
|
11434
13643
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11435
13644
|
}
|
|
11436
13645
|
|
|
@@ -11461,7 +13670,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11461
13670
|
if (isMutatingAgentAction(action)) {
|
|
11462
13671
|
this.onMutatingAction?.(action);
|
|
11463
13672
|
}
|
|
11464
|
-
await
|
|
13673
|
+
await sleep9(this.config.waitBetweenActionsMs);
|
|
11465
13674
|
});
|
|
11466
13675
|
try {
|
|
11467
13676
|
const result = await this.client.execute({
|
|
@@ -11523,7 +13732,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11523
13732
|
await this.cursorController.preview({ x, y }, "agent");
|
|
11524
13733
|
}
|
|
11525
13734
|
};
|
|
11526
|
-
function
|
|
13735
|
+
function sleep9(ms) {
|
|
11527
13736
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11528
13737
|
}
|
|
11529
13738
|
|
|
@@ -11959,7 +14168,7 @@ var CursorController = class {
|
|
|
11959
14168
|
for (const step of motion.points) {
|
|
11960
14169
|
await this.renderer.move(step, this.style);
|
|
11961
14170
|
if (motion.stepDelayMs > 0) {
|
|
11962
|
-
await
|
|
14171
|
+
await sleep10(motion.stepDelayMs);
|
|
11963
14172
|
}
|
|
11964
14173
|
}
|
|
11965
14174
|
if (shouldPulse(intent)) {
|
|
@@ -12117,7 +14326,7 @@ function clamp2(value, min, max) {
|
|
|
12117
14326
|
function shouldPulse(intent) {
|
|
12118
14327
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
12119
14328
|
}
|
|
12120
|
-
function
|
|
14329
|
+
function sleep10(ms) {
|
|
12121
14330
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12122
14331
|
}
|
|
12123
14332
|
|
|
@@ -12591,15 +14800,22 @@ var Opensteer = class _Opensteer {
|
|
|
12591
14800
|
}
|
|
12592
14801
|
return;
|
|
12593
14802
|
}
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
14803
|
+
let closedOwnedBrowser = false;
|
|
14804
|
+
try {
|
|
14805
|
+
if (this.ownsBrowser) {
|
|
14806
|
+
await this.pool.close();
|
|
14807
|
+
closedOwnedBrowser = true;
|
|
14808
|
+
}
|
|
14809
|
+
} finally {
|
|
14810
|
+
this.browser = null;
|
|
14811
|
+
this.pageRef = null;
|
|
14812
|
+
this.contextRef = null;
|
|
14813
|
+
if (!this.ownsBrowser || closedOwnedBrowser) {
|
|
14814
|
+
this.ownsBrowser = false;
|
|
14815
|
+
}
|
|
14816
|
+
if (this.cursorController) {
|
|
14817
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
14818
|
+
}
|
|
12603
14819
|
}
|
|
12604
14820
|
}
|
|
12605
14821
|
async syncLocalSelectorCacheToCloud() {
|
|
@@ -15053,7 +17269,7 @@ var CdpOverlayCursorRenderer = class {
|
|
|
15053
17269
|
outlineColor: toProtocolRgba(pulseOutline)
|
|
15054
17270
|
});
|
|
15055
17271
|
});
|
|
15056
|
-
await
|
|
17272
|
+
await sleep11(PULSE_DELAY_MS);
|
|
15057
17273
|
await this.move(point, style);
|
|
15058
17274
|
}
|
|
15059
17275
|
async clear() {
|
|
@@ -15177,7 +17393,7 @@ function clampAlpha(value) {
|
|
|
15177
17393
|
function roundPointValue(value) {
|
|
15178
17394
|
return Math.round(value * 100) / 100;
|
|
15179
17395
|
}
|
|
15180
|
-
function
|
|
17396
|
+
function sleep11(ms) {
|
|
15181
17397
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15182
17398
|
}
|
|
15183
17399
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -15237,6 +17453,7 @@ function sleep7(ms) {
|
|
|
15237
17453
|
createCuaClient,
|
|
15238
17454
|
createEmptyRegistry,
|
|
15239
17455
|
createExtractCallback,
|
|
17456
|
+
createIsolatedRuntimeProfile,
|
|
15240
17457
|
createResolveCallback,
|
|
15241
17458
|
createTab,
|
|
15242
17459
|
detectChromePaths,
|
|
@@ -15252,8 +17469,10 @@ function sleep7(ms) {
|
|
|
15252
17469
|
getElementValue,
|
|
15253
17470
|
getModelProvider,
|
|
15254
17471
|
getOrCreatePersistentProfile,
|
|
17472
|
+
getOwnedRealBrowserProcessPolicy,
|
|
15255
17473
|
getPageHtml,
|
|
15256
17474
|
getPageTitle,
|
|
17475
|
+
hasActiveRuntimeProfileCreations,
|
|
15257
17476
|
importCookies,
|
|
15258
17477
|
isCloudActionMethod,
|
|
15259
17478
|
isCloudErrorCode,
|
|
@@ -15269,6 +17488,7 @@ function sleep7(ms) {
|
|
|
15269
17488
|
performInput,
|
|
15270
17489
|
performScroll,
|
|
15271
17490
|
performSelect,
|
|
17491
|
+
persistIsolatedRuntimeProfile,
|
|
15272
17492
|
planSnappyCursorMotion,
|
|
15273
17493
|
prepareSnapshot,
|
|
15274
17494
|
pressKey,
|