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