opensteer 0.6.12 → 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-RC4IPQDZ.js → chunk-HBTSQ2V4.js} +2535 -1104
- package/dist/cli/profile.cjs +1800 -1121
- package/dist/cli/profile.js +1 -1
- package/dist/cli/server.cjs +1718 -1039
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +2527 -1102
- package/dist/index.d.cts +26 -17
- package/dist/index.d.ts +26 -17
- package/dist/index.js +5 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -491,8 +491,10 @@ __export(index_exports, {
|
|
|
491
491
|
getElementValue: () => getElementValue,
|
|
492
492
|
getModelProvider: () => getModelProvider,
|
|
493
493
|
getOrCreatePersistentProfile: () => getOrCreatePersistentProfile,
|
|
494
|
+
getOwnedRealBrowserProcessPolicy: () => getOwnedRealBrowserProcessPolicy,
|
|
494
495
|
getPageHtml: () => getPageHtml,
|
|
495
496
|
getPageTitle: () => getPageTitle,
|
|
497
|
+
hasActiveRuntimeProfileCreations: () => hasActiveRuntimeProfileCreations,
|
|
496
498
|
importCookies: () => importCookies,
|
|
497
499
|
isCloudActionMethod: () => isCloudActionMethod,
|
|
498
500
|
isCloudErrorCode: () => isCloudErrorCode,
|
|
@@ -531,10 +533,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
531
533
|
var import_crypto = require("crypto");
|
|
532
534
|
|
|
533
535
|
// src/browser/pool.ts
|
|
534
|
-
var
|
|
535
|
-
var import_promises2 = require("fs/promises");
|
|
536
|
-
var import_node_net = require("net");
|
|
537
|
-
var import_playwright = require("playwright");
|
|
536
|
+
var import_playwright2 = require("playwright");
|
|
538
537
|
|
|
539
538
|
// src/browser/cdp-proxy.ts
|
|
540
539
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -1027,1064 +1026,2644 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
|
|
|
1027
1026
|
}
|
|
1028
1027
|
|
|
1029
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
|
|
1030
1041
|
var import_node_crypto = require("crypto");
|
|
1031
|
-
var import_node_child_process = require("child_process");
|
|
1032
1042
|
var import_node_fs = require("fs");
|
|
1033
|
-
var
|
|
1034
|
-
var import_node_os = require("os");
|
|
1043
|
+
var import_promises2 = require("fs/promises");
|
|
1035
1044
|
var import_node_path = require("path");
|
|
1045
|
+
|
|
1046
|
+
// src/browser/process-owner.ts
|
|
1047
|
+
var import_node_child_process = require("child_process");
|
|
1048
|
+
var import_promises = require("fs/promises");
|
|
1036
1049
|
var import_node_util = require("util");
|
|
1037
1050
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
1038
|
-
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
1039
|
-
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
1040
|
-
var LOCK_OWNER_FILE = "owner.json";
|
|
1041
|
-
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
1042
1051
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
1043
1052
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
1044
|
-
var
|
|
1053
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
1045
1054
|
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
1046
1055
|
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
1047
|
-
var
|
|
1048
|
-
"SingletonCookie",
|
|
1049
|
-
"SingletonLock",
|
|
1050
|
-
"SingletonSocket",
|
|
1051
|
-
"DevToolsActivePort",
|
|
1052
|
-
"lockfile"
|
|
1053
|
-
]);
|
|
1054
|
-
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
1055
|
-
...CHROME_SINGLETON_ENTRIES,
|
|
1056
|
-
OPENSTEER_META_FILE,
|
|
1057
|
-
OPENSTEER_RUNTIME_META_FILE
|
|
1058
|
-
]);
|
|
1059
|
-
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1060
|
-
"Crash Reports",
|
|
1061
|
-
"Crashpad",
|
|
1062
|
-
"BrowserMetrics",
|
|
1063
|
-
"GrShaderCache",
|
|
1064
|
-
"ShaderCache",
|
|
1065
|
-
"GraphiteDawnCache",
|
|
1066
|
-
"component_crx_cache",
|
|
1067
|
-
"Crowd Deny",
|
|
1068
|
-
"hyphen-data",
|
|
1069
|
-
"OnDeviceHeadSuggestModel",
|
|
1070
|
-
"OptimizationGuidePredictionModels",
|
|
1071
|
-
"Segmentation Platform",
|
|
1072
|
-
"SmartCardDeviceNames",
|
|
1073
|
-
"WidevineCdm",
|
|
1074
|
-
"pnacl"
|
|
1075
|
-
]);
|
|
1076
|
-
var CURRENT_PROCESS_LOCK_OWNER = {
|
|
1056
|
+
var CURRENT_PROCESS_OWNER = {
|
|
1077
1057
|
pid: process.pid,
|
|
1078
1058
|
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
1079
1059
|
};
|
|
1080
1060
|
var linuxClockTicksPerSecondPromise = null;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
);
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
)
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
1099
|
-
throw new Error(
|
|
1100
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
const created = await createPersistentProfileClone(
|
|
1104
|
-
resolvedSourceUserDataDir,
|
|
1105
|
-
sourceProfileDir,
|
|
1106
|
-
targetUserDataDir,
|
|
1107
|
-
profileDirectory,
|
|
1108
|
-
metadata
|
|
1109
|
-
);
|
|
1110
|
-
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1111
|
-
return {
|
|
1112
|
-
created,
|
|
1113
|
-
userDataDir: targetUserDataDir
|
|
1114
|
-
};
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
async function clearPersistentProfileSingletons(userDataDir) {
|
|
1118
|
-
await Promise.all(
|
|
1119
|
-
[...CHROME_SINGLETON_ENTRIES].map(
|
|
1120
|
-
(entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
|
|
1121
|
-
force: true,
|
|
1122
|
-
recursive: true
|
|
1123
|
-
}).catch(() => void 0)
|
|
1124
|
-
)
|
|
1125
|
-
);
|
|
1126
|
-
}
|
|
1127
|
-
async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
|
|
1128
|
-
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1129
|
-
const runtimeRootDir = expandHome(runtimesRootDir);
|
|
1130
|
-
await (0, import_promises.mkdir)(runtimeRootDir, { recursive: true });
|
|
1131
|
-
return await withPersistentProfileLock(
|
|
1132
|
-
resolvedSourceUserDataDir,
|
|
1133
|
-
async () => {
|
|
1134
|
-
const sourceMetadata = await readPersistentProfileMetadata(
|
|
1135
|
-
resolvedSourceUserDataDir
|
|
1136
|
-
);
|
|
1137
|
-
await cleanOrphanedOwnedDirs(
|
|
1138
|
-
runtimeRootDir,
|
|
1139
|
-
buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
|
|
1140
|
-
);
|
|
1141
|
-
const runtimeUserDataDir = await (0, import_promises.mkdtemp)(
|
|
1142
|
-
buildRuntimeProfileDirPrefix(
|
|
1143
|
-
runtimeRootDir,
|
|
1144
|
-
resolvedSourceUserDataDir
|
|
1145
|
-
)
|
|
1146
|
-
);
|
|
1147
|
-
try {
|
|
1148
|
-
await copyUserDataDirSnapshot(
|
|
1149
|
-
resolvedSourceUserDataDir,
|
|
1150
|
-
runtimeUserDataDir
|
|
1151
|
-
);
|
|
1152
|
-
await writeRuntimeProfileMetadata(
|
|
1153
|
-
runtimeUserDataDir,
|
|
1154
|
-
await buildRuntimeProfileMetadata(
|
|
1155
|
-
runtimeUserDataDir,
|
|
1156
|
-
sourceMetadata?.profileDirectory ?? null
|
|
1157
|
-
)
|
|
1158
|
-
);
|
|
1159
|
-
return {
|
|
1160
|
-
persistentUserDataDir: resolvedSourceUserDataDir,
|
|
1161
|
-
userDataDir: runtimeUserDataDir
|
|
1162
|
-
};
|
|
1163
|
-
} catch (error) {
|
|
1164
|
-
await (0, import_promises.rm)(runtimeUserDataDir, {
|
|
1165
|
-
recursive: true,
|
|
1166
|
-
force: true
|
|
1167
|
-
}).catch(() => void 0);
|
|
1168
|
-
throw error;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
|
|
1174
|
-
const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
|
|
1175
|
-
const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
|
|
1176
|
-
await withPersistentProfileLock(resolvedPersistentUserDataDir, async () => {
|
|
1177
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(resolvedPersistentUserDataDir), { recursive: true });
|
|
1178
|
-
await cleanOrphanedOwnedDirs(
|
|
1179
|
-
(0, import_node_path.dirname)(resolvedPersistentUserDataDir),
|
|
1180
|
-
buildPersistentProfileTempDirNamePrefix(resolvedPersistentUserDataDir)
|
|
1181
|
-
);
|
|
1182
|
-
const metadata = await requirePersistentProfileMetadata(
|
|
1183
|
-
resolvedPersistentUserDataDir
|
|
1184
|
-
);
|
|
1185
|
-
const runtimeMetadata = await requireRuntimeProfileMetadata(
|
|
1186
|
-
resolvedRuntimeUserDataDir,
|
|
1187
|
-
metadata.profileDirectory
|
|
1188
|
-
);
|
|
1189
|
-
await mergePersistentProfileSnapshot(
|
|
1190
|
-
resolvedRuntimeUserDataDir,
|
|
1191
|
-
resolvedPersistentUserDataDir,
|
|
1192
|
-
metadata,
|
|
1193
|
-
runtimeMetadata
|
|
1194
|
-
);
|
|
1195
|
-
});
|
|
1196
|
-
await (0, import_promises.rm)(resolvedRuntimeUserDataDir, {
|
|
1197
|
-
recursive: true,
|
|
1198
|
-
force: true
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
1202
|
-
const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
1203
|
-
const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
|
|
1204
|
-
const profileLabel = sanitizePathSegment(profileDirectory || "Default");
|
|
1205
|
-
return `${sourceLabel}-${profileLabel}-${hash}`;
|
|
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;
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
pid,
|
|
1076
|
+
processStartedAtMs
|
|
1077
|
+
};
|
|
1206
1078
|
}
|
|
1207
|
-
function
|
|
1208
|
-
|
|
1079
|
+
function processOwnersEqual(left, right) {
|
|
1080
|
+
if (!left || !right) {
|
|
1081
|
+
return left === right;
|
|
1082
|
+
}
|
|
1083
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
1209
1084
|
}
|
|
1210
|
-
function
|
|
1211
|
-
|
|
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";
|
|
1212
1100
|
}
|
|
1213
|
-
function
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
+
}
|
|
1216
1109
|
}
|
|
1217
|
-
function
|
|
1218
|
-
|
|
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
|
+
};
|
|
1219
1119
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
recursive: true,
|
|
1223
|
-
filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
|
|
1224
|
-
});
|
|
1120
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1121
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
1225
1122
|
}
|
|
1226
|
-
function
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
return true;
|
|
1123
|
+
async function readProcessStartedAtMs(pid) {
|
|
1124
|
+
if (pid <= 0) {
|
|
1125
|
+
return null;
|
|
1230
1126
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
return true;
|
|
1127
|
+
if (process.platform === "linux") {
|
|
1128
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
1234
1129
|
}
|
|
1235
|
-
|
|
1130
|
+
if (process.platform === "win32") {
|
|
1131
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
1132
|
+
}
|
|
1133
|
+
return await readPsProcessStartedAtMs(pid);
|
|
1236
1134
|
}
|
|
1237
|
-
async function
|
|
1238
|
-
let
|
|
1135
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
1136
|
+
let statRaw;
|
|
1239
1137
|
try {
|
|
1240
|
-
|
|
1138
|
+
statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
1241
1139
|
} catch {
|
|
1242
|
-
return;
|
|
1140
|
+
return null;
|
|
1243
1141
|
}
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
if (entry === targetProfileDirectory) continue;
|
|
1248
|
-
const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
|
|
1249
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
|
|
1250
|
-
if ((0, import_node_fs.existsSync)(targetPath)) continue;
|
|
1251
|
-
let entryStat;
|
|
1252
|
-
try {
|
|
1253
|
-
entryStat = await (0, import_promises.stat)(sourcePath);
|
|
1254
|
-
} catch {
|
|
1255
|
-
continue;
|
|
1256
|
-
}
|
|
1257
|
-
if (entryStat.isFile()) {
|
|
1258
|
-
copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1259
|
-
} else if (entryStat.isDirectory()) {
|
|
1260
|
-
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1261
|
-
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1262
|
-
copyTasks.push(
|
|
1263
|
-
(0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1264
|
-
() => void 0
|
|
1265
|
-
)
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1142
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
1143
|
+
if (startTicks === null) {
|
|
1144
|
+
return null;
|
|
1268
1145
|
}
|
|
1269
|
-
await Promise.all(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
|
1275
1155
|
);
|
|
1276
1156
|
}
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
source: sourceUserDataDir
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
1285
|
-
if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
|
|
1286
|
-
return false;
|
|
1157
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1158
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1159
|
+
if (closingParenIndex === -1) {
|
|
1160
|
+
return null;
|
|
1287
1161
|
}
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
);
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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;
|
|
1165
|
+
}
|
|
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;
|
|
1180
|
+
}
|
|
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) {
|
|
1195
|
+
try {
|
|
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
|
+
}
|
|
1207
|
+
);
|
|
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
|
|
1227
|
+
}
|
|
1299
1228
|
);
|
|
1229
|
+
const startedAt = stdout.trim();
|
|
1230
|
+
if (!startedAt) {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
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();
|
|
1248
|
+
} finally {
|
|
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, {
|
|
1278
|
+
recursive: true,
|
|
1279
|
+
force: true
|
|
1280
|
+
}).catch(() => void 0);
|
|
1281
|
+
}
|
|
1282
|
+
const owner = await readLockOwner(lockDirPath);
|
|
1283
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
return async () => {
|
|
1289
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
1290
|
+
recursive: true,
|
|
1291
|
+
force: true
|
|
1292
|
+
}).catch(() => void 0);
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
async function isDirLockHeld(lockDirPath) {
|
|
1296
|
+
if (!(0, import_node_fs.existsSync)(lockDirPath)) {
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
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);
|
|
1304
|
+
}
|
|
1305
|
+
function getErrorCode(error) {
|
|
1306
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1307
|
+
}
|
|
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)
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
1342
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
let reclaimed = false;
|
|
1346
|
+
try {
|
|
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
|
+
}
|
|
1367
|
+
}
|
|
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)()}`;
|
|
1300
1373
|
try {
|
|
1301
|
-
await (0,
|
|
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
|
+
}
|
|
1302
1387
|
} catch (error) {
|
|
1303
|
-
|
|
1388
|
+
const code = getErrorCode(error);
|
|
1389
|
+
if (code === "ENOENT") {
|
|
1304
1390
|
return false;
|
|
1305
1391
|
}
|
|
1306
1392
|
throw error;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
return true;
|
|
1310
|
-
} finally {
|
|
1311
|
-
if (!published) {
|
|
1312
|
-
await (0, import_promises.rm)(tempUserDataDir, {
|
|
1393
|
+
} finally {
|
|
1394
|
+
await (0, import_promises2.rm)(tempReclaimerDirPath, {
|
|
1313
1395
|
recursive: true,
|
|
1314
1396
|
force: true
|
|
1315
1397
|
}).catch(() => void 0);
|
|
1316
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);
|
|
1317
1410
|
}
|
|
1318
1411
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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);
|
|
1324
1444
|
}
|
|
1325
|
-
await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(targetUserDataDir, profileDirectory), {
|
|
1326
|
-
recursive: true
|
|
1327
|
-
});
|
|
1328
|
-
await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
|
|
1329
|
-
await writePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1330
1445
|
}
|
|
1331
|
-
async function
|
|
1332
|
-
|
|
1333
|
-
|
|
1446
|
+
async function isPersistentProfileWriteLocked(targetUserDataDir) {
|
|
1447
|
+
return await isDirLockHeld(
|
|
1448
|
+
buildPersistentProfileWriteLockDirPath(targetUserDataDir)
|
|
1334
1449
|
);
|
|
1335
|
-
|
|
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 });
|
|
1336
1503
|
try {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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;
|
|
1362
2040
|
} finally {
|
|
1363
2041
|
if (!published) {
|
|
1364
|
-
await (0,
|
|
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), {
|
|
1365
2690
|
recursive: true,
|
|
1366
2691
|
force: true
|
|
1367
2692
|
}).catch(() => void 0);
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
async function buildRuntimeProfileMetadata(runtimeUserDataDir, profileDirectory) {
|
|
1372
|
-
if (!profileDirectory) {
|
|
1373
|
-
return {
|
|
1374
|
-
baseEntries: {},
|
|
1375
|
-
profileDirectory: null
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
const baseEntries = await collectPersistentSnapshotEntries(
|
|
1379
|
-
runtimeUserDataDir,
|
|
1380
|
-
profileDirectory
|
|
2693
|
+
})
|
|
1381
2694
|
);
|
|
1382
|
-
return {
|
|
1383
|
-
baseEntries: serializeSnapshotManifestEntries(baseEntries),
|
|
1384
|
-
profileDirectory
|
|
1385
|
-
};
|
|
1386
2695
|
}
|
|
1387
|
-
async function
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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)
|
|
1391
2711
|
);
|
|
1392
2712
|
}
|
|
1393
|
-
async function
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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;
|
|
1403
2731
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
return null;
|
|
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);
|
|
1413
2740
|
}
|
|
1414
2741
|
}
|
|
1415
|
-
async function
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
throw new Error(
|
|
1419
|
-
`Runtime profile metadata was not found for "${userDataDir}".`
|
|
1420
|
-
);
|
|
2742
|
+
async function restoreClaimedRuntimeProfile(claimedRuntimeUserDataDir, runtimeUserDataDir) {
|
|
2743
|
+
if (!(0, import_node_fs3.existsSync)(claimedRuntimeUserDataDir)) {
|
|
2744
|
+
return;
|
|
1421
2745
|
}
|
|
1422
|
-
if (
|
|
2746
|
+
if ((0, import_node_fs3.existsSync)(runtimeUserDataDir)) {
|
|
1423
2747
|
throw new Error(
|
|
1424
|
-
`Runtime profile "${
|
|
2748
|
+
`Runtime profile "${runtimeUserDataDir}" was recreated before the failed persist could restore it from "${claimedRuntimeUserDataDir}".`
|
|
1425
2749
|
);
|
|
1426
2750
|
}
|
|
1427
|
-
|
|
2751
|
+
await (0, import_promises4.rename)(claimedRuntimeUserDataDir, runtimeUserDataDir);
|
|
1428
2752
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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);
|
|
1438
2762
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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);
|
|
1447
2781
|
continue;
|
|
1448
2782
|
}
|
|
1449
|
-
await
|
|
1450
|
-
|
|
1451
|
-
entry.name,
|
|
1452
|
-
collected
|
|
2783
|
+
const runtimeMarker = await readRuntimeProfileCreationMarker(
|
|
2784
|
+
marker.runtimeUserDataDir
|
|
1453
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;
|
|
1454
2805
|
}
|
|
1455
|
-
return
|
|
2806
|
+
return hasLiveCreation;
|
|
1456
2807
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
|
|
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);
|
|
1460
2814
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
2815
|
+
}
|
|
2816
|
+
function parseRuntimeProfileCreationMarker(value) {
|
|
2817
|
+
if (!value || typeof value !== "object") {
|
|
2818
|
+
return null;
|
|
1463
2819
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
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;
|
|
1466
2827
|
}
|
|
1467
|
-
return
|
|
2828
|
+
return {
|
|
2829
|
+
creator,
|
|
2830
|
+
persistentUserDataDir,
|
|
2831
|
+
profileDirectory,
|
|
2832
|
+
runtimeUserDataDir
|
|
2833
|
+
};
|
|
1468
2834
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
return;
|
|
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;
|
|
1475
2841
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
try {
|
|
1484
|
-
children = await (0, import_promises.readdir)(sourcePath, {
|
|
1485
|
-
encoding: "utf8",
|
|
1486
|
-
withFileTypes: true
|
|
1487
|
-
});
|
|
1488
|
-
} catch {
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
|
-
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
1492
|
-
for (const child of children) {
|
|
1493
|
-
await collectSnapshotEntry(
|
|
1494
|
-
(0, import_node_path.join)(sourcePath, child.name),
|
|
1495
|
-
(0, import_node_path.join)(relativePath, child.name),
|
|
1496
|
-
collected
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
return;
|
|
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;
|
|
1500
2849
|
}
|
|
1501
|
-
if (
|
|
1502
|
-
|
|
1503
|
-
kind: "file",
|
|
1504
|
-
hash: await hashFile(sourcePath),
|
|
1505
|
-
sourcePath
|
|
1506
|
-
});
|
|
2850
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
2851
|
+
return null;
|
|
1507
2852
|
}
|
|
2853
|
+
return { pid, processStartedAtMs };
|
|
1508
2854
|
}
|
|
1509
|
-
function
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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="],
|
|
1513
2866
|
{
|
|
1514
|
-
|
|
1515
|
-
|
|
2867
|
+
env: PS_COMMAND_ENV2,
|
|
2868
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
|
|
1516
2869
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
const manifestEntries = /* @__PURE__ */ new Map();
|
|
1522
|
-
for (const [relativePath, entry] of Object.entries(entries)) {
|
|
1523
|
-
if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
|
|
1524
|
-
throw new Error(
|
|
1525
|
-
`Runtime profile metadata for "${relativePath}" is invalid.`
|
|
1526
|
-
);
|
|
1527
|
-
}
|
|
1528
|
-
manifestEntries.set(relativePath, {
|
|
1529
|
-
kind: entry.kind,
|
|
1530
|
-
hash: entry.hash
|
|
1531
|
-
});
|
|
2870
|
+
);
|
|
2871
|
+
return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2872
|
+
} catch {
|
|
2873
|
+
return [];
|
|
1532
2874
|
}
|
|
1533
|
-
return manifestEntries;
|
|
1534
2875
|
}
|
|
1535
|
-
function
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
baseEntries.get(relativePath) ?? null,
|
|
1548
|
-
currentEntries.get(relativePath) ?? null,
|
|
1549
|
-
runtimeEntries.get(relativePath) ?? null
|
|
1550
|
-
)
|
|
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
|
+
}
|
|
1551
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 [];
|
|
1552
2894
|
}
|
|
1553
|
-
return mergedEntries;
|
|
1554
2895
|
}
|
|
1555
|
-
function
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
if (!baseEntry) {
|
|
1563
|
-
if (!currentEntry) {
|
|
1564
|
-
return runtimeEntry ? "runtime" : null;
|
|
1565
|
-
}
|
|
1566
|
-
if (!runtimeEntry) {
|
|
1567
|
-
return "current";
|
|
1568
|
-
}
|
|
1569
|
-
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
1570
|
-
return "current";
|
|
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;
|
|
1571
2903
|
}
|
|
1572
|
-
throw new Error(
|
|
1573
|
-
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
1574
|
-
);
|
|
1575
|
-
}
|
|
1576
|
-
if (!currentEntry && !runtimeEntry) {
|
|
1577
|
-
return null;
|
|
1578
|
-
}
|
|
1579
|
-
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
1580
|
-
return currentEntry ? "current" : null;
|
|
1581
2904
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
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
|
+
};
|
|
1585
2941
|
}
|
|
1586
|
-
function
|
|
1587
|
-
if (
|
|
1588
|
-
return
|
|
2942
|
+
function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
|
|
2943
|
+
if (platformName === "win32") {
|
|
2944
|
+
return {
|
|
2945
|
+
detached: false,
|
|
2946
|
+
killStrategy: "taskkill",
|
|
2947
|
+
shouldUnref: true
|
|
2948
|
+
};
|
|
1589
2949
|
}
|
|
1590
|
-
|
|
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
|
+
};
|
|
1591
2962
|
}
|
|
1592
|
-
async function
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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);
|
|
1599
3004
|
continue;
|
|
1600
3005
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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;
|
|
1605
3050
|
}
|
|
1606
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetPath), { recursive: true });
|
|
1607
|
-
await (0, import_promises.copyFile)(entry.sourcePath, targetPath);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
function compareSnapshotPaths(left, right) {
|
|
1611
|
-
const leftDepth = left.split(import_node_path.sep).length;
|
|
1612
|
-
const rightDepth = right.split(import_node_path.sep).length;
|
|
1613
|
-
if (leftDepth !== rightDepth) {
|
|
1614
|
-
return leftDepth - rightDepth;
|
|
1615
3051
|
}
|
|
1616
|
-
return left.localeCompare(right);
|
|
1617
3052
|
}
|
|
1618
|
-
async function
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
|
1624
3063
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
|
1628
3091
|
});
|
|
1629
|
-
|
|
3092
|
+
throw error;
|
|
3093
|
+
}
|
|
1630
3094
|
}
|
|
1631
|
-
async function
|
|
1632
|
-
|
|
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
|
+
);
|
|
1633
3107
|
return;
|
|
1634
3108
|
}
|
|
1635
|
-
await
|
|
3109
|
+
await context.page.close().catch(() => void 0);
|
|
3110
|
+
await context.browser.close().catch(() => void 0);
|
|
1636
3111
|
}
|
|
1637
|
-
async function
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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
|
+
);
|
|
1643
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") {
|
|
1644
3138
|
return {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
source: parsed.source
|
|
3139
|
+
kind: "ready",
|
|
3140
|
+
metadata
|
|
1648
3141
|
};
|
|
1649
|
-
} catch {
|
|
1650
|
-
return null;
|
|
1651
3142
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
);
|
|
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
|
+
};
|
|
1659
3154
|
}
|
|
1660
|
-
return
|
|
1661
|
-
}
|
|
1662
|
-
function
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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();
|
|
1670
3174
|
}
|
|
1671
|
-
const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
|
|
1672
|
-
let targetMovedToBackup = false;
|
|
1673
|
-
let replacementPublished = false;
|
|
1674
3175
|
try {
|
|
1675
|
-
await (
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
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
|
+
};
|
|
1679
3197
|
} catch (error) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
3198
|
+
await killSpawnedBrowserProcess(processHandle);
|
|
3199
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3200
|
+
force: true,
|
|
3201
|
+
recursive: true
|
|
3202
|
+
}).catch(() => void 0);
|
|
1683
3203
|
throw error;
|
|
1684
|
-
} finally {
|
|
1685
|
-
if (replacementPublished && targetMovedToBackup && (0, import_node_fs.existsSync)(backupUserDataDir)) {
|
|
1686
|
-
await (0, import_promises.rm)(backupUserDataDir, {
|
|
1687
|
-
recursive: true,
|
|
1688
|
-
force: true
|
|
1689
|
-
}).catch(() => void 0);
|
|
1690
|
-
}
|
|
1691
3204
|
}
|
|
1692
3205
|
}
|
|
1693
|
-
async function
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
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;
|
|
1707
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);
|
|
1708
3237
|
}
|
|
1709
|
-
|
|
1710
|
-
await (0, import_promises.rm)(tempLockDirPath, {
|
|
1711
|
-
recursive: true,
|
|
1712
|
-
force: true
|
|
1713
|
-
}).catch(() => void 0);
|
|
1714
|
-
}
|
|
1715
|
-
const owner = await readLockOwner(lockDirPath);
|
|
1716
|
-
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
1717
|
-
continue;
|
|
3238
|
+
return false;
|
|
1718
3239
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
return await action();
|
|
1723
|
-
} finally {
|
|
1724
|
-
await (0, import_promises.rm)(lockDirPath, {
|
|
1725
|
-
recursive: true,
|
|
1726
|
-
force: true
|
|
1727
|
-
}).catch(() => void 0);
|
|
3240
|
+
);
|
|
3241
|
+
if (shouldPreserveLiveBrowser) {
|
|
3242
|
+
return;
|
|
1728
3243
|
}
|
|
3244
|
+
await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
|
|
3245
|
+
await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
|
|
1729
3246
|
}
|
|
1730
|
-
async function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
return (await readLockParticipantRecord(filePath)).owner;
|
|
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);
|
|
1741
3257
|
}
|
|
1742
|
-
async function
|
|
1743
|
-
|
|
1744
|
-
(
|
|
3258
|
+
async function waitForSharedSessionReady(metadata, timeoutMs) {
|
|
3259
|
+
await resolveCdpWebSocketUrl(
|
|
3260
|
+
buildSharedSessionDiscoveryUrl(metadata.debugPort),
|
|
3261
|
+
timeoutMs
|
|
1745
3262
|
);
|
|
1746
3263
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
owner: null
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1759
|
-
return {
|
|
1760
|
-
exists: true,
|
|
1761
|
-
owner: {
|
|
1762
|
-
pid,
|
|
1763
|
-
processStartedAtMs
|
|
1764
|
-
}
|
|
1765
|
-
};
|
|
1766
|
-
} catch (error) {
|
|
1767
|
-
return {
|
|
1768
|
-
exists: getErrorCode(error) !== "ENOENT",
|
|
1769
|
-
owner: null
|
|
1770
|
-
};
|
|
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");
|
|
1771
3273
|
}
|
|
3274
|
+
return args;
|
|
1772
3275
|
}
|
|
1773
|
-
async function
|
|
1774
|
-
|
|
1775
|
-
return false;
|
|
1776
|
-
}
|
|
1777
|
-
let reclaimed = false;
|
|
3276
|
+
async function requestBrowserShutdown(browser) {
|
|
3277
|
+
let session = null;
|
|
1778
3278
|
try {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
await (0, import_promises.rm)(lockDirPath, {
|
|
1787
|
-
recursive: true,
|
|
1788
|
-
force: true
|
|
1789
|
-
}).catch(() => void 0);
|
|
1790
|
-
reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
|
|
1791
|
-
return reclaimed;
|
|
3279
|
+
session = await browser.newBrowserCDPSession();
|
|
3280
|
+
await session.send("Browser.close");
|
|
3281
|
+
} catch {
|
|
1792
3282
|
} finally {
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
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;
|
|
1799
3315
|
}
|
|
1800
|
-
|
|
1801
|
-
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
1802
|
-
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
1803
|
-
while (true) {
|
|
1804
|
-
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${(0, import_node_crypto.randomUUID)()}`;
|
|
3316
|
+
if (processPolicy.killStrategy === "process-group") {
|
|
1805
3317
|
try {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
await (0, import_promises.rename)(tempReclaimerDirPath, reclaimerDirPath);
|
|
1810
|
-
return true;
|
|
1811
|
-
} catch (error) {
|
|
1812
|
-
if (getErrorCode(error) === "ENOENT") {
|
|
1813
|
-
return false;
|
|
1814
|
-
}
|
|
1815
|
-
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
1816
|
-
throw error;
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
} catch (error) {
|
|
1820
|
-
const code = getErrorCode(error);
|
|
1821
|
-
if (code === "ENOENT") {
|
|
1822
|
-
return false;
|
|
1823
|
-
}
|
|
1824
|
-
throw error;
|
|
1825
|
-
} finally {
|
|
1826
|
-
await (0, import_promises.rm)(tempReclaimerDirPath, {
|
|
1827
|
-
recursive: true,
|
|
1828
|
-
force: true
|
|
1829
|
-
}).catch(() => void 0);
|
|
1830
|
-
}
|
|
1831
|
-
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
1832
|
-
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
1833
|
-
return false;
|
|
3318
|
+
process.kill(-pid, "SIGKILL");
|
|
3319
|
+
return;
|
|
3320
|
+
} catch {
|
|
1834
3321
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
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;
|
|
1837
3333
|
}
|
|
1838
|
-
await (
|
|
1839
|
-
recursive: true,
|
|
1840
|
-
force: true
|
|
1841
|
-
}).catch(() => void 0);
|
|
3334
|
+
await sleep5(50);
|
|
1842
3335
|
}
|
|
1843
3336
|
}
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1846
|
-
|
|
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);
|
|
1847
3344
|
}
|
|
1848
|
-
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
1849
3345
|
}
|
|
1850
|
-
async function
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1853
|
-
PROCESS_STARTED_AT_MS
|
|
1854
|
-
)) {
|
|
1855
|
-
return "live";
|
|
3346
|
+
async function waitForSpawnedProcessOwner(pid, timeoutMs) {
|
|
3347
|
+
if (!pid || pid <= 0) {
|
|
3348
|
+
throw new Error("Chrome did not expose a child process id.");
|
|
1856
3349
|
}
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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);
|
|
1863
3357
|
}
|
|
1864
|
-
|
|
1865
|
-
}
|
|
1866
|
-
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1867
|
-
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
1868
|
-
}
|
|
1869
|
-
function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
|
|
1870
|
-
return (0, import_node_path.join)(
|
|
1871
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
1872
|
-
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
|
|
3358
|
+
throw new Error(
|
|
3359
|
+
`Chrome process ${pid} did not report a stable process start time.`
|
|
1873
3360
|
);
|
|
1874
3361
|
}
|
|
1875
|
-
function
|
|
1876
|
-
return
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
return (0, import_node_path.join)(
|
|
1880
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
1881
|
-
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-backup-${Date.now()}`
|
|
3362
|
+
async function withSharedSessionLock(persistentUserDataDir, action) {
|
|
3363
|
+
return await withDirLock(
|
|
3364
|
+
buildSharedSessionLockPath(persistentUserDataDir),
|
|
3365
|
+
action
|
|
1882
3366
|
);
|
|
1883
3367
|
}
|
|
1884
|
-
function
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
}
|
|
1897
|
-
function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
|
|
1898
|
-
return (0, import_node_path.join)(
|
|
1899
|
-
runtimesRootDir,
|
|
1900
|
-
`${buildRuntimeProfileDirNamePrefix(sourceUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
|
|
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
|
+
}
|
|
1901
3380
|
);
|
|
3381
|
+
return {
|
|
3382
|
+
client,
|
|
3383
|
+
metadata,
|
|
3384
|
+
reuseExistingPage: liveClients.length === 0
|
|
3385
|
+
};
|
|
1902
3386
|
}
|
|
1903
|
-
async function
|
|
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);
|
|
1904
3394
|
let entries;
|
|
1905
3395
|
try {
|
|
1906
|
-
entries = await (0,
|
|
3396
|
+
entries = await (0, import_promises5.readdir)(clientsDirPath, {
|
|
1907
3397
|
encoding: "utf8",
|
|
1908
3398
|
withFileTypes: true
|
|
1909
3399
|
});
|
|
1910
3400
|
} catch {
|
|
1911
|
-
return;
|
|
3401
|
+
return [];
|
|
1912
3402
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
await (0,
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
return
|
|
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;
|
|
1931
3421
|
}
|
|
1932
|
-
function
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
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 {
|
|
1937
3427
|
return null;
|
|
1938
3428
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
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") {
|
|
1945
3439
|
return null;
|
|
1946
3440
|
}
|
|
1947
|
-
|
|
3441
|
+
const parsed = value;
|
|
3442
|
+
const owner = parseProcessOwner(parsed.owner);
|
|
3443
|
+
if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
|
|
1948
3444
|
return null;
|
|
1949
3445
|
}
|
|
1950
|
-
return {
|
|
3446
|
+
return {
|
|
3447
|
+
clientId: parsed.clientId,
|
|
3448
|
+
createdAt: parsed.createdAt,
|
|
3449
|
+
owner
|
|
3450
|
+
};
|
|
1951
3451
|
}
|
|
1952
|
-
function
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1958
|
-
return code !== "ESRCH";
|
|
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
|
+
);
|
|
1959
3457
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
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
|
+
);
|
|
1964
3462
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
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);
|
|
1967
3506
|
}
|
|
1968
|
-
if (
|
|
1969
|
-
|
|
3507
|
+
if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
|
|
3508
|
+
await killOwnedBrowserProcess(closePlan.browserOwner);
|
|
3509
|
+
await waitForProcessToExit(closePlan.browserOwner, 2e3);
|
|
1970
3510
|
}
|
|
1971
|
-
|
|
3511
|
+
await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
|
|
1972
3512
|
}
|
|
1973
|
-
async function
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
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
|
+
);
|
|
1983
3556
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
return null;
|
|
3557
|
+
return contexts[0];
|
|
3558
|
+
}
|
|
3559
|
+
async function getSharedSessionPage(context, reuseExistingPage) {
|
|
3560
|
+
if (reuseExistingPage) {
|
|
3561
|
+
return await getExistingPageOrCreate(context);
|
|
1990
3562
|
}
|
|
1991
|
-
return
|
|
1992
|
-
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
1993
|
-
);
|
|
3563
|
+
return await context.newPage();
|
|
1994
3564
|
}
|
|
1995
|
-
function
|
|
1996
|
-
const
|
|
1997
|
-
if (
|
|
1998
|
-
return
|
|
3565
|
+
async function getExistingPageOrCreate(context) {
|
|
3566
|
+
const existingPage = context.pages()[0];
|
|
3567
|
+
if (existingPage) {
|
|
3568
|
+
return existingPage;
|
|
1999
3569
|
}
|
|
2000
|
-
|
|
2001
|
-
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
2002
|
-
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
3570
|
+
return await context.newPage();
|
|
2003
3571
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
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.";
|
|
3597
|
+
}
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2010
3600
|
}
|
|
2011
|
-
|
|
2012
|
-
bootTimeLine.slice("btime ".length),
|
|
2013
|
-
10
|
|
2014
|
-
);
|
|
2015
|
-
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
2016
|
-
} catch {
|
|
2017
|
-
return null;
|
|
3601
|
+
await sleep5(100);
|
|
2018
3602
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
linuxClockTicksPerSecondPromise ??= execFileAsync(
|
|
2022
|
-
"getconf",
|
|
2023
|
-
["CLK_TCK"]
|
|
2024
|
-
).then(
|
|
2025
|
-
({ stdout }) => {
|
|
2026
|
-
const value = Number.parseInt(stdout.trim(), 10);
|
|
2027
|
-
return Number.isFinite(value) && value > 0 ? value : null;
|
|
2028
|
-
},
|
|
2029
|
-
() => null
|
|
3603
|
+
throw new Error(
|
|
3604
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2030
3605
|
);
|
|
2031
|
-
return await linuxClockTicksPerSecondPromise;
|
|
2032
3606
|
}
|
|
2033
|
-
|
|
3607
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
3608
|
+
let parsed;
|
|
2034
3609
|
try {
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
{
|
|
3610
|
+
parsed = new URL(cdpUrl);
|
|
3611
|
+
} catch {
|
|
3612
|
+
throw new Error(
|
|
3613
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
2039
3614
|
);
|
|
2040
|
-
return parsePsStartedAtMs(stdout);
|
|
2041
|
-
} catch (error) {
|
|
2042
|
-
return null;
|
|
2043
3615
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
const raw = stdout.trim();
|
|
2047
|
-
if (!raw) {
|
|
2048
|
-
return null;
|
|
3616
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
3617
|
+
return parsed;
|
|
2049
3618
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
}
|
|
2053
|
-
async function readWindowsProcessStartedAtMs(pid) {
|
|
2054
|
-
const script = [
|
|
2055
|
-
"$process = Get-Process -Id " + String(pid) + " -ErrorAction SilentlyContinue",
|
|
2056
|
-
"if ($null -eq $process) { exit 3 }",
|
|
2057
|
-
'$process.StartTime.ToUniversalTime().ToString("o")'
|
|
2058
|
-
].join("; ");
|
|
2059
|
-
try {
|
|
2060
|
-
const { stdout } = await execFileAsync(
|
|
2061
|
-
"powershell.exe",
|
|
2062
|
-
["-NoLogo", "-NoProfile", "-Command", script]
|
|
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).`
|
|
2063
3622
|
);
|
|
2064
|
-
return parsePsStartedAtMs(stdout);
|
|
2065
|
-
} catch (error) {
|
|
2066
|
-
return null;
|
|
2067
3623
|
}
|
|
3624
|
+
const normalized = new URL(parsed.toString());
|
|
3625
|
+
normalized.pathname = "/json/version";
|
|
3626
|
+
normalized.search = "";
|
|
3627
|
+
normalized.hash = "";
|
|
3628
|
+
return normalized;
|
|
2068
3629
|
}
|
|
2069
|
-
function
|
|
2070
|
-
return
|
|
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
|
+
});
|
|
2071
3651
|
}
|
|
2072
|
-
async function
|
|
3652
|
+
async function sleep5(ms) {
|
|
2073
3653
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2074
3654
|
}
|
|
2075
3655
|
|
|
2076
3656
|
// src/browser/pool.ts
|
|
2077
3657
|
var BrowserPool = class {
|
|
2078
3658
|
browser = null;
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
managedRuntimeProfile = null;
|
|
3659
|
+
activeSessionClose = null;
|
|
3660
|
+
closeInFlight = null;
|
|
2082
3661
|
defaults;
|
|
2083
3662
|
constructor(defaults = {}) {
|
|
2084
3663
|
this.defaults = defaults;
|
|
2085
3664
|
}
|
|
2086
3665
|
async launch(options = {}) {
|
|
2087
|
-
if (this.browser || this.
|
|
3666
|
+
if (this.browser || this.activeSessionClose) {
|
|
2088
3667
|
await this.close();
|
|
2089
3668
|
}
|
|
2090
3669
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -2131,30 +3710,26 @@ var BrowserPool = class {
|
|
|
2131
3710
|
return this.launchSandbox(options);
|
|
2132
3711
|
}
|
|
2133
3712
|
async close() {
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
this.
|
|
2140
|
-
this.launchedProcess = null;
|
|
3713
|
+
if (this.closeInFlight) {
|
|
3714
|
+
await this.closeInFlight;
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
const closeOperation = this.closeCurrent();
|
|
3718
|
+
this.closeInFlight = closeOperation;
|
|
2141
3719
|
try {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
3720
|
+
await closeOperation;
|
|
3721
|
+
this.browser = null;
|
|
3722
|
+
this.activeSessionClose = null;
|
|
2145
3723
|
} finally {
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
if (this.managedRuntimeProfile === managedRuntimeProfile) {
|
|
2154
|
-
this.managedRuntimeProfile = null;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
3724
|
+
this.closeInFlight = null;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
async closeCurrent() {
|
|
3728
|
+
if (this.activeSessionClose) {
|
|
3729
|
+
await this.activeSessionClose();
|
|
3730
|
+
return;
|
|
2157
3731
|
}
|
|
3732
|
+
await this.browser?.close().catch(() => void 0);
|
|
2158
3733
|
}
|
|
2159
3734
|
async connectToRunning(cdpUrl, timeout) {
|
|
2160
3735
|
let browser = null;
|
|
@@ -2169,11 +3744,14 @@ var BrowserPool = class {
|
|
|
2169
3744
|
}
|
|
2170
3745
|
cdpProxy = new CDPProxy(browserWsUrl, targetId);
|
|
2171
3746
|
const proxyWsUrl = await cdpProxy.start();
|
|
2172
|
-
browser = await
|
|
3747
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
2173
3748
|
timeout: timeout ?? 3e4
|
|
2174
3749
|
});
|
|
2175
3750
|
this.browser = browser;
|
|
2176
|
-
this.
|
|
3751
|
+
this.activeSessionClose = async () => {
|
|
3752
|
+
await browser?.close().catch(() => void 0);
|
|
3753
|
+
cdpProxy?.close();
|
|
3754
|
+
};
|
|
2177
3755
|
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
2178
3756
|
return { browser, context, page, isExternal: true };
|
|
2179
3757
|
} catch (error) {
|
|
@@ -2182,7 +3760,7 @@ var BrowserPool = class {
|
|
|
2182
3760
|
}
|
|
2183
3761
|
cdpProxy?.close();
|
|
2184
3762
|
this.browser = null;
|
|
2185
|
-
this.
|
|
3763
|
+
this.activeSessionClose = null;
|
|
2186
3764
|
throw error;
|
|
2187
3765
|
}
|
|
2188
3766
|
}
|
|
@@ -2202,60 +3780,29 @@ var BrowserPool = class {
|
|
|
2202
3780
|
sourceUserDataDir,
|
|
2203
3781
|
profileDirectory
|
|
2204
3782
|
);
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const launchArgs = buildRealBrowserLaunchArgs({
|
|
2215
|
-
userDataDir: runtimeProfile.userDataDir,
|
|
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,
|
|
2216
3792
|
profileDirectory,
|
|
2217
|
-
|
|
2218
|
-
headless
|
|
3793
|
+
timeoutMs: options.timeout ?? 3e4
|
|
2219
3794
|
});
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
`http://127.0.0.1:${debugPort}`,
|
|
2229
|
-
options.timeout ?? 3e4
|
|
2230
|
-
);
|
|
2231
|
-
browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
|
|
2232
|
-
timeout: options.timeout ?? 3e4
|
|
2233
|
-
});
|
|
2234
|
-
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
2235
|
-
browser
|
|
2236
|
-
);
|
|
2237
|
-
if (options.initialUrl) {
|
|
2238
|
-
await page.goto(options.initialUrl, {
|
|
2239
|
-
waitUntil: "domcontentloaded",
|
|
2240
|
-
timeout: options.timeout ?? 3e4
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
this.browser = browser;
|
|
2244
|
-
this.launchedProcess = processHandle;
|
|
2245
|
-
this.managedRuntimeProfile = runtimeProfile;
|
|
2246
|
-
return { browser, context, page, isExternal: false };
|
|
2247
|
-
} catch (error) {
|
|
2248
|
-
await browser?.close().catch(() => void 0);
|
|
2249
|
-
await killProcessTree(processHandle);
|
|
2250
|
-
await (0, import_promises2.rm)(runtimeProfile.userDataDir, {
|
|
2251
|
-
recursive: true,
|
|
2252
|
-
force: true
|
|
2253
|
-
}).catch(() => void 0);
|
|
2254
|
-
throw error;
|
|
2255
|
-
}
|
|
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
|
+
};
|
|
2256
3803
|
}
|
|
2257
3804
|
async launchSandbox(options) {
|
|
2258
|
-
const browser = await
|
|
3805
|
+
const browser = await import_playwright2.chromium.launch({
|
|
2259
3806
|
headless: resolveLaunchHeadless(
|
|
2260
3807
|
"chromium",
|
|
2261
3808
|
options.headless,
|
|
@@ -2268,11 +3815,14 @@ var BrowserPool = class {
|
|
|
2268
3815
|
const context = await browser.newContext(options.context || {});
|
|
2269
3816
|
const page = await context.newPage();
|
|
2270
3817
|
this.browser = browser;
|
|
3818
|
+
this.activeSessionClose = async () => {
|
|
3819
|
+
await browser.close().catch(() => void 0);
|
|
3820
|
+
};
|
|
2271
3821
|
return { browser, context, page, isExternal: false };
|
|
2272
3822
|
}
|
|
2273
3823
|
};
|
|
2274
3824
|
async function pickBrowserContextAndPage(browser) {
|
|
2275
|
-
const context =
|
|
3825
|
+
const context = getPrimaryBrowserContext2(browser);
|
|
2276
3826
|
const page = await getAttachedPageOrCreate(context);
|
|
2277
3827
|
return { context, page };
|
|
2278
3828
|
}
|
|
@@ -2285,11 +3835,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
|
2285
3835
|
}
|
|
2286
3836
|
return mode === "real";
|
|
2287
3837
|
}
|
|
2288
|
-
async function createOwnedBrowserContextAndPage(browser) {
|
|
2289
|
-
const context = getPrimaryBrowserContext(browser);
|
|
2290
|
-
const page = await getExistingPageOrCreate(context);
|
|
2291
|
-
return { context, page };
|
|
2292
|
-
}
|
|
2293
3838
|
async function getAttachedPageOrCreate(context) {
|
|
2294
3839
|
const pages = context.pages();
|
|
2295
3840
|
const inspectablePage = pages.find(
|
|
@@ -2304,14 +3849,7 @@ async function getAttachedPageOrCreate(context) {
|
|
|
2304
3849
|
}
|
|
2305
3850
|
return await context.newPage();
|
|
2306
3851
|
}
|
|
2307
|
-
|
|
2308
|
-
const existingPage = context.pages()[0];
|
|
2309
|
-
if (existingPage) {
|
|
2310
|
-
return existingPage;
|
|
2311
|
-
}
|
|
2312
|
-
return await context.newPage();
|
|
2313
|
-
}
|
|
2314
|
-
function getPrimaryBrowserContext(browser) {
|
|
3852
|
+
function getPrimaryBrowserContext2(browser) {
|
|
2315
3853
|
const contexts = browser.contexts();
|
|
2316
3854
|
if (contexts.length === 0) {
|
|
2317
3855
|
throw new Error(
|
|
@@ -2330,125 +3868,6 @@ function safePageUrl(page) {
|
|
|
2330
3868
|
return "";
|
|
2331
3869
|
}
|
|
2332
3870
|
}
|
|
2333
|
-
function normalizeDiscoveryUrl(cdpUrl) {
|
|
2334
|
-
let parsed;
|
|
2335
|
-
try {
|
|
2336
|
-
parsed = new URL(cdpUrl);
|
|
2337
|
-
} catch {
|
|
2338
|
-
throw new Error(
|
|
2339
|
-
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
2340
|
-
);
|
|
2341
|
-
}
|
|
2342
|
-
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
2343
|
-
return parsed;
|
|
2344
|
-
}
|
|
2345
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2346
|
-
throw new Error(
|
|
2347
|
-
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
const normalized = new URL(parsed.toString());
|
|
2351
|
-
normalized.pathname = "/json/version";
|
|
2352
|
-
normalized.search = "";
|
|
2353
|
-
normalized.hash = "";
|
|
2354
|
-
return normalized;
|
|
2355
|
-
}
|
|
2356
|
-
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
2357
|
-
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
2358
|
-
return cdpUrl;
|
|
2359
|
-
}
|
|
2360
|
-
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
2361
|
-
const deadline = Date.now() + timeoutMs;
|
|
2362
|
-
let lastError = "CDP discovery did not respond.";
|
|
2363
|
-
while (Date.now() < deadline) {
|
|
2364
|
-
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
2365
|
-
try {
|
|
2366
|
-
const response = await fetch(versionUrl, {
|
|
2367
|
-
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
2368
|
-
});
|
|
2369
|
-
if (!response.ok) {
|
|
2370
|
-
lastError = `${response.status} ${response.statusText}`;
|
|
2371
|
-
} else {
|
|
2372
|
-
const payload = await response.json();
|
|
2373
|
-
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
2374
|
-
if (wsUrl && wsUrl.trim()) {
|
|
2375
|
-
return wsUrl;
|
|
2376
|
-
}
|
|
2377
|
-
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
2378
|
-
}
|
|
2379
|
-
} catch (error) {
|
|
2380
|
-
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2381
|
-
}
|
|
2382
|
-
await sleep2(100);
|
|
2383
|
-
}
|
|
2384
|
-
throw new Error(
|
|
2385
|
-
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
async function reserveDebugPort() {
|
|
2389
|
-
return await new Promise((resolve, reject) => {
|
|
2390
|
-
const server = (0, import_node_net.createServer)();
|
|
2391
|
-
server.unref();
|
|
2392
|
-
server.on("error", reject);
|
|
2393
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2394
|
-
const address = server.address();
|
|
2395
|
-
if (!address || typeof address === "string") {
|
|
2396
|
-
server.close();
|
|
2397
|
-
reject(new Error("Failed to reserve a local debug port."));
|
|
2398
|
-
return;
|
|
2399
|
-
}
|
|
2400
|
-
server.close((error) => {
|
|
2401
|
-
if (error) {
|
|
2402
|
-
reject(error);
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
|
-
resolve(address.port);
|
|
2406
|
-
});
|
|
2407
|
-
});
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
function buildRealBrowserLaunchArgs(options) {
|
|
2411
|
-
const args = [
|
|
2412
|
-
`--user-data-dir=${options.userDataDir}`,
|
|
2413
|
-
`--profile-directory=${options.profileDirectory}`,
|
|
2414
|
-
`--remote-debugging-port=${options.debugPort}`,
|
|
2415
|
-
"--disable-blink-features=AutomationControlled"
|
|
2416
|
-
];
|
|
2417
|
-
if (options.headless) {
|
|
2418
|
-
args.push("--headless=new");
|
|
2419
|
-
}
|
|
2420
|
-
return args;
|
|
2421
|
-
}
|
|
2422
|
-
async function killProcessTree(processHandle) {
|
|
2423
|
-
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
if (process.platform === "win32") {
|
|
2427
|
-
await new Promise((resolve) => {
|
|
2428
|
-
const killer = (0, import_node_child_process2.spawn)(
|
|
2429
|
-
"taskkill",
|
|
2430
|
-
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
2431
|
-
{
|
|
2432
|
-
stdio: "ignore"
|
|
2433
|
-
}
|
|
2434
|
-
);
|
|
2435
|
-
killer.on("error", () => resolve());
|
|
2436
|
-
killer.on("exit", () => resolve());
|
|
2437
|
-
});
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
try {
|
|
2441
|
-
process.kill(-processHandle.pid, "SIGKILL");
|
|
2442
|
-
} catch {
|
|
2443
|
-
try {
|
|
2444
|
-
processHandle.kill("SIGKILL");
|
|
2445
|
-
} catch {
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
async function sleep2(ms) {
|
|
2450
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2451
|
-
}
|
|
2452
3871
|
|
|
2453
3872
|
// src/config.ts
|
|
2454
3873
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
@@ -3720,7 +5139,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
3720
5139
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
3721
5140
|
Math.max(0, deadline - Date.now())
|
|
3722
5141
|
);
|
|
3723
|
-
await
|
|
5142
|
+
await sleep6(retryDelay);
|
|
3724
5143
|
}
|
|
3725
5144
|
}
|
|
3726
5145
|
}
|
|
@@ -3753,7 +5172,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
3753
5172
|
() => ({ kind: "resolved" }),
|
|
3754
5173
|
(error) => ({ kind: "rejected", error })
|
|
3755
5174
|
);
|
|
3756
|
-
const timeoutPromise =
|
|
5175
|
+
const timeoutPromise = sleep6(
|
|
3757
5176
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
3758
5177
|
).then(() => ({ kind: "timeout" }));
|
|
3759
5178
|
const result = await Promise.race([
|
|
@@ -3895,7 +5314,7 @@ function isIgnorableFrameError(error) {
|
|
|
3895
5314
|
const message = error.message;
|
|
3896
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");
|
|
3897
5316
|
}
|
|
3898
|
-
function
|
|
5317
|
+
function sleep6(ms) {
|
|
3899
5318
|
return new Promise((resolve) => {
|
|
3900
5319
|
setTimeout(resolve, ms);
|
|
3901
5320
|
});
|
|
@@ -8156,7 +9575,7 @@ async function closeTab(context, activePage, index) {
|
|
|
8156
9575
|
}
|
|
8157
9576
|
|
|
8158
9577
|
// src/actions/cookies.ts
|
|
8159
|
-
var
|
|
9578
|
+
var import_promises6 = require("fs/promises");
|
|
8160
9579
|
async function getCookies(context, url) {
|
|
8161
9580
|
return context.cookies(url ? [url] : void 0);
|
|
8162
9581
|
}
|
|
@@ -8168,10 +9587,10 @@ async function clearCookies(context) {
|
|
|
8168
9587
|
}
|
|
8169
9588
|
async function exportCookies(context, filePath, url) {
|
|
8170
9589
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
8171
|
-
await (0,
|
|
9590
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
8172
9591
|
}
|
|
8173
9592
|
async function importCookies(context, filePath) {
|
|
8174
|
-
const raw = await (0,
|
|
9593
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
8175
9594
|
const cookies = JSON.parse(raw);
|
|
8176
9595
|
await context.addCookies(cookies);
|
|
8177
9596
|
}
|
|
@@ -8482,7 +9901,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8482
9901
|
this.idleSince = 0;
|
|
8483
9902
|
}
|
|
8484
9903
|
const remaining = Math.max(1, options.deadline - now);
|
|
8485
|
-
await
|
|
9904
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
8486
9905
|
}
|
|
8487
9906
|
}
|
|
8488
9907
|
handleRequestStarted = (request) => {
|
|
@@ -8527,7 +9946,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8527
9946
|
return false;
|
|
8528
9947
|
}
|
|
8529
9948
|
};
|
|
8530
|
-
async function
|
|
9949
|
+
async function sleep7(ms) {
|
|
8531
9950
|
await new Promise((resolve) => {
|
|
8532
9951
|
setTimeout(resolve, ms);
|
|
8533
9952
|
});
|
|
@@ -10232,13 +11651,13 @@ function dedupeNewest(entries) {
|
|
|
10232
11651
|
}
|
|
10233
11652
|
|
|
10234
11653
|
// src/cloud/cdp-client.ts
|
|
10235
|
-
var
|
|
11654
|
+
var import_playwright3 = require("playwright");
|
|
10236
11655
|
var CloudCdpClient = class {
|
|
10237
11656
|
async connect(args) {
|
|
10238
11657
|
const endpoint = withTokenQuery(args.wsUrl, args.token);
|
|
10239
11658
|
let browser;
|
|
10240
11659
|
try {
|
|
10241
|
-
browser = await
|
|
11660
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
10242
11661
|
} catch (error) {
|
|
10243
11662
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
10244
11663
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -12055,7 +13474,7 @@ async function executeAgentAction(page, action) {
|
|
|
12055
13474
|
}
|
|
12056
13475
|
case "wait": {
|
|
12057
13476
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
12058
|
-
await
|
|
13477
|
+
await sleep8(ms);
|
|
12059
13478
|
return;
|
|
12060
13479
|
}
|
|
12061
13480
|
case "goto": {
|
|
@@ -12220,7 +13639,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
12220
13639
|
}
|
|
12221
13640
|
}
|
|
12222
13641
|
}
|
|
12223
|
-
function
|
|
13642
|
+
function sleep8(ms) {
|
|
12224
13643
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
12225
13644
|
}
|
|
12226
13645
|
|
|
@@ -12251,7 +13670,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
12251
13670
|
if (isMutatingAgentAction(action)) {
|
|
12252
13671
|
this.onMutatingAction?.(action);
|
|
12253
13672
|
}
|
|
12254
|
-
await
|
|
13673
|
+
await sleep9(this.config.waitBetweenActionsMs);
|
|
12255
13674
|
});
|
|
12256
13675
|
try {
|
|
12257
13676
|
const result = await this.client.execute({
|
|
@@ -12313,7 +13732,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
12313
13732
|
await this.cursorController.preview({ x, y }, "agent");
|
|
12314
13733
|
}
|
|
12315
13734
|
};
|
|
12316
|
-
function
|
|
13735
|
+
function sleep9(ms) {
|
|
12317
13736
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
12318
13737
|
}
|
|
12319
13738
|
|
|
@@ -12749,7 +14168,7 @@ var CursorController = class {
|
|
|
12749
14168
|
for (const step of motion.points) {
|
|
12750
14169
|
await this.renderer.move(step, this.style);
|
|
12751
14170
|
if (motion.stepDelayMs > 0) {
|
|
12752
|
-
await
|
|
14171
|
+
await sleep10(motion.stepDelayMs);
|
|
12753
14172
|
}
|
|
12754
14173
|
}
|
|
12755
14174
|
if (shouldPulse(intent)) {
|
|
@@ -12907,7 +14326,7 @@ function clamp2(value, min, max) {
|
|
|
12907
14326
|
function shouldPulse(intent) {
|
|
12908
14327
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
12909
14328
|
}
|
|
12910
|
-
function
|
|
14329
|
+
function sleep10(ms) {
|
|
12911
14330
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12912
14331
|
}
|
|
12913
14332
|
|
|
@@ -13381,15 +14800,19 @@ var Opensteer = class _Opensteer {
|
|
|
13381
14800
|
}
|
|
13382
14801
|
return;
|
|
13383
14802
|
}
|
|
14803
|
+
let closedOwnedBrowser = false;
|
|
13384
14804
|
try {
|
|
13385
14805
|
if (this.ownsBrowser) {
|
|
13386
14806
|
await this.pool.close();
|
|
14807
|
+
closedOwnedBrowser = true;
|
|
13387
14808
|
}
|
|
13388
14809
|
} finally {
|
|
13389
14810
|
this.browser = null;
|
|
13390
14811
|
this.pageRef = null;
|
|
13391
14812
|
this.contextRef = null;
|
|
13392
|
-
this.ownsBrowser
|
|
14813
|
+
if (!this.ownsBrowser || closedOwnedBrowser) {
|
|
14814
|
+
this.ownsBrowser = false;
|
|
14815
|
+
}
|
|
13393
14816
|
if (this.cursorController) {
|
|
13394
14817
|
await this.cursorController.dispose().catch(() => void 0);
|
|
13395
14818
|
}
|
|
@@ -15846,7 +17269,7 @@ var CdpOverlayCursorRenderer = class {
|
|
|
15846
17269
|
outlineColor: toProtocolRgba(pulseOutline)
|
|
15847
17270
|
});
|
|
15848
17271
|
});
|
|
15849
|
-
await
|
|
17272
|
+
await sleep11(PULSE_DELAY_MS);
|
|
15850
17273
|
await this.move(point, style);
|
|
15851
17274
|
}
|
|
15852
17275
|
async clear() {
|
|
@@ -15970,7 +17393,7 @@ function clampAlpha(value) {
|
|
|
15970
17393
|
function roundPointValue(value) {
|
|
15971
17394
|
return Math.round(value * 100) / 100;
|
|
15972
17395
|
}
|
|
15973
|
-
function
|
|
17396
|
+
function sleep11(ms) {
|
|
15974
17397
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15975
17398
|
}
|
|
15976
17399
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -16046,8 +17469,10 @@ function sleep8(ms) {
|
|
|
16046
17469
|
getElementValue,
|
|
16047
17470
|
getModelProvider,
|
|
16048
17471
|
getOrCreatePersistentProfile,
|
|
17472
|
+
getOwnedRealBrowserProcessPolicy,
|
|
16049
17473
|
getPageHtml,
|
|
16050
17474
|
getPageTitle,
|
|
17475
|
+
hasActiveRuntimeProfileCreations,
|
|
16051
17476
|
importCookies,
|
|
16052
17477
|
isCloudActionMethod,
|
|
16053
17478
|
isCloudErrorCode,
|