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/profile.cjs
CHANGED
|
@@ -424,8 +424,8 @@ __export(profile_exports, {
|
|
|
424
424
|
runOpensteerProfileCli: () => runOpensteerProfileCli
|
|
425
425
|
});
|
|
426
426
|
module.exports = __toCommonJS(profile_exports);
|
|
427
|
-
var
|
|
428
|
-
var
|
|
427
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
428
|
+
var import_promises8 = require("readline/promises");
|
|
429
429
|
|
|
430
430
|
// src/config.ts
|
|
431
431
|
var import_fs = __toESM(require("fs"), 1);
|
|
@@ -1323,10 +1323,7 @@ function getCallerFilePath() {
|
|
|
1323
1323
|
var import_crypto = require("crypto");
|
|
1324
1324
|
|
|
1325
1325
|
// src/browser/pool.ts
|
|
1326
|
-
var
|
|
1327
|
-
var import_promises2 = require("fs/promises");
|
|
1328
|
-
var import_node_net = require("net");
|
|
1329
|
-
var import_playwright = require("playwright");
|
|
1326
|
+
var import_playwright2 = require("playwright");
|
|
1330
1327
|
|
|
1331
1328
|
// src/browser/cdp-proxy.ts
|
|
1332
1329
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -1819,669 +1816,247 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
|
|
|
1819
1816
|
}
|
|
1820
1817
|
|
|
1821
1818
|
// src/browser/persistent-profile.ts
|
|
1819
|
+
var import_node_crypto3 = require("crypto");
|
|
1820
|
+
var import_node_child_process2 = require("child_process");
|
|
1821
|
+
var import_node_fs3 = require("fs");
|
|
1822
|
+
var import_promises4 = require("fs/promises");
|
|
1823
|
+
var import_node_os = require("os");
|
|
1824
|
+
var import_node_path4 = require("path");
|
|
1825
|
+
var import_node_util2 = require("util");
|
|
1826
|
+
|
|
1827
|
+
// src/browser/persistent-profile-coordination.ts
|
|
1828
|
+
var import_node_path2 = require("path");
|
|
1829
|
+
|
|
1830
|
+
// src/browser/dir-lock.ts
|
|
1822
1831
|
var import_node_crypto = require("crypto");
|
|
1823
|
-
var import_node_child_process = require("child_process");
|
|
1824
1832
|
var import_node_fs = require("fs");
|
|
1825
|
-
var
|
|
1826
|
-
var import_node_os = require("os");
|
|
1833
|
+
var import_promises2 = require("fs/promises");
|
|
1827
1834
|
var import_node_path = require("path");
|
|
1835
|
+
|
|
1836
|
+
// src/browser/process-owner.ts
|
|
1837
|
+
var import_node_child_process = require("child_process");
|
|
1838
|
+
var import_promises = require("fs/promises");
|
|
1828
1839
|
var import_node_util = require("util");
|
|
1829
1840
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
1830
|
-
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
1831
|
-
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
1832
|
-
var LOCK_OWNER_FILE = "owner.json";
|
|
1833
|
-
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
1834
1841
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
1835
1842
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
1836
|
-
var
|
|
1843
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
1837
1844
|
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
1838
1845
|
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
1839
|
-
var
|
|
1840
|
-
"SingletonCookie",
|
|
1841
|
-
"SingletonLock",
|
|
1842
|
-
"SingletonSocket",
|
|
1843
|
-
"DevToolsActivePort",
|
|
1844
|
-
"lockfile"
|
|
1845
|
-
]);
|
|
1846
|
-
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
1847
|
-
...CHROME_SINGLETON_ENTRIES,
|
|
1848
|
-
OPENSTEER_META_FILE,
|
|
1849
|
-
OPENSTEER_RUNTIME_META_FILE
|
|
1850
|
-
]);
|
|
1851
|
-
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1852
|
-
"Crash Reports",
|
|
1853
|
-
"Crashpad",
|
|
1854
|
-
"BrowserMetrics",
|
|
1855
|
-
"GrShaderCache",
|
|
1856
|
-
"ShaderCache",
|
|
1857
|
-
"GraphiteDawnCache",
|
|
1858
|
-
"component_crx_cache",
|
|
1859
|
-
"Crowd Deny",
|
|
1860
|
-
"hyphen-data",
|
|
1861
|
-
"OnDeviceHeadSuggestModel",
|
|
1862
|
-
"OptimizationGuidePredictionModels",
|
|
1863
|
-
"Segmentation Platform",
|
|
1864
|
-
"SmartCardDeviceNames",
|
|
1865
|
-
"WidevineCdm",
|
|
1866
|
-
"pnacl"
|
|
1867
|
-
]);
|
|
1868
|
-
var CURRENT_PROCESS_LOCK_OWNER = {
|
|
1846
|
+
var CURRENT_PROCESS_OWNER = {
|
|
1869
1847
|
pid: process.pid,
|
|
1870
1848
|
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
1871
1849
|
};
|
|
1872
1850
|
var linuxClockTicksPerSecondPromise = null;
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
expandHome(profilesRootDir),
|
|
1877
|
-
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
1878
|
-
);
|
|
1879
|
-
const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
1880
|
-
const metadata = buildPersistentProfileMetadata(
|
|
1881
|
-
resolvedSourceUserDataDir,
|
|
1882
|
-
profileDirectory
|
|
1883
|
-
);
|
|
1884
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
|
|
1885
|
-
return await withPersistentProfileLock(targetUserDataDir, async () => {
|
|
1886
|
-
await cleanOrphanedOwnedDirs(
|
|
1887
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
1888
|
-
buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
|
|
1889
|
-
);
|
|
1890
|
-
if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
1891
|
-
throw new Error(
|
|
1892
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1893
|
-
);
|
|
1894
|
-
}
|
|
1895
|
-
const created = await createPersistentProfileClone(
|
|
1896
|
-
resolvedSourceUserDataDir,
|
|
1897
|
-
sourceProfileDir,
|
|
1898
|
-
targetUserDataDir,
|
|
1899
|
-
profileDirectory,
|
|
1900
|
-
metadata
|
|
1901
|
-
);
|
|
1902
|
-
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1903
|
-
return {
|
|
1904
|
-
created,
|
|
1905
|
-
userDataDir: targetUserDataDir
|
|
1906
|
-
};
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
1909
|
-
async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
|
|
1910
|
-
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1911
|
-
const runtimeRootDir = expandHome(runtimesRootDir);
|
|
1912
|
-
await (0, import_promises.mkdir)(runtimeRootDir, { recursive: true });
|
|
1913
|
-
return await withPersistentProfileLock(
|
|
1914
|
-
resolvedSourceUserDataDir,
|
|
1915
|
-
async () => {
|
|
1916
|
-
const sourceMetadata = await readPersistentProfileMetadata(
|
|
1917
|
-
resolvedSourceUserDataDir
|
|
1918
|
-
);
|
|
1919
|
-
await cleanOrphanedOwnedDirs(
|
|
1920
|
-
runtimeRootDir,
|
|
1921
|
-
buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
|
|
1922
|
-
);
|
|
1923
|
-
const runtimeUserDataDir = await (0, import_promises.mkdtemp)(
|
|
1924
|
-
buildRuntimeProfileDirPrefix(
|
|
1925
|
-
runtimeRootDir,
|
|
1926
|
-
resolvedSourceUserDataDir
|
|
1927
|
-
)
|
|
1928
|
-
);
|
|
1929
|
-
try {
|
|
1930
|
-
await copyUserDataDirSnapshot(
|
|
1931
|
-
resolvedSourceUserDataDir,
|
|
1932
|
-
runtimeUserDataDir
|
|
1933
|
-
);
|
|
1934
|
-
await writeRuntimeProfileMetadata(
|
|
1935
|
-
runtimeUserDataDir,
|
|
1936
|
-
await buildRuntimeProfileMetadata(
|
|
1937
|
-
runtimeUserDataDir,
|
|
1938
|
-
sourceMetadata?.profileDirectory ?? null
|
|
1939
|
-
)
|
|
1940
|
-
);
|
|
1941
|
-
return {
|
|
1942
|
-
persistentUserDataDir: resolvedSourceUserDataDir,
|
|
1943
|
-
userDataDir: runtimeUserDataDir
|
|
1944
|
-
};
|
|
1945
|
-
} catch (error) {
|
|
1946
|
-
await (0, import_promises.rm)(runtimeUserDataDir, {
|
|
1947
|
-
recursive: true,
|
|
1948
|
-
force: true
|
|
1949
|
-
}).catch(() => void 0);
|
|
1950
|
-
throw error;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
|
|
1956
|
-
const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
|
|
1957
|
-
const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
|
|
1958
|
-
await withPersistentProfileLock(resolvedPersistentUserDataDir, async () => {
|
|
1959
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(resolvedPersistentUserDataDir), { recursive: true });
|
|
1960
|
-
await cleanOrphanedOwnedDirs(
|
|
1961
|
-
(0, import_node_path.dirname)(resolvedPersistentUserDataDir),
|
|
1962
|
-
buildPersistentProfileTempDirNamePrefix(resolvedPersistentUserDataDir)
|
|
1963
|
-
);
|
|
1964
|
-
const metadata = await requirePersistentProfileMetadata(
|
|
1965
|
-
resolvedPersistentUserDataDir
|
|
1966
|
-
);
|
|
1967
|
-
const runtimeMetadata = await requireRuntimeProfileMetadata(
|
|
1968
|
-
resolvedRuntimeUserDataDir,
|
|
1969
|
-
metadata.profileDirectory
|
|
1970
|
-
);
|
|
1971
|
-
await mergePersistentProfileSnapshot(
|
|
1972
|
-
resolvedRuntimeUserDataDir,
|
|
1973
|
-
resolvedPersistentUserDataDir,
|
|
1974
|
-
metadata,
|
|
1975
|
-
runtimeMetadata
|
|
1976
|
-
);
|
|
1977
|
-
});
|
|
1978
|
-
await (0, import_promises.rm)(resolvedRuntimeUserDataDir, {
|
|
1979
|
-
recursive: true,
|
|
1980
|
-
force: true
|
|
1981
|
-
});
|
|
1982
|
-
}
|
|
1983
|
-
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
1984
|
-
const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
1985
|
-
const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
|
|
1986
|
-
const profileLabel = sanitizePathSegment(profileDirectory || "Default");
|
|
1987
|
-
return `${sourceLabel}-${profileLabel}-${hash}`;
|
|
1988
|
-
}
|
|
1989
|
-
function defaultPersistentProfilesRootDir() {
|
|
1990
|
-
return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
|
|
1991
|
-
}
|
|
1992
|
-
function defaultRuntimeProfilesRootDir() {
|
|
1993
|
-
return (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-runtimes");
|
|
1994
|
-
}
|
|
1995
|
-
function sanitizePathSegment(value) {
|
|
1996
|
-
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
1997
|
-
return sanitized.replace(/^-|-$/g, "") || "profile";
|
|
1998
|
-
}
|
|
1999
|
-
function isProfileDirectory(userDataDir, entry) {
|
|
2000
|
-
return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
|
|
2001
|
-
}
|
|
2002
|
-
async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
|
|
2003
|
-
await (0, import_promises.cp)(sourceUserDataDir, targetUserDataDir, {
|
|
2004
|
-
recursive: true,
|
|
2005
|
-
filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
|
|
2006
|
-
});
|
|
2007
|
-
}
|
|
2008
|
-
function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
|
|
2009
|
-
const candidateRelativePath = (0, import_node_path.relative)(userDataDir, candidatePath);
|
|
2010
|
-
if (!candidateRelativePath) {
|
|
2011
|
-
return true;
|
|
2012
|
-
}
|
|
2013
|
-
const segments = candidateRelativePath.split(import_node_path.sep).filter(Boolean);
|
|
2014
|
-
if (segments.length !== 1) {
|
|
2015
|
-
return true;
|
|
1851
|
+
function parseProcessOwner(value) {
|
|
1852
|
+
if (!value || typeof value !== "object") {
|
|
1853
|
+
return null;
|
|
2016
1854
|
}
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
entries = await (0, import_promises.readdir)(sourceUserDataDir);
|
|
2023
|
-
} catch {
|
|
2024
|
-
return;
|
|
1855
|
+
const parsed = value;
|
|
1856
|
+
const pid = Number(parsed.pid);
|
|
1857
|
+
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
1858
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1859
|
+
return null;
|
|
2025
1860
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
if (COPY_SKIP_ENTRIES.has(entry)) continue;
|
|
2029
|
-
if (entry === targetProfileDirectory) continue;
|
|
2030
|
-
const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
|
|
2031
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
|
|
2032
|
-
if ((0, import_node_fs.existsSync)(targetPath)) continue;
|
|
2033
|
-
let entryStat;
|
|
2034
|
-
try {
|
|
2035
|
-
entryStat = await (0, import_promises.stat)(sourcePath);
|
|
2036
|
-
} catch {
|
|
2037
|
-
continue;
|
|
2038
|
-
}
|
|
2039
|
-
if (entryStat.isFile()) {
|
|
2040
|
-
copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
2041
|
-
} else if (entryStat.isDirectory()) {
|
|
2042
|
-
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
2043
|
-
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
2044
|
-
copyTasks.push(
|
|
2045
|
-
(0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
2046
|
-
() => void 0
|
|
2047
|
-
)
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
1861
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
1862
|
+
return null;
|
|
2050
1863
|
}
|
|
2051
|
-
await Promise.all(copyTasks);
|
|
2052
|
-
}
|
|
2053
|
-
async function writePersistentProfileMetadata(userDataDir, metadata) {
|
|
2054
|
-
await (0, import_promises.writeFile)(
|
|
2055
|
-
(0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
|
|
2056
|
-
JSON.stringify(metadata, null, 2)
|
|
2057
|
-
);
|
|
2058
|
-
}
|
|
2059
|
-
function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
|
|
2060
1864
|
return {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
source: sourceUserDataDir
|
|
1865
|
+
pid,
|
|
1866
|
+
processStartedAtMs
|
|
2064
1867
|
};
|
|
2065
1868
|
}
|
|
2066
|
-
|
|
2067
|
-
if (
|
|
2068
|
-
return
|
|
2069
|
-
}
|
|
2070
|
-
const tempUserDataDir = await (0, import_promises.mkdtemp)(
|
|
2071
|
-
buildPersistentProfileTempDirPrefix(targetUserDataDir)
|
|
2072
|
-
);
|
|
2073
|
-
let published = false;
|
|
2074
|
-
try {
|
|
2075
|
-
await materializePersistentProfileSnapshot(
|
|
2076
|
-
sourceUserDataDir,
|
|
2077
|
-
sourceProfileDir,
|
|
2078
|
-
tempUserDataDir,
|
|
2079
|
-
profileDirectory,
|
|
2080
|
-
metadata
|
|
2081
|
-
);
|
|
2082
|
-
try {
|
|
2083
|
-
await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
|
|
2084
|
-
} catch (error) {
|
|
2085
|
-
if (wasDirPublishedByAnotherProcess(error, targetUserDataDir)) {
|
|
2086
|
-
return false;
|
|
2087
|
-
}
|
|
2088
|
-
throw error;
|
|
2089
|
-
}
|
|
2090
|
-
published = true;
|
|
2091
|
-
return true;
|
|
2092
|
-
} finally {
|
|
2093
|
-
if (!published) {
|
|
2094
|
-
await (0, import_promises.rm)(tempUserDataDir, {
|
|
2095
|
-
recursive: true,
|
|
2096
|
-
force: true
|
|
2097
|
-
}).catch(() => void 0);
|
|
2098
|
-
}
|
|
1869
|
+
function processOwnersEqual(left, right) {
|
|
1870
|
+
if (!left || !right) {
|
|
1871
|
+
return left === right;
|
|
2099
1872
|
}
|
|
1873
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
2100
1874
|
}
|
|
2101
|
-
async function
|
|
2102
|
-
if (
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
1875
|
+
async function getProcessLiveness(owner) {
|
|
1876
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
1877
|
+
owner.processStartedAtMs,
|
|
1878
|
+
PROCESS_STARTED_AT_MS
|
|
1879
|
+
)) {
|
|
1880
|
+
return "live";
|
|
2106
1881
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
1882
|
+
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
1883
|
+
if (typeof startedAtMs === "number") {
|
|
1884
|
+
return hasMatchingProcessStartTime(
|
|
1885
|
+
owner.processStartedAtMs,
|
|
1886
|
+
startedAtMs
|
|
1887
|
+
) ? "live" : "dead";
|
|
1888
|
+
}
|
|
1889
|
+
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
2112
1890
|
}
|
|
2113
|
-
|
|
2114
|
-
const tempUserDataDir = await (0, import_promises.mkdtemp)(
|
|
2115
|
-
buildPersistentProfileTempDirPrefix(persistentUserDataDir)
|
|
2116
|
-
);
|
|
2117
|
-
let published = false;
|
|
1891
|
+
function isProcessRunning(pid) {
|
|
2118
1892
|
try {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
const
|
|
2123
|
-
|
|
2124
|
-
metadata.profileDirectory
|
|
2125
|
-
);
|
|
2126
|
-
const runtimeEntries = await collectPersistentSnapshotEntries(
|
|
2127
|
-
runtimeUserDataDir,
|
|
2128
|
-
metadata.profileDirectory
|
|
2129
|
-
);
|
|
2130
|
-
const mergedEntries = resolveMergedSnapshotEntries(
|
|
2131
|
-
baseEntries,
|
|
2132
|
-
currentEntries,
|
|
2133
|
-
runtimeEntries
|
|
2134
|
-
);
|
|
2135
|
-
await materializeMergedPersistentProfileSnapshot(
|
|
2136
|
-
tempUserDataDir,
|
|
2137
|
-
currentEntries,
|
|
2138
|
-
runtimeEntries,
|
|
2139
|
-
mergedEntries
|
|
2140
|
-
);
|
|
2141
|
-
await writePersistentProfileMetadata(tempUserDataDir, metadata);
|
|
2142
|
-
await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
|
|
2143
|
-
published = true;
|
|
2144
|
-
} finally {
|
|
2145
|
-
if (!published) {
|
|
2146
|
-
await (0, import_promises.rm)(tempUserDataDir, {
|
|
2147
|
-
recursive: true,
|
|
2148
|
-
force: true
|
|
2149
|
-
}).catch(() => void 0);
|
|
2150
|
-
}
|
|
1893
|
+
process.kill(pid, 0);
|
|
1894
|
+
return true;
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
1897
|
+
return code !== "ESRCH";
|
|
2151
1898
|
}
|
|
2152
1899
|
}
|
|
2153
|
-
async function
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
profileDirectory: null
|
|
2158
|
-
};
|
|
1900
|
+
async function readProcessOwner(pid) {
|
|
1901
|
+
const processStartedAtMs = await readProcessStartedAtMs(pid);
|
|
1902
|
+
if (processStartedAtMs === null) {
|
|
1903
|
+
return null;
|
|
2159
1904
|
}
|
|
2160
|
-
const baseEntries = await collectPersistentSnapshotEntries(
|
|
2161
|
-
runtimeUserDataDir,
|
|
2162
|
-
profileDirectory
|
|
2163
|
-
);
|
|
2164
1905
|
return {
|
|
2165
|
-
|
|
2166
|
-
|
|
1906
|
+
pid,
|
|
1907
|
+
processStartedAtMs
|
|
2167
1908
|
};
|
|
2168
1909
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
(0, import_node_path.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
2172
|
-
JSON.stringify(metadata, null, 2)
|
|
2173
|
-
);
|
|
1910
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1911
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
2174
1912
|
}
|
|
2175
|
-
async function
|
|
2176
|
-
|
|
2177
|
-
const raw = await (0, import_promises.readFile)(
|
|
2178
|
-
(0, import_node_path.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
2179
|
-
"utf8"
|
|
2180
|
-
);
|
|
2181
|
-
const parsed = JSON.parse(raw);
|
|
2182
|
-
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
2183
|
-
if (profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
|
|
2184
|
-
return null;
|
|
2185
|
-
}
|
|
2186
|
-
const baseEntries = deserializeSnapshotManifestEntries(
|
|
2187
|
-
parsed.baseEntries
|
|
2188
|
-
);
|
|
2189
|
-
return {
|
|
2190
|
-
baseEntries: Object.fromEntries(baseEntries),
|
|
2191
|
-
profileDirectory
|
|
2192
|
-
};
|
|
2193
|
-
} catch {
|
|
1913
|
+
async function readProcessStartedAtMs(pid) {
|
|
1914
|
+
if (pid <= 0) {
|
|
2194
1915
|
return null;
|
|
2195
1916
|
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
const metadata = await readRuntimeProfileMetadata(userDataDir);
|
|
2199
|
-
if (!metadata) {
|
|
2200
|
-
throw new Error(
|
|
2201
|
-
`Runtime profile metadata was not found for "${userDataDir}".`
|
|
2202
|
-
);
|
|
2203
|
-
}
|
|
2204
|
-
if (metadata.profileDirectory !== expectedProfileDirectory) {
|
|
2205
|
-
throw new Error(
|
|
2206
|
-
`Runtime profile "${userDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
|
|
2207
|
-
);
|
|
2208
|
-
}
|
|
2209
|
-
return metadata;
|
|
2210
|
-
}
|
|
2211
|
-
async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
|
|
2212
|
-
let rootEntries;
|
|
2213
|
-
try {
|
|
2214
|
-
rootEntries = await (0, import_promises.readdir)(userDataDir, {
|
|
2215
|
-
encoding: "utf8",
|
|
2216
|
-
withFileTypes: true
|
|
2217
|
-
});
|
|
2218
|
-
} catch {
|
|
2219
|
-
return /* @__PURE__ */ new Map();
|
|
2220
|
-
}
|
|
2221
|
-
rootEntries.sort((left, right) => left.name.localeCompare(right.name));
|
|
2222
|
-
const collected = /* @__PURE__ */ new Map();
|
|
2223
|
-
for (const entry of rootEntries) {
|
|
2224
|
-
if (!shouldIncludePersistentRootEntry(
|
|
2225
|
-
userDataDir,
|
|
2226
|
-
profileDirectory,
|
|
2227
|
-
entry.name
|
|
2228
|
-
)) {
|
|
2229
|
-
continue;
|
|
2230
|
-
}
|
|
2231
|
-
await collectSnapshotEntry(
|
|
2232
|
-
(0, import_node_path.join)(userDataDir, entry.name),
|
|
2233
|
-
entry.name,
|
|
2234
|
-
collected
|
|
2235
|
-
);
|
|
2236
|
-
}
|
|
2237
|
-
return collected;
|
|
2238
|
-
}
|
|
2239
|
-
function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
|
|
2240
|
-
if (entry === profileDirectory) {
|
|
2241
|
-
return true;
|
|
2242
|
-
}
|
|
2243
|
-
if (COPY_SKIP_ENTRIES.has(entry)) {
|
|
2244
|
-
return false;
|
|
1917
|
+
if (process.platform === "linux") {
|
|
1918
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
2245
1919
|
}
|
|
2246
|
-
if (
|
|
2247
|
-
return
|
|
1920
|
+
if (process.platform === "win32") {
|
|
1921
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
2248
1922
|
}
|
|
2249
|
-
return
|
|
1923
|
+
return await readPsProcessStartedAtMs(pid);
|
|
2250
1924
|
}
|
|
2251
|
-
async function
|
|
2252
|
-
let
|
|
1925
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
1926
|
+
let statRaw;
|
|
2253
1927
|
try {
|
|
2254
|
-
|
|
1928
|
+
statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
2255
1929
|
} catch {
|
|
2256
|
-
return;
|
|
2257
|
-
}
|
|
2258
|
-
if (entryStat.isDirectory()) {
|
|
2259
|
-
collected.set(relativePath, {
|
|
2260
|
-
kind: "directory",
|
|
2261
|
-
hash: null,
|
|
2262
|
-
sourcePath
|
|
2263
|
-
});
|
|
2264
|
-
let children;
|
|
2265
|
-
try {
|
|
2266
|
-
children = await (0, import_promises.readdir)(sourcePath, {
|
|
2267
|
-
encoding: "utf8",
|
|
2268
|
-
withFileTypes: true
|
|
2269
|
-
});
|
|
2270
|
-
} catch {
|
|
2271
|
-
return;
|
|
2272
|
-
}
|
|
2273
|
-
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
2274
|
-
for (const child of children) {
|
|
2275
|
-
await collectSnapshotEntry(
|
|
2276
|
-
(0, import_node_path.join)(sourcePath, child.name),
|
|
2277
|
-
(0, import_node_path.join)(relativePath, child.name),
|
|
2278
|
-
collected
|
|
2279
|
-
);
|
|
2280
|
-
}
|
|
2281
|
-
return;
|
|
2282
|
-
}
|
|
2283
|
-
if (entryStat.isFile()) {
|
|
2284
|
-
collected.set(relativePath, {
|
|
2285
|
-
kind: "file",
|
|
2286
|
-
hash: await hashFile(sourcePath),
|
|
2287
|
-
sourcePath
|
|
2288
|
-
});
|
|
1930
|
+
return null;
|
|
2289
1931
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
[...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
|
|
2294
|
-
relativePath,
|
|
2295
|
-
{
|
|
2296
|
-
kind: entry.kind,
|
|
2297
|
-
hash: entry.hash
|
|
2298
|
-
}
|
|
2299
|
-
])
|
|
2300
|
-
);
|
|
2301
|
-
}
|
|
2302
|
-
function deserializeSnapshotManifestEntries(entries) {
|
|
2303
|
-
const manifestEntries = /* @__PURE__ */ new Map();
|
|
2304
|
-
for (const [relativePath, entry] of Object.entries(entries)) {
|
|
2305
|
-
if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
|
|
2306
|
-
throw new Error(
|
|
2307
|
-
`Runtime profile metadata for "${relativePath}" is invalid.`
|
|
2308
|
-
);
|
|
2309
|
-
}
|
|
2310
|
-
manifestEntries.set(relativePath, {
|
|
2311
|
-
kind: entry.kind,
|
|
2312
|
-
hash: entry.hash
|
|
2313
|
-
});
|
|
1932
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
1933
|
+
if (startTicks === null) {
|
|
1934
|
+
return null;
|
|
2314
1935
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
const mergedEntries = /* @__PURE__ */ new Map();
|
|
2319
|
-
const relativePaths = /* @__PURE__ */ new Set([
|
|
2320
|
-
...baseEntries.keys(),
|
|
2321
|
-
...currentEntries.keys(),
|
|
2322
|
-
...runtimeEntries.keys()
|
|
1936
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
1937
|
+
readLinuxBootTimeMs(),
|
|
1938
|
+
readLinuxClockTicksPerSecond()
|
|
2323
1939
|
]);
|
|
2324
|
-
|
|
2325
|
-
mergedEntries.set(
|
|
2326
|
-
relativePath,
|
|
2327
|
-
resolveMergedSnapshotEntrySelection(
|
|
2328
|
-
relativePath,
|
|
2329
|
-
baseEntries.get(relativePath) ?? null,
|
|
2330
|
-
currentEntries.get(relativePath) ?? null,
|
|
2331
|
-
runtimeEntries.get(relativePath) ?? null
|
|
2332
|
-
)
|
|
2333
|
-
);
|
|
2334
|
-
}
|
|
2335
|
-
return mergedEntries;
|
|
2336
|
-
}
|
|
2337
|
-
function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
|
|
2338
|
-
if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
|
|
2339
|
-
return currentEntry ? "current" : null;
|
|
2340
|
-
}
|
|
2341
|
-
if (snapshotEntriesEqual(currentEntry, baseEntry)) {
|
|
2342
|
-
return runtimeEntry ? "runtime" : null;
|
|
2343
|
-
}
|
|
2344
|
-
if (!baseEntry) {
|
|
2345
|
-
if (!currentEntry) {
|
|
2346
|
-
return runtimeEntry ? "runtime" : null;
|
|
2347
|
-
}
|
|
2348
|
-
if (!runtimeEntry) {
|
|
2349
|
-
return "current";
|
|
2350
|
-
}
|
|
2351
|
-
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
2352
|
-
return "current";
|
|
2353
|
-
}
|
|
2354
|
-
throw new Error(
|
|
2355
|
-
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
2356
|
-
);
|
|
2357
|
-
}
|
|
2358
|
-
if (!currentEntry && !runtimeEntry) {
|
|
1940
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
2359
1941
|
return null;
|
|
2360
1942
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
}
|
|
2364
|
-
throw new Error(
|
|
2365
|
-
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
1943
|
+
return Math.floor(
|
|
1944
|
+
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
2366
1945
|
);
|
|
2367
1946
|
}
|
|
2368
|
-
function
|
|
2369
|
-
|
|
2370
|
-
|
|
1947
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1948
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1949
|
+
if (closingParenIndex === -1) {
|
|
1950
|
+
return null;
|
|
2371
1951
|
}
|
|
2372
|
-
|
|
1952
|
+
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
1953
|
+
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
1954
|
+
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
2373
1955
|
}
|
|
2374
|
-
async function
|
|
2375
|
-
|
|
2376
|
-
(
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
if (!entry) {
|
|
2381
|
-
continue;
|
|
2382
|
-
}
|
|
2383
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, relativePath);
|
|
2384
|
-
if (entry.kind === "directory") {
|
|
2385
|
-
await (0, import_promises.mkdir)(targetPath, { recursive: true });
|
|
2386
|
-
continue;
|
|
1956
|
+
async function readLinuxBootTimeMs() {
|
|
1957
|
+
try {
|
|
1958
|
+
const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
|
|
1959
|
+
const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
|
|
1960
|
+
if (!bootTimeLine) {
|
|
1961
|
+
return null;
|
|
2387
1962
|
}
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
if (leftDepth !== rightDepth) {
|
|
2396
|
-
return leftDepth - rightDepth;
|
|
1963
|
+
const bootTimeSeconds = Number.parseInt(
|
|
1964
|
+
bootTimeLine.slice("btime ".length),
|
|
1965
|
+
10
|
|
1966
|
+
);
|
|
1967
|
+
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
1968
|
+
} catch {
|
|
1969
|
+
return null;
|
|
2397
1970
|
}
|
|
2398
|
-
return left.localeCompare(right);
|
|
2399
|
-
}
|
|
2400
|
-
async function hashFile(filePath) {
|
|
2401
|
-
return new Promise((resolve, reject) => {
|
|
2402
|
-
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
2403
|
-
const stream = (0, import_node_fs.createReadStream)(filePath);
|
|
2404
|
-
stream.on("data", (chunk) => {
|
|
2405
|
-
hash.update(chunk);
|
|
2406
|
-
});
|
|
2407
|
-
stream.on("error", reject);
|
|
2408
|
-
stream.on("end", () => {
|
|
2409
|
-
resolve(hash.digest("hex"));
|
|
2410
|
-
});
|
|
2411
|
-
});
|
|
2412
1971
|
}
|
|
2413
|
-
async function
|
|
2414
|
-
if (
|
|
2415
|
-
|
|
1972
|
+
async function readLinuxClockTicksPerSecond() {
|
|
1973
|
+
if (!linuxClockTicksPerSecondPromise) {
|
|
1974
|
+
linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
|
|
1975
|
+
encoding: "utf8",
|
|
1976
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1977
|
+
}).then(({ stdout }) => {
|
|
1978
|
+
const value = Number.parseInt(stdout.trim(), 10);
|
|
1979
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
1980
|
+
}).catch(() => null);
|
|
2416
1981
|
}
|
|
2417
|
-
await
|
|
1982
|
+
return await linuxClockTicksPerSecondPromise;
|
|
2418
1983
|
}
|
|
2419
|
-
async function
|
|
1984
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
2420
1985
|
try {
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
1986
|
+
const { stdout } = await execFileAsync(
|
|
1987
|
+
"powershell.exe",
|
|
1988
|
+
[
|
|
1989
|
+
"-NoProfile",
|
|
1990
|
+
"-Command",
|
|
1991
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString("o")`
|
|
1992
|
+
],
|
|
1993
|
+
{
|
|
1994
|
+
encoding: "utf8",
|
|
1995
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
1996
|
+
}
|
|
1997
|
+
);
|
|
1998
|
+
const isoTimestamp = stdout.trim();
|
|
1999
|
+
if (!isoTimestamp) {
|
|
2424
2000
|
return null;
|
|
2425
2001
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
profileDirectory: parsed.profileDirectory,
|
|
2429
|
-
source: parsed.source
|
|
2430
|
-
};
|
|
2002
|
+
const startedAtMs = Date.parse(isoTimestamp);
|
|
2003
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
2431
2004
|
} catch {
|
|
2432
2005
|
return null;
|
|
2433
2006
|
}
|
|
2434
2007
|
}
|
|
2435
|
-
async function
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2008
|
+
async function readPsProcessStartedAtMs(pid) {
|
|
2009
|
+
try {
|
|
2010
|
+
const { stdout } = await execFileAsync(
|
|
2011
|
+
"ps",
|
|
2012
|
+
["-o", "lstart=", "-p", String(pid)],
|
|
2013
|
+
{
|
|
2014
|
+
encoding: "utf8",
|
|
2015
|
+
env: PS_COMMAND_ENV,
|
|
2016
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
2017
|
+
}
|
|
2440
2018
|
);
|
|
2019
|
+
const startedAt = stdout.trim();
|
|
2020
|
+
if (!startedAt) {
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
|
|
2024
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
2025
|
+
} catch {
|
|
2026
|
+
return null;
|
|
2441
2027
|
}
|
|
2442
|
-
return metadata;
|
|
2443
|
-
}
|
|
2444
|
-
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
2445
|
-
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2446
|
-
return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2447
2028
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
let replacementPublished = false;
|
|
2029
|
+
|
|
2030
|
+
// src/browser/dir-lock.ts
|
|
2031
|
+
var LOCK_OWNER_FILE = "owner.json";
|
|
2032
|
+
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
2033
|
+
var LOCK_RETRY_DELAY_MS = 50;
|
|
2034
|
+
async function withDirLock(lockDirPath, action) {
|
|
2035
|
+
const releaseLock = await acquireDirLock(lockDirPath);
|
|
2456
2036
|
try {
|
|
2457
|
-
await (
|
|
2458
|
-
targetMovedToBackup = true;
|
|
2459
|
-
await (0, import_promises.rename)(replacementUserDataDir, targetUserDataDir);
|
|
2460
|
-
replacementPublished = true;
|
|
2461
|
-
} catch (error) {
|
|
2462
|
-
if (targetMovedToBackup && !(0, import_node_fs.existsSync)(targetUserDataDir)) {
|
|
2463
|
-
await (0, import_promises.rename)(backupUserDataDir, targetUserDataDir).catch(() => void 0);
|
|
2464
|
-
}
|
|
2465
|
-
throw error;
|
|
2037
|
+
return await action();
|
|
2466
2038
|
} finally {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2039
|
+
await releaseLock();
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async function acquireDirLock(lockDirPath) {
|
|
2043
|
+
while (true) {
|
|
2044
|
+
const releaseLock = await tryAcquireDirLock(lockDirPath);
|
|
2045
|
+
if (releaseLock) {
|
|
2046
|
+
return releaseLock;
|
|
2472
2047
|
}
|
|
2048
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
2473
2049
|
}
|
|
2474
2050
|
}
|
|
2475
|
-
async function
|
|
2476
|
-
|
|
2477
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
|
|
2051
|
+
async function tryAcquireDirLock(lockDirPath) {
|
|
2052
|
+
await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
|
|
2478
2053
|
while (true) {
|
|
2479
|
-
const tempLockDirPath = `${lockDirPath}-${process.pid}-${
|
|
2054
|
+
const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
2480
2055
|
try {
|
|
2481
|
-
await (0,
|
|
2482
|
-
await writeLockOwner(tempLockDirPath,
|
|
2056
|
+
await (0, import_promises2.mkdir)(tempLockDirPath);
|
|
2057
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
|
|
2483
2058
|
try {
|
|
2484
|
-
await (0,
|
|
2059
|
+
await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
|
|
2485
2060
|
break;
|
|
2486
2061
|
} catch (error) {
|
|
2487
2062
|
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
@@ -2489,7 +2064,7 @@ async function withPersistentProfileLock(targetUserDataDir, action) {
|
|
|
2489
2064
|
}
|
|
2490
2065
|
}
|
|
2491
2066
|
} finally {
|
|
2492
|
-
await (0,
|
|
2067
|
+
await (0, import_promises2.rm)(tempLockDirPath, {
|
|
2493
2068
|
recursive: true,
|
|
2494
2069
|
force: true
|
|
2495
2070
|
}).catch(() => void 0);
|
|
@@ -2498,52 +2073,48 @@ async function withPersistentProfileLock(targetUserDataDir, action) {
|
|
|
2498
2073
|
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
2499
2074
|
continue;
|
|
2500
2075
|
}
|
|
2501
|
-
|
|
2076
|
+
return null;
|
|
2502
2077
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
} finally {
|
|
2506
|
-
await (0, import_promises.rm)(lockDirPath, {
|
|
2078
|
+
return async () => {
|
|
2079
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
2507
2080
|
recursive: true,
|
|
2508
2081
|
force: true
|
|
2509
2082
|
}).catch(() => void 0);
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
async function isDirLockHeld(lockDirPath) {
|
|
2086
|
+
if (!(0, import_node_fs.existsSync)(lockDirPath)) {
|
|
2087
|
+
return false;
|
|
2088
|
+
}
|
|
2089
|
+
const owner = await readLockOwner(lockDirPath);
|
|
2090
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
2091
|
+
return false;
|
|
2510
2092
|
}
|
|
2093
|
+
return (0, import_node_fs.existsSync)(lockDirPath);
|
|
2094
|
+
}
|
|
2095
|
+
function getErrorCode(error) {
|
|
2096
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2097
|
+
}
|
|
2098
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
2099
|
+
const code = getErrorCode(error);
|
|
2100
|
+
return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2511
2101
|
}
|
|
2512
2102
|
async function writeLockOwner(lockDirPath, owner) {
|
|
2513
|
-
await
|
|
2103
|
+
await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
|
|
2514
2104
|
}
|
|
2515
2105
|
async function readLockOwner(lockDirPath) {
|
|
2516
2106
|
return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
|
|
2517
2107
|
}
|
|
2518
|
-
async function writeLockParticipant(filePath, owner, options) {
|
|
2519
|
-
await (0, import_promises.writeFile)(filePath, JSON.stringify(owner), options);
|
|
2520
|
-
}
|
|
2521
2108
|
async function readLockParticipant(filePath) {
|
|
2522
2109
|
return (await readLockParticipantRecord(filePath)).owner;
|
|
2523
2110
|
}
|
|
2524
|
-
async function readLockReclaimerRecord(lockDirPath) {
|
|
2525
|
-
return await readLockParticipantRecord(
|
|
2526
|
-
(0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
2527
|
-
);
|
|
2528
|
-
}
|
|
2529
2111
|
async function readLockParticipantRecord(filePath) {
|
|
2530
2112
|
try {
|
|
2531
|
-
const raw = await (0,
|
|
2532
|
-
const
|
|
2533
|
-
const pid = Number(parsed.pid);
|
|
2534
|
-
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
2535
|
-
if (!Number.isInteger(pid) || !Number.isInteger(processStartedAtMs)) {
|
|
2536
|
-
return {
|
|
2537
|
-
exists: true,
|
|
2538
|
-
owner: null
|
|
2539
|
-
};
|
|
2540
|
-
}
|
|
2113
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf8");
|
|
2114
|
+
const owner = parseProcessOwner(JSON.parse(raw));
|
|
2541
2115
|
return {
|
|
2542
2116
|
exists: true,
|
|
2543
|
-
owner
|
|
2544
|
-
pid,
|
|
2545
|
-
processStartedAtMs
|
|
2546
|
-
}
|
|
2117
|
+
owner
|
|
2547
2118
|
};
|
|
2548
2119
|
} catch (error) {
|
|
2549
2120
|
return {
|
|
@@ -2552,6 +2123,11 @@ async function readLockParticipantRecord(filePath) {
|
|
|
2552
2123
|
};
|
|
2553
2124
|
}
|
|
2554
2125
|
}
|
|
2126
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
2127
|
+
return await readLockParticipantRecord(
|
|
2128
|
+
(0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2555
2131
|
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
2556
2132
|
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
2557
2133
|
return false;
|
|
@@ -2559,13 +2135,13 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
|
2559
2135
|
let reclaimed = false;
|
|
2560
2136
|
try {
|
|
2561
2137
|
const owner = await readLockOwner(lockDirPath);
|
|
2562
|
-
if (!
|
|
2138
|
+
if (!processOwnersEqual(owner, expectedOwner)) {
|
|
2563
2139
|
return false;
|
|
2564
2140
|
}
|
|
2565
2141
|
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
2566
2142
|
return false;
|
|
2567
2143
|
}
|
|
2568
|
-
await (0,
|
|
2144
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
2569
2145
|
recursive: true,
|
|
2570
2146
|
force: true
|
|
2571
2147
|
}).catch(() => void 0);
|
|
@@ -2573,7 +2149,7 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
|
2573
2149
|
return reclaimed;
|
|
2574
2150
|
} finally {
|
|
2575
2151
|
if (!reclaimed) {
|
|
2576
|
-
await (0,
|
|
2152
|
+
await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
|
|
2577
2153
|
recursive: true,
|
|
2578
2154
|
force: true
|
|
2579
2155
|
}).catch(() => void 0);
|
|
@@ -2583,12 +2159,12 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
|
2583
2159
|
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
2584
2160
|
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
2585
2161
|
while (true) {
|
|
2586
|
-
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${
|
|
2162
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
2587
2163
|
try {
|
|
2588
|
-
await (0,
|
|
2589
|
-
await writeLockOwner(tempReclaimerDirPath,
|
|
2164
|
+
await (0, import_promises2.mkdir)(tempReclaimerDirPath);
|
|
2165
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
|
|
2590
2166
|
try {
|
|
2591
|
-
await (0,
|
|
2167
|
+
await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
|
|
2592
2168
|
return true;
|
|
2593
2169
|
} catch (error) {
|
|
2594
2170
|
if (getErrorCode(error) === "ENOENT") {
|
|
@@ -2605,7 +2181,7 @@ async function tryAcquireLockReclaimer(lockDirPath) {
|
|
|
2605
2181
|
}
|
|
2606
2182
|
throw error;
|
|
2607
2183
|
} finally {
|
|
2608
|
-
await (0,
|
|
2184
|
+
await (0, import_promises2.rm)(tempReclaimerDirPath, {
|
|
2609
2185
|
recursive: true,
|
|
2610
2186
|
force: true
|
|
2611
2187
|
}).catch(() => void 0);
|
|
@@ -2617,256 +2193,1515 @@ async function tryAcquireLockReclaimer(lockDirPath) {
|
|
|
2617
2193
|
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
2618
2194
|
return false;
|
|
2619
2195
|
}
|
|
2620
|
-
await (0,
|
|
2196
|
+
await (0, import_promises2.rm)(reclaimerDirPath, {
|
|
2621
2197
|
recursive: true,
|
|
2622
2198
|
force: true
|
|
2623
2199
|
}).catch(() => void 0);
|
|
2624
2200
|
}
|
|
2625
2201
|
}
|
|
2626
|
-
function
|
|
2627
|
-
|
|
2628
|
-
return left === right;
|
|
2629
|
-
}
|
|
2630
|
-
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
2631
|
-
}
|
|
2632
|
-
async function getProcessLiveness(owner) {
|
|
2633
|
-
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
2634
|
-
owner.processStartedAtMs,
|
|
2635
|
-
PROCESS_STARTED_AT_MS
|
|
2636
|
-
)) {
|
|
2637
|
-
return "live";
|
|
2638
|
-
}
|
|
2639
|
-
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
2640
|
-
if (typeof startedAtMs === "number") {
|
|
2641
|
-
return hasMatchingProcessStartTime(
|
|
2642
|
-
owner.processStartedAtMs,
|
|
2643
|
-
startedAtMs
|
|
2644
|
-
) ? "live" : "dead";
|
|
2645
|
-
}
|
|
2646
|
-
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
2202
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
2203
|
+
return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
2647
2204
|
}
|
|
2648
|
-
function
|
|
2649
|
-
|
|
2205
|
+
async function sleep(ms) {
|
|
2206
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2650
2207
|
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2208
|
+
|
|
2209
|
+
// src/browser/persistent-profile-coordination.ts
|
|
2210
|
+
var PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS = 50;
|
|
2211
|
+
async function withPersistentProfileControlLock(targetUserDataDir, action) {
|
|
2212
|
+
return await withDirLock(
|
|
2213
|
+
buildPersistentProfileControlLockDirPath(targetUserDataDir),
|
|
2214
|
+
action
|
|
2655
2215
|
);
|
|
2656
2216
|
}
|
|
2657
|
-
function
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
return (0, import_node_path.join)(
|
|
2662
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
2663
|
-
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-backup-${Date.now()}`
|
|
2217
|
+
async function acquirePersistentProfileWriteLock(targetUserDataDir) {
|
|
2218
|
+
const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
|
|
2219
|
+
const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
|
|
2220
|
+
targetUserDataDir
|
|
2664
2221
|
);
|
|
2222
|
+
while (true) {
|
|
2223
|
+
let releaseWriteLock = null;
|
|
2224
|
+
const releaseControlLock = await acquireDirLock(controlLockDirPath);
|
|
2225
|
+
try {
|
|
2226
|
+
releaseWriteLock = await tryAcquireDirLock(writeLockDirPath);
|
|
2227
|
+
} finally {
|
|
2228
|
+
await releaseControlLock();
|
|
2229
|
+
}
|
|
2230
|
+
if (releaseWriteLock) {
|
|
2231
|
+
return releaseWriteLock;
|
|
2232
|
+
}
|
|
2233
|
+
await sleep2(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2234
|
+
}
|
|
2665
2235
|
}
|
|
2666
|
-
function
|
|
2667
|
-
return
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
2671
|
-
}
|
|
2672
|
-
function buildRuntimeProfileKey(sourceUserDataDir) {
|
|
2673
|
-
const hash = (0, import_node_crypto.createHash)("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
|
|
2674
|
-
return `${sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "profile")}-${hash}`;
|
|
2236
|
+
async function isPersistentProfileWriteLocked(targetUserDataDir) {
|
|
2237
|
+
return await isDirLockHeld(
|
|
2238
|
+
buildPersistentProfileWriteLockDirPath(targetUserDataDir)
|
|
2239
|
+
);
|
|
2675
2240
|
}
|
|
2676
|
-
function
|
|
2677
|
-
return `${
|
|
2241
|
+
function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
|
|
2242
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
|
|
2678
2243
|
}
|
|
2679
|
-
function
|
|
2680
|
-
return (0,
|
|
2681
|
-
|
|
2682
|
-
`${
|
|
2244
|
+
function buildPersistentProfileControlLockDirPath(targetUserDataDir) {
|
|
2245
|
+
return (0, import_node_path2.join)(
|
|
2246
|
+
(0, import_node_path2.dirname)(targetUserDataDir),
|
|
2247
|
+
`${(0, import_node_path2.basename)(targetUserDataDir)}.control.lock`
|
|
2683
2248
|
);
|
|
2684
2249
|
}
|
|
2685
|
-
async function
|
|
2250
|
+
async function sleep2(ms) {
|
|
2251
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// src/browser/shared-real-browser-session-state.ts
|
|
2255
|
+
var import_node_crypto2 = require("crypto");
|
|
2256
|
+
var import_node_fs2 = require("fs");
|
|
2257
|
+
var import_promises3 = require("fs/promises");
|
|
2258
|
+
var import_node_path3 = require("path");
|
|
2259
|
+
var SHARED_SESSION_METADATA_FILE = "session.json";
|
|
2260
|
+
var SHARED_SESSION_CLIENTS_DIR = "clients";
|
|
2261
|
+
var SHARED_SESSION_RETRY_DELAY_MS = 50;
|
|
2262
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_PREFIX = `${SHARED_SESSION_METADATA_FILE}.`;
|
|
2263
|
+
var SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX = ".tmp";
|
|
2264
|
+
function buildSharedSessionDirPath(persistentUserDataDir) {
|
|
2265
|
+
return (0, import_node_path3.join)(
|
|
2266
|
+
(0, import_node_path3.dirname)(persistentUserDataDir),
|
|
2267
|
+
`${(0, import_node_path3.basename)(persistentUserDataDir)}.session`
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
function buildSharedSessionLockPath(persistentUserDataDir) {
|
|
2271
|
+
return `${buildSharedSessionDirPath(persistentUserDataDir)}.lock`;
|
|
2272
|
+
}
|
|
2273
|
+
function buildSharedSessionClientsDirPath(persistentUserDataDir) {
|
|
2274
|
+
return (0, import_node_path3.join)(
|
|
2275
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
2276
|
+
SHARED_SESSION_CLIENTS_DIR
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
function buildSharedSessionClientPath(persistentUserDataDir, clientId) {
|
|
2280
|
+
return (0, import_node_path3.join)(
|
|
2281
|
+
buildSharedSessionClientsDirPath(persistentUserDataDir),
|
|
2282
|
+
`${clientId}.json`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
async function readSharedSessionMetadata(persistentUserDataDir) {
|
|
2286
|
+
return (await readSharedSessionMetadataRecord(persistentUserDataDir)).metadata;
|
|
2287
|
+
}
|
|
2288
|
+
async function writeSharedSessionMetadata(persistentUserDataDir, metadata) {
|
|
2289
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
2290
|
+
const metadataPath = buildSharedSessionMetadataPath(persistentUserDataDir);
|
|
2291
|
+
const tempPath = buildSharedSessionMetadataTempPath(sessionDirPath);
|
|
2292
|
+
await (0, import_promises3.mkdir)(sessionDirPath, { recursive: true });
|
|
2293
|
+
try {
|
|
2294
|
+
await (0, import_promises3.writeFile)(tempPath, JSON.stringify(metadata, null, 2));
|
|
2295
|
+
await (0, import_promises3.rename)(tempPath, metadataPath);
|
|
2296
|
+
} finally {
|
|
2297
|
+
await (0, import_promises3.rm)(tempPath, { force: true }).catch(() => void 0);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
async function hasLiveSharedRealBrowserSession(persistentUserDataDir) {
|
|
2301
|
+
const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
|
|
2302
|
+
const metadataRecord = await readSharedSessionMetadataRecord(
|
|
2303
|
+
persistentUserDataDir
|
|
2304
|
+
);
|
|
2305
|
+
if (!metadataRecord.exists) {
|
|
2306
|
+
return await hasLiveSharedSessionPublisherOrClients(sessionDirPath);
|
|
2307
|
+
}
|
|
2308
|
+
if (!metadataRecord.metadata) {
|
|
2309
|
+
return true;
|
|
2310
|
+
}
|
|
2311
|
+
if (await getProcessLiveness(metadataRecord.metadata.browserOwner) === "dead") {
|
|
2312
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
2313
|
+
force: true,
|
|
2314
|
+
recursive: true
|
|
2315
|
+
}).catch(() => void 0);
|
|
2316
|
+
return false;
|
|
2317
|
+
}
|
|
2318
|
+
return true;
|
|
2319
|
+
}
|
|
2320
|
+
async function waitForSharedRealBrowserSessionToDrain(persistentUserDataDir) {
|
|
2321
|
+
while (true) {
|
|
2322
|
+
if (!await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
await sleep3(SHARED_SESSION_RETRY_DELAY_MS);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
async function readSharedSessionMetadataRecord(persistentUserDataDir) {
|
|
2329
|
+
try {
|
|
2330
|
+
const raw = await (0, import_promises3.readFile)(
|
|
2331
|
+
buildSharedSessionMetadataPath(persistentUserDataDir),
|
|
2332
|
+
"utf8"
|
|
2333
|
+
);
|
|
2334
|
+
return {
|
|
2335
|
+
exists: true,
|
|
2336
|
+
metadata: parseSharedSessionMetadata(JSON.parse(raw))
|
|
2337
|
+
};
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
return {
|
|
2340
|
+
exists: getErrorCode2(error) !== "ENOENT",
|
|
2341
|
+
metadata: null
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
async function hasLiveSharedSessionPublisherOrClients(sessionDirPath) {
|
|
2346
|
+
if (!(0, import_node_fs2.existsSync)(sessionDirPath)) {
|
|
2347
|
+
return false;
|
|
2348
|
+
}
|
|
2349
|
+
let entries;
|
|
2350
|
+
try {
|
|
2351
|
+
entries = await readDirNames(sessionDirPath);
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
2354
|
+
}
|
|
2355
|
+
let hasUnknownEntries = false;
|
|
2356
|
+
for (const entry of entries) {
|
|
2357
|
+
if (entry === SHARED_SESSION_METADATA_FILE) {
|
|
2358
|
+
return true;
|
|
2359
|
+
}
|
|
2360
|
+
if (entry === SHARED_SESSION_CLIENTS_DIR) {
|
|
2361
|
+
if (await hasDirectoryEntries((0, import_node_path3.join)(sessionDirPath, entry))) {
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
const owner = parseSharedSessionMetadataTempOwner(entry);
|
|
2367
|
+
if (!owner) {
|
|
2368
|
+
if (isSharedSessionMetadataTempFile(entry)) {
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
hasUnknownEntries = true;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
if (await getProcessLiveness(owner) !== "dead") {
|
|
2375
|
+
return true;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
if (hasUnknownEntries) {
|
|
2379
|
+
return true;
|
|
2380
|
+
}
|
|
2381
|
+
await (0, import_promises3.rm)(sessionDirPath, {
|
|
2382
|
+
force: true,
|
|
2383
|
+
recursive: true
|
|
2384
|
+
}).catch(() => void 0);
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
function buildSharedSessionMetadataPath(persistentUserDataDir) {
|
|
2388
|
+
return (0, import_node_path3.join)(
|
|
2389
|
+
buildSharedSessionDirPath(persistentUserDataDir),
|
|
2390
|
+
SHARED_SESSION_METADATA_FILE
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
function buildSharedSessionMetadataTempPath(sessionDirPath) {
|
|
2394
|
+
return (0, import_node_path3.join)(
|
|
2395
|
+
sessionDirPath,
|
|
2396
|
+
[
|
|
2397
|
+
SHARED_SESSION_METADATA_FILE,
|
|
2398
|
+
CURRENT_PROCESS_OWNER.pid,
|
|
2399
|
+
CURRENT_PROCESS_OWNER.processStartedAtMs,
|
|
2400
|
+
(0, import_node_crypto2.randomUUID)(),
|
|
2401
|
+
"tmp"
|
|
2402
|
+
].join(".")
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
function parseSharedSessionMetadata(value) {
|
|
2406
|
+
if (!value || typeof value !== "object") {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
const parsed = value;
|
|
2410
|
+
const browserOwner = parseProcessOwner(parsed.browserOwner);
|
|
2411
|
+
const stateOwner = parseProcessOwner(parsed.stateOwner);
|
|
2412
|
+
const state = parsed.state === "launching" || parsed.state === "ready" || parsed.state === "closing" ? parsed.state : null;
|
|
2413
|
+
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) {
|
|
2414
|
+
return null;
|
|
2415
|
+
}
|
|
2416
|
+
return {
|
|
2417
|
+
browserOwner,
|
|
2418
|
+
createdAt: parsed.createdAt,
|
|
2419
|
+
debugPort: parsed.debugPort,
|
|
2420
|
+
executablePath: parsed.executablePath,
|
|
2421
|
+
headless: parsed.headless,
|
|
2422
|
+
persistentUserDataDir: parsed.persistentUserDataDir,
|
|
2423
|
+
profileDirectory: parsed.profileDirectory,
|
|
2424
|
+
sessionId: parsed.sessionId,
|
|
2425
|
+
state,
|
|
2426
|
+
stateOwner
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function parseSharedSessionMetadataTempOwner(entryName) {
|
|
2430
|
+
if (!isSharedSessionMetadataTempFile(entryName)) {
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2433
|
+
const segments = entryName.split(".");
|
|
2434
|
+
if (segments.length < 5) {
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2437
|
+
return parseProcessOwner({
|
|
2438
|
+
pid: Number.parseInt(segments[2] ?? "", 10),
|
|
2439
|
+
processStartedAtMs: Number.parseInt(segments[3] ?? "", 10)
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
function isSharedSessionMetadataTempFile(entryName) {
|
|
2443
|
+
return entryName.startsWith(SHARED_SESSION_METADATA_TEMP_FILE_PREFIX) && entryName.endsWith(SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX);
|
|
2444
|
+
}
|
|
2445
|
+
function getErrorCode2(error) {
|
|
2446
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2447
|
+
}
|
|
2448
|
+
async function hasDirectoryEntries(dirPath) {
|
|
2449
|
+
try {
|
|
2450
|
+
return (await readDirNames(dirPath)).length > 0;
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
return getErrorCode2(error) !== "ENOENT";
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
async function readDirNames(dirPath) {
|
|
2456
|
+
return await (0, import_promises3.readdir)(dirPath, { encoding: "utf8" });
|
|
2457
|
+
}
|
|
2458
|
+
async function sleep3(ms) {
|
|
2459
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/browser/persistent-profile.ts
|
|
2463
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
2464
|
+
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
2465
|
+
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
2466
|
+
var OPENSTEER_RUNTIME_CREATING_FILE = ".opensteer-runtime-creating.json";
|
|
2467
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
|
|
2468
|
+
var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
|
|
2469
|
+
var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
|
|
2470
|
+
"SingletonCookie",
|
|
2471
|
+
"SingletonLock",
|
|
2472
|
+
"SingletonSocket",
|
|
2473
|
+
"DevToolsActivePort",
|
|
2474
|
+
"lockfile"
|
|
2475
|
+
]);
|
|
2476
|
+
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
2477
|
+
...CHROME_SINGLETON_ENTRIES,
|
|
2478
|
+
OPENSTEER_META_FILE,
|
|
2479
|
+
OPENSTEER_RUNTIME_META_FILE,
|
|
2480
|
+
OPENSTEER_RUNTIME_CREATING_FILE
|
|
2481
|
+
]);
|
|
2482
|
+
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
2483
|
+
"Crash Reports",
|
|
2484
|
+
"Crashpad",
|
|
2485
|
+
"BrowserMetrics",
|
|
2486
|
+
"GrShaderCache",
|
|
2487
|
+
"ShaderCache",
|
|
2488
|
+
"GraphiteDawnCache",
|
|
2489
|
+
"component_crx_cache",
|
|
2490
|
+
"Crowd Deny",
|
|
2491
|
+
"hyphen-data",
|
|
2492
|
+
"OnDeviceHeadSuggestModel",
|
|
2493
|
+
"OptimizationGuidePredictionModels",
|
|
2494
|
+
"Segmentation Platform",
|
|
2495
|
+
"SmartCardDeviceNames",
|
|
2496
|
+
"WidevineCdm",
|
|
2497
|
+
"pnacl"
|
|
2498
|
+
]);
|
|
2499
|
+
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
2500
|
+
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
2501
|
+
const targetUserDataDir = (0, import_node_path4.join)(
|
|
2502
|
+
expandHome(profilesRootDir),
|
|
2503
|
+
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
2504
|
+
);
|
|
2505
|
+
const sourceProfileDir = (0, import_node_path4.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
2506
|
+
const metadata = buildPersistentProfileMetadata(
|
|
2507
|
+
resolvedSourceUserDataDir,
|
|
2508
|
+
profileDirectory
|
|
2509
|
+
);
|
|
2510
|
+
await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetUserDataDir), { recursive: true });
|
|
2511
|
+
if (await isHealthyPersistentProfile(
|
|
2512
|
+
targetUserDataDir,
|
|
2513
|
+
resolvedSourceUserDataDir,
|
|
2514
|
+
profileDirectory
|
|
2515
|
+
) && !await isPersistentProfileWriteLocked(targetUserDataDir)) {
|
|
2516
|
+
return {
|
|
2517
|
+
created: false,
|
|
2518
|
+
userDataDir: targetUserDataDir
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
return await withPersistentProfileWriteAccess(targetUserDataDir, async () => {
|
|
2522
|
+
await recoverPersistentProfileBackup(targetUserDataDir);
|
|
2523
|
+
await cleanOrphanedOwnedDirs(
|
|
2524
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
2525
|
+
buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
|
|
2526
|
+
);
|
|
2527
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
2528
|
+
throw new Error(
|
|
2529
|
+
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
const created = await createPersistentProfileClone(
|
|
2533
|
+
resolvedSourceUserDataDir,
|
|
2534
|
+
sourceProfileDir,
|
|
2535
|
+
targetUserDataDir,
|
|
2536
|
+
profileDirectory,
|
|
2537
|
+
metadata
|
|
2538
|
+
);
|
|
2539
|
+
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
2540
|
+
return {
|
|
2541
|
+
created,
|
|
2542
|
+
userDataDir: targetUserDataDir
|
|
2543
|
+
};
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
async function clearPersistentProfileSingletons(userDataDir) {
|
|
2547
|
+
await Promise.all(
|
|
2548
|
+
[...CHROME_SINGLETON_ENTRIES].map(
|
|
2549
|
+
(entry) => (0, import_promises4.rm)((0, import_node_path4.join)(userDataDir, entry), {
|
|
2550
|
+
force: true,
|
|
2551
|
+
recursive: true
|
|
2552
|
+
}).catch(() => void 0)
|
|
2553
|
+
)
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
2557
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
2558
|
+
const sourceLabel = sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "user-data");
|
|
2559
|
+
const profileLabel = sanitizePathSegment(profileDirectory || "Default");
|
|
2560
|
+
return `${sourceLabel}-${profileLabel}-${hash}`;
|
|
2561
|
+
}
|
|
2562
|
+
function defaultPersistentProfilesRootDir() {
|
|
2563
|
+
return (0, import_node_path4.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
|
|
2564
|
+
}
|
|
2565
|
+
function sanitizePathSegment(value) {
|
|
2566
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
2567
|
+
return sanitized.replace(/^-|-$/g, "") || "profile";
|
|
2568
|
+
}
|
|
2569
|
+
function isProfileDirectory(userDataDir, entry) {
|
|
2570
|
+
return (0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, entry, "Preferences"));
|
|
2571
|
+
}
|
|
2572
|
+
async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
|
|
2573
|
+
let entries;
|
|
2574
|
+
try {
|
|
2575
|
+
entries = await (0, import_promises4.readdir)(sourceUserDataDir);
|
|
2576
|
+
} catch {
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
const copyTasks = [];
|
|
2580
|
+
for (const entry of entries) {
|
|
2581
|
+
if (COPY_SKIP_ENTRIES.has(entry)) continue;
|
|
2582
|
+
if (entry === targetProfileDirectory) continue;
|
|
2583
|
+
const sourcePath = (0, import_node_path4.join)(sourceUserDataDir, entry);
|
|
2584
|
+
const targetPath = (0, import_node_path4.join)(targetUserDataDir, entry);
|
|
2585
|
+
if ((0, import_node_fs3.existsSync)(targetPath)) continue;
|
|
2586
|
+
let entryStat;
|
|
2587
|
+
try {
|
|
2588
|
+
entryStat = await (0, import_promises4.stat)(sourcePath);
|
|
2589
|
+
} catch {
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
if (entryStat.isFile()) {
|
|
2593
|
+
copyTasks.push((0, import_promises4.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
2594
|
+
} else if (entryStat.isDirectory()) {
|
|
2595
|
+
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
2596
|
+
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
2597
|
+
copyTasks.push(
|
|
2598
|
+
(0, import_promises4.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
2599
|
+
() => void 0
|
|
2600
|
+
)
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
await Promise.all(copyTasks);
|
|
2605
|
+
}
|
|
2606
|
+
async function writePersistentProfileMetadata(userDataDir, metadata) {
|
|
2607
|
+
await (0, import_promises4.writeFile)(
|
|
2608
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE),
|
|
2609
|
+
JSON.stringify(metadata, null, 2)
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
|
|
2613
|
+
return {
|
|
2614
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2615
|
+
profileDirectory,
|
|
2616
|
+
source: sourceUserDataDir
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
2620
|
+
if ((0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
2621
|
+
return false;
|
|
2622
|
+
}
|
|
2623
|
+
const tempUserDataDir = await (0, import_promises4.mkdtemp)(
|
|
2624
|
+
buildPersistentProfileTempDirPrefix(targetUserDataDir)
|
|
2625
|
+
);
|
|
2626
|
+
let published = false;
|
|
2627
|
+
try {
|
|
2628
|
+
await materializePersistentProfileSnapshot(
|
|
2629
|
+
sourceUserDataDir,
|
|
2630
|
+
sourceProfileDir,
|
|
2631
|
+
tempUserDataDir,
|
|
2632
|
+
profileDirectory,
|
|
2633
|
+
metadata
|
|
2634
|
+
);
|
|
2635
|
+
try {
|
|
2636
|
+
await (0, import_promises4.rename)(tempUserDataDir, targetUserDataDir);
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
if (wasDirPublishedByAnotherProcess2(error, targetUserDataDir)) {
|
|
2639
|
+
return false;
|
|
2640
|
+
}
|
|
2641
|
+
throw error;
|
|
2642
|
+
}
|
|
2643
|
+
published = true;
|
|
2644
|
+
return true;
|
|
2645
|
+
} finally {
|
|
2646
|
+
if (!published) {
|
|
2647
|
+
await (0, import_promises4.rm)(tempUserDataDir, {
|
|
2648
|
+
recursive: true,
|
|
2649
|
+
force: true
|
|
2650
|
+
}).catch(() => void 0);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
2655
|
+
if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
|
|
2656
|
+
throw new Error(
|
|
2657
|
+
`Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
await (0, import_promises4.cp)(sourceProfileDir, (0, import_node_path4.join)(targetUserDataDir, profileDirectory), {
|
|
2661
|
+
recursive: true
|
|
2662
|
+
});
|
|
2663
|
+
await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
|
|
2664
|
+
await writePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
2665
|
+
}
|
|
2666
|
+
async function readRuntimeProfileCreationMarker(userDataDir) {
|
|
2667
|
+
try {
|
|
2668
|
+
const raw = await (0, import_promises4.readFile)(
|
|
2669
|
+
(0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
|
|
2670
|
+
"utf8"
|
|
2671
|
+
);
|
|
2672
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
2673
|
+
} catch {
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
async function ensurePersistentProfileMetadata(userDataDir, metadata) {
|
|
2678
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE))) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
await writePersistentProfileMetadata(userDataDir, metadata);
|
|
2682
|
+
}
|
|
2683
|
+
async function recoverPersistentProfileBackup(targetUserDataDir) {
|
|
2684
|
+
const backupDirPaths = await listPersistentProfileBackupDirs(targetUserDataDir);
|
|
2685
|
+
if (backupDirPaths.length === 0) {
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
|
|
2689
|
+
const [latestBackupDirPath, ...staleBackupDirPaths] = backupDirPaths;
|
|
2690
|
+
await (0, import_promises4.rename)(latestBackupDirPath, targetUserDataDir);
|
|
2691
|
+
await Promise.all(
|
|
2692
|
+
staleBackupDirPaths.map(
|
|
2693
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
2694
|
+
recursive: true,
|
|
2695
|
+
force: true
|
|
2696
|
+
}).catch(() => void 0)
|
|
2697
|
+
)
|
|
2698
|
+
);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
await Promise.all(
|
|
2702
|
+
backupDirPaths.map(
|
|
2703
|
+
(backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
|
|
2704
|
+
recursive: true,
|
|
2705
|
+
force: true
|
|
2706
|
+
}).catch(() => void 0)
|
|
2707
|
+
)
|
|
2708
|
+
);
|
|
2709
|
+
}
|
|
2710
|
+
async function listPersistentProfileBackupDirs(targetUserDataDir) {
|
|
2711
|
+
const profilesDir = (0, import_node_path4.dirname)(targetUserDataDir);
|
|
2712
|
+
let entries;
|
|
2713
|
+
try {
|
|
2714
|
+
entries = await (0, import_promises4.readdir)(profilesDir, {
|
|
2715
|
+
encoding: "utf8",
|
|
2716
|
+
withFileTypes: true
|
|
2717
|
+
});
|
|
2718
|
+
} catch {
|
|
2719
|
+
return [];
|
|
2720
|
+
}
|
|
2721
|
+
const backupDirNamePrefix = buildPersistentProfileBackupDirNamePrefix(targetUserDataDir);
|
|
2722
|
+
return entries.filter(
|
|
2723
|
+
(entry) => entry.isDirectory() && entry.name.startsWith(backupDirNamePrefix)
|
|
2724
|
+
).map((entry) => (0, import_node_path4.join)(profilesDir, entry.name)).sort((leftPath, rightPath) => rightPath.localeCompare(leftPath));
|
|
2725
|
+
}
|
|
2726
|
+
async function readPersistentProfileMetadata(userDataDir) {
|
|
2727
|
+
try {
|
|
2728
|
+
const raw = await (0, import_promises4.readFile)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
|
|
2729
|
+
const parsed = JSON.parse(raw);
|
|
2730
|
+
if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
return {
|
|
2734
|
+
createdAt: parsed.createdAt,
|
|
2735
|
+
profileDirectory: parsed.profileDirectory,
|
|
2736
|
+
source: parsed.source
|
|
2737
|
+
};
|
|
2738
|
+
} catch {
|
|
2739
|
+
return null;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
async function isHealthyPersistentProfile(userDataDir, expectedSourceUserDataDir, expectedProfileDirectory) {
|
|
2743
|
+
if (!(0, import_node_fs3.existsSync)(userDataDir) || !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, expectedProfileDirectory))) {
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
const metadata = await readPersistentProfileMetadata(userDataDir);
|
|
2747
|
+
return metadata?.source === expectedSourceUserDataDir && metadata.profileDirectory === expectedProfileDirectory;
|
|
2748
|
+
}
|
|
2749
|
+
function wasDirPublishedByAnotherProcess2(error, targetDirPath) {
|
|
2750
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2751
|
+
return (0, import_node_fs3.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2752
|
+
}
|
|
2753
|
+
async function withPersistentProfileWriteAccess(targetUserDataDir, action) {
|
|
2754
|
+
const releaseWriteLock = await acquirePersistentProfileWriteLock(
|
|
2755
|
+
targetUserDataDir
|
|
2756
|
+
);
|
|
2757
|
+
try {
|
|
2758
|
+
await waitForRuntimeProfileCreationsToDrain(targetUserDataDir);
|
|
2759
|
+
await waitForSharedRealBrowserSessionToDrain(targetUserDataDir);
|
|
2760
|
+
return await action();
|
|
2761
|
+
} finally {
|
|
2762
|
+
await releaseWriteLock();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
|
|
2766
|
+
return (0, import_node_path4.join)(
|
|
2767
|
+
(0, import_node_path4.dirname)(targetUserDataDir),
|
|
2768
|
+
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-`
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
|
|
2772
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-tmp-`;
|
|
2773
|
+
}
|
|
2774
|
+
function buildPersistentProfileBackupDirNamePrefix(targetUserDataDir) {
|
|
2775
|
+
return `${(0, import_node_path4.basename)(targetUserDataDir)}-backup-`;
|
|
2776
|
+
}
|
|
2777
|
+
function buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir) {
|
|
2778
|
+
return (0, import_node_path4.join)(
|
|
2779
|
+
(0, import_node_path4.dirname)(persistentUserDataDir),
|
|
2780
|
+
`${(0, import_node_path4.basename)(persistentUserDataDir)}.creating`
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
function buildRuntimeProfileCreationRegistrationPath(persistentUserDataDir, runtimeUserDataDir) {
|
|
2784
|
+
const key = (0, import_node_crypto3.createHash)("sha256").update(runtimeUserDataDir).digest("hex").slice(0, 16);
|
|
2785
|
+
return (0, import_node_path4.join)(
|
|
2786
|
+
buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir),
|
|
2787
|
+
`${key}.json`
|
|
2788
|
+
);
|
|
2789
|
+
}
|
|
2790
|
+
async function clearRuntimeProfileCreationState(runtimeUserDataDir, persistentUserDataDir) {
|
|
2791
|
+
await Promise.all([
|
|
2792
|
+
(0, import_promises4.rm)((0, import_node_path4.join)(runtimeUserDataDir, OPENSTEER_RUNTIME_CREATING_FILE), {
|
|
2793
|
+
force: true
|
|
2794
|
+
}).catch(() => void 0),
|
|
2795
|
+
(0, import_promises4.rm)(
|
|
2796
|
+
buildRuntimeProfileCreationRegistrationPath(
|
|
2797
|
+
persistentUserDataDir,
|
|
2798
|
+
runtimeUserDataDir
|
|
2799
|
+
),
|
|
2800
|
+
{
|
|
2801
|
+
force: true
|
|
2802
|
+
}
|
|
2803
|
+
).catch(() => void 0)
|
|
2804
|
+
]);
|
|
2805
|
+
}
|
|
2806
|
+
async function listRuntimeProfileCreationRegistrations(persistentUserDataDir) {
|
|
2807
|
+
const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
|
|
2808
|
+
persistentUserDataDir
|
|
2809
|
+
);
|
|
2810
|
+
let entries;
|
|
2811
|
+
try {
|
|
2812
|
+
entries = await (0, import_promises4.readdir)(registryDirPath, {
|
|
2813
|
+
encoding: "utf8",
|
|
2814
|
+
withFileTypes: true
|
|
2815
|
+
});
|
|
2816
|
+
} catch {
|
|
2817
|
+
return [];
|
|
2818
|
+
}
|
|
2819
|
+
return await Promise.all(
|
|
2820
|
+
entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
2821
|
+
const filePath = (0, import_node_path4.join)(registryDirPath, entry.name);
|
|
2822
|
+
return {
|
|
2823
|
+
filePath,
|
|
2824
|
+
marker: await readRuntimeProfileCreationRegistration(filePath)
|
|
2825
|
+
};
|
|
2826
|
+
})
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
async function readRuntimeProfileCreationRegistration(filePath) {
|
|
2830
|
+
try {
|
|
2831
|
+
const raw = await (0, import_promises4.readFile)(filePath, "utf8");
|
|
2832
|
+
return parseRuntimeProfileCreationMarker(JSON.parse(raw));
|
|
2833
|
+
} catch {
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
|
|
2686
2838
|
let entries;
|
|
2687
2839
|
try {
|
|
2688
|
-
entries = await (0,
|
|
2840
|
+
entries = await (0, import_promises4.readdir)(rootDir, {
|
|
2689
2841
|
encoding: "utf8",
|
|
2690
2842
|
withFileTypes: true
|
|
2691
2843
|
});
|
|
2692
2844
|
} catch {
|
|
2693
2845
|
return;
|
|
2694
2846
|
}
|
|
2695
|
-
await Promise.all(
|
|
2696
|
-
entries.map(async (entry) => {
|
|
2697
|
-
if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
|
|
2701
|
-
return;
|
|
2847
|
+
await Promise.all(
|
|
2848
|
+
entries.map(async (entry) => {
|
|
2849
|
+
if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
await (0, import_promises4.rm)((0, import_node_path4.join)(rootDir, entry.name), {
|
|
2856
|
+
recursive: true,
|
|
2857
|
+
force: true
|
|
2858
|
+
}).catch(() => void 0);
|
|
2859
|
+
})
|
|
2860
|
+
);
|
|
2861
|
+
}
|
|
2862
|
+
async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
|
|
2863
|
+
const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
|
|
2864
|
+
return owner ? await getProcessLiveness(owner) !== "dead" : false;
|
|
2865
|
+
}
|
|
2866
|
+
async function hasActiveRuntimeProfileCreations(persistentUserDataDir) {
|
|
2867
|
+
const registrations = await listRuntimeProfileCreationRegistrations(
|
|
2868
|
+
persistentUserDataDir
|
|
2869
|
+
);
|
|
2870
|
+
let hasLiveCreation = false;
|
|
2871
|
+
for (const registration of registrations) {
|
|
2872
|
+
const marker = registration.marker;
|
|
2873
|
+
if (!marker || marker.persistentUserDataDir !== persistentUserDataDir) {
|
|
2874
|
+
await (0, import_promises4.rm)(registration.filePath, {
|
|
2875
|
+
force: true
|
|
2876
|
+
}).catch(() => void 0);
|
|
2877
|
+
continue;
|
|
2878
|
+
}
|
|
2879
|
+
const runtimeMarker = await readRuntimeProfileCreationMarker(
|
|
2880
|
+
marker.runtimeUserDataDir
|
|
2881
|
+
);
|
|
2882
|
+
if (!runtimeMarker || runtimeMarker.persistentUserDataDir !== persistentUserDataDir || runtimeMarker.runtimeUserDataDir !== marker.runtimeUserDataDir) {
|
|
2883
|
+
await clearRuntimeProfileCreationState(
|
|
2884
|
+
marker.runtimeUserDataDir,
|
|
2885
|
+
persistentUserDataDir
|
|
2886
|
+
);
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
if (await getProcessLiveness(runtimeMarker.creator) === "dead") {
|
|
2890
|
+
await clearRuntimeProfileCreationState(
|
|
2891
|
+
marker.runtimeUserDataDir,
|
|
2892
|
+
persistentUserDataDir
|
|
2893
|
+
);
|
|
2894
|
+
await (0, import_promises4.rm)(marker.runtimeUserDataDir, {
|
|
2895
|
+
recursive: true,
|
|
2896
|
+
force: true
|
|
2897
|
+
}).catch(() => void 0);
|
|
2898
|
+
continue;
|
|
2899
|
+
}
|
|
2900
|
+
hasLiveCreation = true;
|
|
2901
|
+
}
|
|
2902
|
+
return hasLiveCreation;
|
|
2903
|
+
}
|
|
2904
|
+
async function waitForRuntimeProfileCreationsToDrain(persistentUserDataDir) {
|
|
2905
|
+
while (true) {
|
|
2906
|
+
if (!await hasActiveRuntimeProfileCreations(persistentUserDataDir)) {
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
function parseRuntimeProfileCreationMarker(value) {
|
|
2913
|
+
if (!value || typeof value !== "object") {
|
|
2914
|
+
return null;
|
|
2915
|
+
}
|
|
2916
|
+
const parsed = value;
|
|
2917
|
+
const creator = parseProcessOwner(parsed.creator);
|
|
2918
|
+
const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
|
|
2919
|
+
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
2920
|
+
const runtimeUserDataDir = typeof parsed.runtimeUserDataDir === "string" ? parsed.runtimeUserDataDir : void 0;
|
|
2921
|
+
if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || runtimeUserDataDir === void 0) {
|
|
2922
|
+
return null;
|
|
2923
|
+
}
|
|
2924
|
+
return {
|
|
2925
|
+
creator,
|
|
2926
|
+
persistentUserDataDir,
|
|
2927
|
+
profileDirectory,
|
|
2928
|
+
runtimeUserDataDir
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
|
|
2932
|
+
const remainder = ownedDirName.slice(ownedDirPrefix.length);
|
|
2933
|
+
const firstDashIndex = remainder.indexOf("-");
|
|
2934
|
+
const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
|
|
2935
|
+
if (firstDashIndex === -1 || secondDashIndex === -1) {
|
|
2936
|
+
return null;
|
|
2937
|
+
}
|
|
2938
|
+
const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
|
|
2939
|
+
const processStartedAtMs = Number.parseInt(
|
|
2940
|
+
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
2941
|
+
10
|
|
2942
|
+
);
|
|
2943
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
2944
|
+
return null;
|
|
2945
|
+
}
|
|
2946
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
return { pid, processStartedAtMs };
|
|
2950
|
+
}
|
|
2951
|
+
async function sleep4(ms) {
|
|
2952
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
// src/browser/shared-real-browser-session.ts
|
|
2956
|
+
var import_node_crypto4 = require("crypto");
|
|
2957
|
+
var import_node_child_process3 = require("child_process");
|
|
2958
|
+
var import_promises5 = require("fs/promises");
|
|
2959
|
+
var import_node_net = require("net");
|
|
2960
|
+
var import_node_path5 = require("path");
|
|
2961
|
+
var import_playwright = require("playwright");
|
|
2962
|
+
var SHARED_SESSION_RETRY_DELAY_MS2 = 50;
|
|
2963
|
+
async function acquireSharedRealBrowserSession(options) {
|
|
2964
|
+
const reservation = await reserveSharedSessionClient(options);
|
|
2965
|
+
const sessionContext = await attachToSharedSession(reservation, options);
|
|
2966
|
+
let closed = false;
|
|
2967
|
+
return {
|
|
2968
|
+
browser: sessionContext.browser,
|
|
2969
|
+
context: sessionContext.context,
|
|
2970
|
+
page: sessionContext.page,
|
|
2971
|
+
close: async () => {
|
|
2972
|
+
if (closed) {
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
closed = true;
|
|
2976
|
+
await releaseSharedSessionClient(sessionContext);
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
|
|
2981
|
+
if (platformName === "win32") {
|
|
2982
|
+
return {
|
|
2983
|
+
detached: false,
|
|
2984
|
+
killStrategy: "taskkill",
|
|
2985
|
+
shouldUnref: true
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
if (platformName === "darwin") {
|
|
2989
|
+
return {
|
|
2990
|
+
detached: false,
|
|
2991
|
+
killStrategy: "process",
|
|
2992
|
+
shouldUnref: true
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
return {
|
|
2996
|
+
detached: true,
|
|
2997
|
+
killStrategy: "process-group",
|
|
2998
|
+
shouldUnref: true
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
async function reserveSharedSessionClient(options) {
|
|
3002
|
+
while (true) {
|
|
3003
|
+
const outcome = await withPersistentProfileControlLock(
|
|
3004
|
+
options.persistentProfile.userDataDir,
|
|
3005
|
+
async () => {
|
|
3006
|
+
if (await isPersistentProfileWriteLocked(
|
|
3007
|
+
options.persistentProfile.userDataDir
|
|
3008
|
+
)) {
|
|
3009
|
+
return { kind: "wait" };
|
|
3010
|
+
}
|
|
3011
|
+
if (await hasActiveRuntimeProfileCreations(
|
|
3012
|
+
options.persistentProfile.userDataDir
|
|
3013
|
+
)) {
|
|
3014
|
+
return { kind: "wait" };
|
|
3015
|
+
}
|
|
3016
|
+
return await withSharedSessionLock(
|
|
3017
|
+
options.persistentProfile.userDataDir,
|
|
3018
|
+
async () => {
|
|
3019
|
+
const state = await inspectSharedSessionState(options);
|
|
3020
|
+
if (state.kind === "wait") {
|
|
3021
|
+
return { kind: "wait" };
|
|
3022
|
+
}
|
|
3023
|
+
if (state.kind === "ready") {
|
|
3024
|
+
return {
|
|
3025
|
+
kind: "ready",
|
|
3026
|
+
reservation: await registerSharedSessionClient(
|
|
3027
|
+
options.persistentProfile.userDataDir,
|
|
3028
|
+
state.metadata
|
|
3029
|
+
)
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
return {
|
|
3033
|
+
kind: "launch",
|
|
3034
|
+
reservation: await launchSharedSession(options)
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
);
|
|
3040
|
+
if (outcome.kind === "wait") {
|
|
3041
|
+
await sleep5(SHARED_SESSION_RETRY_DELAY_MS2);
|
|
3042
|
+
continue;
|
|
3043
|
+
}
|
|
3044
|
+
if (outcome.kind === "ready") {
|
|
3045
|
+
return outcome.reservation;
|
|
3046
|
+
}
|
|
3047
|
+
try {
|
|
3048
|
+
await waitForSharedSessionReady(
|
|
3049
|
+
outcome.reservation.metadata,
|
|
3050
|
+
options.timeoutMs
|
|
3051
|
+
);
|
|
3052
|
+
} catch (error) {
|
|
3053
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
3054
|
+
throw error;
|
|
3055
|
+
}
|
|
3056
|
+
try {
|
|
3057
|
+
return await withSharedSessionLock(
|
|
3058
|
+
options.persistentProfile.userDataDir,
|
|
3059
|
+
async () => {
|
|
3060
|
+
const metadata = await readSharedSessionMetadata(
|
|
3061
|
+
options.persistentProfile.userDataDir
|
|
3062
|
+
);
|
|
3063
|
+
if (!metadata || metadata.sessionId !== outcome.reservation.metadata.sessionId || !processOwnersEqual(
|
|
3064
|
+
metadata.browserOwner,
|
|
3065
|
+
outcome.reservation.launchedBrowserOwner
|
|
3066
|
+
)) {
|
|
3067
|
+
throw new Error(
|
|
3068
|
+
"The shared real-browser session changed before launch finalized."
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
const readyMetadata = {
|
|
3072
|
+
...metadata,
|
|
3073
|
+
state: "ready"
|
|
3074
|
+
};
|
|
3075
|
+
await writeSharedSessionMetadata(
|
|
3076
|
+
options.persistentProfile.userDataDir,
|
|
3077
|
+
readyMetadata
|
|
3078
|
+
);
|
|
3079
|
+
return await registerSharedSessionClient(
|
|
3080
|
+
options.persistentProfile.userDataDir,
|
|
3081
|
+
readyMetadata
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
);
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
await cleanupFailedSharedSessionLaunch(outcome.reservation);
|
|
3087
|
+
throw error;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
async function attachToSharedSession(reservation, options) {
|
|
3092
|
+
let browser = null;
|
|
3093
|
+
let page = null;
|
|
3094
|
+
try {
|
|
3095
|
+
const browserWsUrl = await resolveCdpWebSocketUrl(
|
|
3096
|
+
buildSharedSessionDiscoveryUrl(reservation.metadata.debugPort),
|
|
3097
|
+
options.timeoutMs
|
|
3098
|
+
);
|
|
3099
|
+
browser = await import_playwright.chromium.connectOverCDP(browserWsUrl, {
|
|
3100
|
+
timeout: options.timeoutMs
|
|
3101
|
+
});
|
|
3102
|
+
const context = getPrimaryBrowserContext(browser);
|
|
3103
|
+
page = await getSharedSessionPage(context, reservation.reuseExistingPage);
|
|
3104
|
+
if (options.initialUrl) {
|
|
3105
|
+
await page.goto(options.initialUrl, {
|
|
3106
|
+
timeout: options.timeoutMs,
|
|
3107
|
+
waitUntil: "domcontentloaded"
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
return {
|
|
3111
|
+
browser,
|
|
3112
|
+
clientId: reservation.client.clientId,
|
|
3113
|
+
context,
|
|
3114
|
+
page,
|
|
3115
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
3116
|
+
sessionId: reservation.metadata.sessionId
|
|
3117
|
+
};
|
|
3118
|
+
} catch (error) {
|
|
3119
|
+
if (page) {
|
|
3120
|
+
await page.close().catch(() => void 0);
|
|
3121
|
+
}
|
|
3122
|
+
if (browser) {
|
|
3123
|
+
await browser.close().catch(() => void 0);
|
|
3124
|
+
}
|
|
3125
|
+
await cleanupFailedSharedSessionAttach({
|
|
3126
|
+
clientId: reservation.client.clientId,
|
|
3127
|
+
persistentUserDataDir: reservation.metadata.persistentUserDataDir,
|
|
3128
|
+
sessionId: reservation.metadata.sessionId
|
|
3129
|
+
});
|
|
3130
|
+
throw error;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
async function releaseSharedSessionClient(context) {
|
|
3134
|
+
const releasePlan = await prepareSharedSessionCloseIfIdle(
|
|
3135
|
+
context.persistentUserDataDir,
|
|
3136
|
+
context.clientId,
|
|
3137
|
+
context.sessionId
|
|
3138
|
+
);
|
|
3139
|
+
if (releasePlan.closeBrowser) {
|
|
3140
|
+
await closeSharedSessionBrowser(
|
|
3141
|
+
context.persistentUserDataDir,
|
|
3142
|
+
releasePlan,
|
|
3143
|
+
context.browser
|
|
3144
|
+
);
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
await context.page.close().catch(() => void 0);
|
|
3148
|
+
await context.browser.close().catch(() => void 0);
|
|
3149
|
+
}
|
|
3150
|
+
async function inspectSharedSessionState(options) {
|
|
3151
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
3152
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
3153
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3154
|
+
if (!metadata) {
|
|
3155
|
+
if (liveClients.length > 0) {
|
|
3156
|
+
throw new Error(
|
|
3157
|
+
`Shared real-browser session metadata for "${persistentUserDataDir}" is missing while clients are still attached.`
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3161
|
+
force: true,
|
|
3162
|
+
recursive: true
|
|
3163
|
+
}).catch(() => void 0);
|
|
3164
|
+
return { kind: "missing" };
|
|
3165
|
+
}
|
|
3166
|
+
assertSharedSessionCompatibility(metadata, options);
|
|
3167
|
+
const browserState = await getProcessLiveness(metadata.browserOwner);
|
|
3168
|
+
if (browserState === "dead") {
|
|
3169
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3170
|
+
force: true,
|
|
3171
|
+
recursive: true
|
|
3172
|
+
}).catch(() => void 0);
|
|
3173
|
+
return { kind: "missing" };
|
|
3174
|
+
}
|
|
3175
|
+
if (metadata.state === "ready") {
|
|
3176
|
+
return {
|
|
3177
|
+
kind: "ready",
|
|
3178
|
+
metadata
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
const stateOwnerState = await getProcessLiveness(metadata.stateOwner);
|
|
3182
|
+
if (stateOwnerState === "dead") {
|
|
3183
|
+
const recoveredMetadata = {
|
|
3184
|
+
...metadata,
|
|
3185
|
+
state: "ready"
|
|
3186
|
+
};
|
|
3187
|
+
await writeSharedSessionMetadata(persistentUserDataDir, recoveredMetadata);
|
|
3188
|
+
return {
|
|
3189
|
+
kind: "ready",
|
|
3190
|
+
metadata: recoveredMetadata
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
return { kind: "wait" };
|
|
3194
|
+
}
|
|
3195
|
+
async function launchSharedSession(options) {
|
|
3196
|
+
const persistentUserDataDir = options.persistentProfile.userDataDir;
|
|
3197
|
+
await clearPersistentProfileSingletons(persistentUserDataDir);
|
|
3198
|
+
const debugPort = await reserveDebugPort();
|
|
3199
|
+
const launchArgs = buildRealBrowserLaunchArgs({
|
|
3200
|
+
debugPort,
|
|
3201
|
+
headless: options.headless,
|
|
3202
|
+
profileDirectory: options.profileDirectory,
|
|
3203
|
+
userDataDir: persistentUserDataDir
|
|
3204
|
+
});
|
|
3205
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
3206
|
+
const processHandle = (0, import_node_child_process3.spawn)(options.executablePath, launchArgs, {
|
|
3207
|
+
detached: processPolicy.detached,
|
|
3208
|
+
stdio: "ignore"
|
|
3209
|
+
});
|
|
3210
|
+
if (processPolicy.shouldUnref) {
|
|
3211
|
+
processHandle.unref();
|
|
3212
|
+
}
|
|
3213
|
+
try {
|
|
3214
|
+
const browserOwner = await waitForSpawnedProcessOwner(
|
|
3215
|
+
processHandle.pid,
|
|
3216
|
+
options.timeoutMs
|
|
3217
|
+
);
|
|
3218
|
+
const metadata = {
|
|
3219
|
+
browserOwner,
|
|
3220
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3221
|
+
debugPort,
|
|
3222
|
+
executablePath: options.executablePath,
|
|
3223
|
+
headless: options.headless,
|
|
3224
|
+
persistentUserDataDir,
|
|
3225
|
+
profileDirectory: options.profileDirectory,
|
|
3226
|
+
sessionId: (0, import_node_crypto4.randomUUID)(),
|
|
3227
|
+
state: "launching",
|
|
3228
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
3229
|
+
};
|
|
3230
|
+
await writeSharedSessionMetadata(persistentUserDataDir, metadata);
|
|
3231
|
+
return {
|
|
3232
|
+
launchedBrowserOwner: browserOwner,
|
|
3233
|
+
metadata
|
|
3234
|
+
};
|
|
3235
|
+
} catch (error) {
|
|
3236
|
+
await killSpawnedBrowserProcess(processHandle);
|
|
3237
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3238
|
+
force: true,
|
|
3239
|
+
recursive: true
|
|
3240
|
+
}).catch(() => void 0);
|
|
3241
|
+
throw error;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
async function cleanupFailedSharedSessionLaunch(reservation) {
|
|
3245
|
+
const shouldPreserveLiveBrowser = await withSharedSessionLock(
|
|
3246
|
+
reservation.metadata.persistentUserDataDir,
|
|
3247
|
+
async () => {
|
|
3248
|
+
const metadata = await readSharedSessionMetadata(
|
|
3249
|
+
reservation.metadata.persistentUserDataDir
|
|
3250
|
+
);
|
|
3251
|
+
if (metadata && metadata.sessionId === reservation.metadata.sessionId && processOwnersEqual(
|
|
3252
|
+
metadata.browserOwner,
|
|
3253
|
+
reservation.launchedBrowserOwner
|
|
3254
|
+
)) {
|
|
3255
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
3256
|
+
const readyMetadata = {
|
|
3257
|
+
...metadata,
|
|
3258
|
+
state: "ready"
|
|
3259
|
+
};
|
|
3260
|
+
await writeSharedSessionMetadata(
|
|
3261
|
+
reservation.metadata.persistentUserDataDir,
|
|
3262
|
+
readyMetadata
|
|
3263
|
+
);
|
|
3264
|
+
return true;
|
|
3265
|
+
}
|
|
3266
|
+
await (0, import_promises5.rm)(
|
|
3267
|
+
buildSharedSessionDirPath(
|
|
3268
|
+
reservation.metadata.persistentUserDataDir
|
|
3269
|
+
),
|
|
3270
|
+
{
|
|
3271
|
+
force: true,
|
|
3272
|
+
recursive: true
|
|
3273
|
+
}
|
|
3274
|
+
).catch(() => void 0);
|
|
2702
3275
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
force: true
|
|
2706
|
-
}).catch(() => void 0);
|
|
2707
|
-
})
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
2708
3278
|
);
|
|
3279
|
+
if (shouldPreserveLiveBrowser) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
|
|
3283
|
+
await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
|
|
2709
3284
|
}
|
|
2710
|
-
async function
|
|
2711
|
-
const
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
if (firstDashIndex === -1 || secondDashIndex === -1) {
|
|
2719
|
-
return null;
|
|
3285
|
+
async function cleanupFailedSharedSessionAttach(options) {
|
|
3286
|
+
const closePlan = await prepareSharedSessionCloseIfIdle(
|
|
3287
|
+
options.persistentUserDataDir,
|
|
3288
|
+
options.clientId,
|
|
3289
|
+
options.sessionId
|
|
3290
|
+
);
|
|
3291
|
+
if (!closePlan.closeBrowser) {
|
|
3292
|
+
return;
|
|
2720
3293
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
3294
|
+
await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
|
|
3295
|
+
}
|
|
3296
|
+
async function waitForSharedSessionReady(metadata, timeoutMs) {
|
|
3297
|
+
await resolveCdpWebSocketUrl(
|
|
3298
|
+
buildSharedSessionDiscoveryUrl(metadata.debugPort),
|
|
3299
|
+
timeoutMs
|
|
2725
3300
|
);
|
|
2726
|
-
|
|
2727
|
-
|
|
3301
|
+
}
|
|
3302
|
+
function buildRealBrowserLaunchArgs(options) {
|
|
3303
|
+
const args = [
|
|
3304
|
+
`--user-data-dir=${options.userDataDir}`,
|
|
3305
|
+
`--profile-directory=${options.profileDirectory}`,
|
|
3306
|
+
`--remote-debugging-port=${options.debugPort}`,
|
|
3307
|
+
"--disable-blink-features=AutomationControlled"
|
|
3308
|
+
];
|
|
3309
|
+
if (options.headless) {
|
|
3310
|
+
args.push("--headless=new");
|
|
2728
3311
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
3312
|
+
return args;
|
|
3313
|
+
}
|
|
3314
|
+
async function requestBrowserShutdown(browser) {
|
|
3315
|
+
let session = null;
|
|
3316
|
+
try {
|
|
3317
|
+
session = await browser.newBrowserCDPSession();
|
|
3318
|
+
await session.send("Browser.close");
|
|
3319
|
+
} catch {
|
|
3320
|
+
} finally {
|
|
3321
|
+
await session?.detach().catch(() => void 0);
|
|
2731
3322
|
}
|
|
2732
|
-
return { pid, processStartedAtMs };
|
|
2733
3323
|
}
|
|
2734
|
-
function
|
|
3324
|
+
async function killOwnedBrowserProcess(owner) {
|
|
3325
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
await killOwnedBrowserProcessByPid(owner.pid);
|
|
3329
|
+
}
|
|
3330
|
+
async function killSpawnedBrowserProcess(processHandle) {
|
|
3331
|
+
const pid = processHandle.pid;
|
|
3332
|
+
if (!pid || processHandle.exitCode !== null) {
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
await killOwnedBrowserProcessByPid(pid);
|
|
3336
|
+
await waitForPidToExit(pid, 2e3);
|
|
3337
|
+
}
|
|
3338
|
+
async function killOwnedBrowserProcessByPid(pid) {
|
|
3339
|
+
const processPolicy = getOwnedRealBrowserProcessPolicy();
|
|
3340
|
+
if (processPolicy.killStrategy === "taskkill") {
|
|
3341
|
+
await new Promise((resolve) => {
|
|
3342
|
+
const killer = (0, import_node_child_process3.spawn)(
|
|
3343
|
+
"taskkill",
|
|
3344
|
+
["/pid", String(pid), "/t", "/f"],
|
|
3345
|
+
{
|
|
3346
|
+
stdio: "ignore"
|
|
3347
|
+
}
|
|
3348
|
+
);
|
|
3349
|
+
killer.on("error", () => resolve());
|
|
3350
|
+
killer.on("exit", () => resolve());
|
|
3351
|
+
});
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
if (processPolicy.killStrategy === "process-group") {
|
|
3355
|
+
try {
|
|
3356
|
+
process.kill(-pid, "SIGKILL");
|
|
3357
|
+
return;
|
|
3358
|
+
} catch {
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
2735
3361
|
try {
|
|
2736
|
-
process.kill(pid,
|
|
2737
|
-
|
|
2738
|
-
} catch (error) {
|
|
2739
|
-
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2740
|
-
return code !== "ESRCH";
|
|
3362
|
+
process.kill(pid, "SIGKILL");
|
|
3363
|
+
} catch {
|
|
2741
3364
|
}
|
|
2742
3365
|
}
|
|
2743
|
-
async function
|
|
2744
|
-
|
|
2745
|
-
|
|
3366
|
+
async function waitForProcessToExit(owner, timeoutMs) {
|
|
3367
|
+
const deadline = Date.now() + timeoutMs;
|
|
3368
|
+
while (Date.now() < deadline) {
|
|
3369
|
+
if (await getProcessLiveness(owner) === "dead") {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
await sleep5(50);
|
|
2746
3373
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
3374
|
+
}
|
|
3375
|
+
async function waitForPidToExit(pid, timeoutMs) {
|
|
3376
|
+
const deadline = Date.now() + timeoutMs;
|
|
3377
|
+
while (Date.now() < deadline) {
|
|
3378
|
+
if (!isProcessRunning(pid)) {
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
await sleep5(50);
|
|
2749
3382
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
3383
|
+
}
|
|
3384
|
+
async function waitForSpawnedProcessOwner(pid, timeoutMs) {
|
|
3385
|
+
if (!pid || pid <= 0) {
|
|
3386
|
+
throw new Error("Chrome did not expose a child process id.");
|
|
2752
3387
|
}
|
|
2753
|
-
|
|
3388
|
+
const deadline = Date.now() + timeoutMs;
|
|
3389
|
+
while (Date.now() < deadline) {
|
|
3390
|
+
const owner = await readProcessOwner(pid);
|
|
3391
|
+
if (owner) {
|
|
3392
|
+
return owner;
|
|
3393
|
+
}
|
|
3394
|
+
await sleep5(50);
|
|
3395
|
+
}
|
|
3396
|
+
throw new Error(
|
|
3397
|
+
`Chrome process ${pid} did not report a stable process start time.`
|
|
3398
|
+
);
|
|
2754
3399
|
}
|
|
2755
|
-
async function
|
|
2756
|
-
|
|
3400
|
+
async function withSharedSessionLock(persistentUserDataDir, action) {
|
|
3401
|
+
return await withDirLock(
|
|
3402
|
+
buildSharedSessionLockPath(persistentUserDataDir),
|
|
3403
|
+
action
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
async function registerSharedSessionClient(persistentUserDataDir, metadata) {
|
|
3407
|
+
const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
|
|
3408
|
+
const client = buildSharedSessionClientRegistration();
|
|
3409
|
+
await (0, import_promises5.mkdir)(buildSharedSessionClientsDirPath(persistentUserDataDir), {
|
|
3410
|
+
recursive: true
|
|
3411
|
+
});
|
|
3412
|
+
await (0, import_promises5.writeFile)(
|
|
3413
|
+
buildSharedSessionClientPath(persistentUserDataDir, client.clientId),
|
|
3414
|
+
JSON.stringify(client, null, 2),
|
|
3415
|
+
{
|
|
3416
|
+
flag: "wx"
|
|
3417
|
+
}
|
|
3418
|
+
);
|
|
3419
|
+
return {
|
|
3420
|
+
client,
|
|
3421
|
+
metadata,
|
|
3422
|
+
reuseExistingPage: liveClients.length === 0
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
async function removeSharedSessionClientRegistration(persistentUserDataDir, clientId) {
|
|
3426
|
+
await (0, import_promises5.rm)(buildSharedSessionClientPath(persistentUserDataDir, clientId), {
|
|
3427
|
+
force: true
|
|
3428
|
+
}).catch(() => void 0);
|
|
3429
|
+
}
|
|
3430
|
+
async function listLiveSharedSessionClients(persistentUserDataDir) {
|
|
3431
|
+
const clientsDirPath = buildSharedSessionClientsDirPath(persistentUserDataDir);
|
|
3432
|
+
let entries;
|
|
2757
3433
|
try {
|
|
2758
|
-
|
|
2759
|
-
|
|
3434
|
+
entries = await (0, import_promises5.readdir)(clientsDirPath, {
|
|
3435
|
+
encoding: "utf8",
|
|
3436
|
+
withFileTypes: true
|
|
3437
|
+
});
|
|
3438
|
+
} catch {
|
|
3439
|
+
return [];
|
|
3440
|
+
}
|
|
3441
|
+
const liveClients = [];
|
|
3442
|
+
for (const entry of entries) {
|
|
3443
|
+
if (!entry.isFile()) {
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
const filePath = (0, import_node_path5.join)(clientsDirPath, entry.name);
|
|
3447
|
+
const registration = await readSharedSessionClientRegistration(filePath);
|
|
3448
|
+
if (!registration) {
|
|
3449
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
3452
|
+
if (await getProcessLiveness(registration.owner) === "dead") {
|
|
3453
|
+
await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
liveClients.push(registration);
|
|
3457
|
+
}
|
|
3458
|
+
return liveClients;
|
|
3459
|
+
}
|
|
3460
|
+
async function readSharedSessionClientRegistration(filePath) {
|
|
3461
|
+
try {
|
|
3462
|
+
const raw = await (0, import_promises5.readFile)(filePath, "utf8");
|
|
3463
|
+
return parseSharedSessionClientRegistration(JSON.parse(raw));
|
|
3464
|
+
} catch {
|
|
2760
3465
|
return null;
|
|
2761
3466
|
}
|
|
2762
|
-
|
|
2763
|
-
|
|
3467
|
+
}
|
|
3468
|
+
function buildSharedSessionClientRegistration() {
|
|
3469
|
+
return {
|
|
3470
|
+
clientId: (0, import_node_crypto4.randomUUID)(),
|
|
3471
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3472
|
+
owner: CURRENT_PROCESS_OWNER
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
function parseSharedSessionClientRegistration(value) {
|
|
3476
|
+
if (!value || typeof value !== "object") {
|
|
2764
3477
|
return null;
|
|
2765
3478
|
}
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
]);
|
|
2770
|
-
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
3479
|
+
const parsed = value;
|
|
3480
|
+
const owner = parseProcessOwner(parsed.owner);
|
|
3481
|
+
if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
|
|
2771
3482
|
return null;
|
|
2772
3483
|
}
|
|
2773
|
-
return
|
|
2774
|
-
|
|
2775
|
-
|
|
3484
|
+
return {
|
|
3485
|
+
clientId: parsed.clientId,
|
|
3486
|
+
createdAt: parsed.createdAt,
|
|
3487
|
+
owner
|
|
3488
|
+
};
|
|
2776
3489
|
}
|
|
2777
|
-
function
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3490
|
+
function assertSharedSessionCompatibility(metadata, options) {
|
|
3491
|
+
if (metadata.executablePath !== options.executablePath) {
|
|
3492
|
+
throw new Error(
|
|
3493
|
+
`Chrome profile "${options.profileDirectory}" is already running with executable "${metadata.executablePath}", not "${options.executablePath}".`
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3496
|
+
if (metadata.headless !== options.headless) {
|
|
3497
|
+
throw new Error(
|
|
3498
|
+
`Chrome profile "${options.profileDirectory}" is already running with headless=${metadata.headless}, not ${options.headless}.`
|
|
3499
|
+
);
|
|
2781
3500
|
}
|
|
2782
|
-
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
2783
|
-
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
2784
|
-
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
2785
3501
|
}
|
|
2786
|
-
async function
|
|
2787
|
-
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
3502
|
+
async function prepareSharedSessionCloseIfIdle(persistentUserDataDir, clientId, sessionId) {
|
|
3503
|
+
return await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
3504
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3505
|
+
await removeSharedSessionClientRegistration(
|
|
3506
|
+
persistentUserDataDir,
|
|
3507
|
+
clientId
|
|
3508
|
+
);
|
|
3509
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
3510
|
+
return {
|
|
3511
|
+
closeBrowser: false,
|
|
3512
|
+
sessionId
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
3516
|
+
persistentUserDataDir
|
|
3517
|
+
);
|
|
3518
|
+
if (liveClients.length > 0) {
|
|
3519
|
+
return {
|
|
3520
|
+
closeBrowser: false,
|
|
3521
|
+
sessionId: metadata.sessionId
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
const closingMetadata = {
|
|
3525
|
+
...metadata,
|
|
3526
|
+
state: "closing",
|
|
3527
|
+
stateOwner: CURRENT_PROCESS_OWNER
|
|
3528
|
+
};
|
|
3529
|
+
await writeSharedSessionMetadata(
|
|
3530
|
+
persistentUserDataDir,
|
|
3531
|
+
closingMetadata
|
|
3532
|
+
);
|
|
3533
|
+
return {
|
|
3534
|
+
browserOwner: closingMetadata.browserOwner,
|
|
3535
|
+
closeBrowser: true,
|
|
3536
|
+
sessionId: closingMetadata.sessionId
|
|
3537
|
+
};
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
async function closeSharedSessionBrowser(persistentUserDataDir, closePlan, browser) {
|
|
3541
|
+
if (browser) {
|
|
3542
|
+
await requestBrowserShutdown(browser);
|
|
3543
|
+
await waitForProcessToExit(closePlan.browserOwner, 1e3);
|
|
3544
|
+
}
|
|
3545
|
+
if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
|
|
3546
|
+
await killOwnedBrowserProcess(closePlan.browserOwner);
|
|
3547
|
+
await waitForProcessToExit(closePlan.browserOwner, 2e3);
|
|
3548
|
+
}
|
|
3549
|
+
await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
|
|
3550
|
+
}
|
|
3551
|
+
async function finalizeSharedSessionClose(persistentUserDataDir, sessionId) {
|
|
3552
|
+
await withSharedSessionLock(persistentUserDataDir, async () => {
|
|
3553
|
+
const metadata = await readSharedSessionMetadata(persistentUserDataDir);
|
|
3554
|
+
if (!metadata || metadata.sessionId !== sessionId) {
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
const liveClients = await listLiveSharedSessionClients(
|
|
3558
|
+
persistentUserDataDir
|
|
3559
|
+
);
|
|
3560
|
+
if (liveClients.length > 0) {
|
|
3561
|
+
const readyMetadata = {
|
|
3562
|
+
...metadata,
|
|
3563
|
+
state: "ready"
|
|
3564
|
+
};
|
|
3565
|
+
await writeSharedSessionMetadata(
|
|
3566
|
+
persistentUserDataDir,
|
|
3567
|
+
readyMetadata
|
|
3568
|
+
);
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
|
|
3572
|
+
const readyMetadata = {
|
|
3573
|
+
...metadata,
|
|
3574
|
+
state: "ready"
|
|
3575
|
+
};
|
|
3576
|
+
await writeSharedSessionMetadata(
|
|
3577
|
+
persistentUserDataDir,
|
|
3578
|
+
readyMetadata
|
|
3579
|
+
);
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
|
|
3583
|
+
force: true,
|
|
3584
|
+
recursive: true
|
|
3585
|
+
}).catch(() => void 0);
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
function getPrimaryBrowserContext(browser) {
|
|
3589
|
+
const contexts = browser.contexts();
|
|
3590
|
+
if (contexts.length === 0) {
|
|
3591
|
+
throw new Error(
|
|
3592
|
+
"Connection succeeded but no browser contexts were exposed."
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
return contexts[0];
|
|
3596
|
+
}
|
|
3597
|
+
async function getSharedSessionPage(context, reuseExistingPage) {
|
|
3598
|
+
if (reuseExistingPage) {
|
|
3599
|
+
return await getExistingPageOrCreate(context);
|
|
3600
|
+
}
|
|
3601
|
+
return await context.newPage();
|
|
3602
|
+
}
|
|
3603
|
+
async function getExistingPageOrCreate(context) {
|
|
3604
|
+
const existingPage = context.pages()[0];
|
|
3605
|
+
if (existingPage) {
|
|
3606
|
+
return existingPage;
|
|
3607
|
+
}
|
|
3608
|
+
return await context.newPage();
|
|
3609
|
+
}
|
|
3610
|
+
function buildSharedSessionDiscoveryUrl(debugPort) {
|
|
3611
|
+
return `http://127.0.0.1:${debugPort}`;
|
|
3612
|
+
}
|
|
3613
|
+
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
3614
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
3615
|
+
return cdpUrl;
|
|
3616
|
+
}
|
|
3617
|
+
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
3618
|
+
const deadline = Date.now() + timeoutMs;
|
|
3619
|
+
let lastError = "CDP discovery did not respond.";
|
|
3620
|
+
while (Date.now() < deadline) {
|
|
3621
|
+
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
3622
|
+
try {
|
|
3623
|
+
const response = await fetch(versionUrl, {
|
|
3624
|
+
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
3625
|
+
});
|
|
3626
|
+
if (!response.ok) {
|
|
3627
|
+
lastError = `${response.status} ${response.statusText}`;
|
|
3628
|
+
} else {
|
|
3629
|
+
const payload = await response.json();
|
|
3630
|
+
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
3631
|
+
if (wsUrl && wsUrl.trim()) {
|
|
3632
|
+
return wsUrl;
|
|
3633
|
+
}
|
|
3634
|
+
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
3635
|
+
}
|
|
3636
|
+
} catch (error) {
|
|
3637
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2792
3638
|
}
|
|
2793
|
-
|
|
2794
|
-
bootTimeLine.slice("btime ".length),
|
|
2795
|
-
10
|
|
2796
|
-
);
|
|
2797
|
-
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
2798
|
-
} catch {
|
|
2799
|
-
return null;
|
|
3639
|
+
await sleep5(100);
|
|
2800
3640
|
}
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
linuxClockTicksPerSecondPromise ??= execFileAsync(
|
|
2804
|
-
"getconf",
|
|
2805
|
-
["CLK_TCK"]
|
|
2806
|
-
).then(
|
|
2807
|
-
({ stdout }) => {
|
|
2808
|
-
const value = Number.parseInt(stdout.trim(), 10);
|
|
2809
|
-
return Number.isFinite(value) && value > 0 ? value : null;
|
|
2810
|
-
},
|
|
2811
|
-
() => null
|
|
3641
|
+
throw new Error(
|
|
3642
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2812
3643
|
);
|
|
2813
|
-
return await linuxClockTicksPerSecondPromise;
|
|
2814
3644
|
}
|
|
2815
|
-
|
|
3645
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
3646
|
+
let parsed;
|
|
2816
3647
|
try {
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
{
|
|
3648
|
+
parsed = new URL(cdpUrl);
|
|
3649
|
+
} catch {
|
|
3650
|
+
throw new Error(
|
|
3651
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
2821
3652
|
);
|
|
2822
|
-
return parsePsStartedAtMs(stdout);
|
|
2823
|
-
} catch (error) {
|
|
2824
|
-
return null;
|
|
2825
3653
|
}
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
const raw = stdout.trim();
|
|
2829
|
-
if (!raw) {
|
|
2830
|
-
return null;
|
|
3654
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
3655
|
+
return parsed;
|
|
2831
3656
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
}
|
|
2835
|
-
async function readWindowsProcessStartedAtMs(pid) {
|
|
2836
|
-
const script = [
|
|
2837
|
-
"$process = Get-Process -Id " + String(pid) + " -ErrorAction SilentlyContinue",
|
|
2838
|
-
"if ($null -eq $process) { exit 3 }",
|
|
2839
|
-
'$process.StartTime.ToUniversalTime().ToString("o")'
|
|
2840
|
-
].join("; ");
|
|
2841
|
-
try {
|
|
2842
|
-
const { stdout } = await execFileAsync(
|
|
2843
|
-
"powershell.exe",
|
|
2844
|
-
["-NoLogo", "-NoProfile", "-Command", script]
|
|
3657
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
3658
|
+
throw new Error(
|
|
3659
|
+
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
2845
3660
|
);
|
|
2846
|
-
return parsePsStartedAtMs(stdout);
|
|
2847
|
-
} catch (error) {
|
|
2848
|
-
return null;
|
|
2849
3661
|
}
|
|
3662
|
+
const normalized = new URL(parsed.toString());
|
|
3663
|
+
normalized.pathname = "/json/version";
|
|
3664
|
+
normalized.search = "";
|
|
3665
|
+
normalized.hash = "";
|
|
3666
|
+
return normalized;
|
|
2850
3667
|
}
|
|
2851
|
-
function
|
|
2852
|
-
return
|
|
3668
|
+
async function reserveDebugPort() {
|
|
3669
|
+
return await new Promise((resolve, reject) => {
|
|
3670
|
+
const server = (0, import_node_net.createServer)();
|
|
3671
|
+
server.unref();
|
|
3672
|
+
server.on("error", reject);
|
|
3673
|
+
server.listen(0, "127.0.0.1", () => {
|
|
3674
|
+
const address = server.address();
|
|
3675
|
+
if (!address || typeof address === "string") {
|
|
3676
|
+
server.close();
|
|
3677
|
+
reject(new Error("Failed to reserve a local debug port."));
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
server.close((error) => {
|
|
3681
|
+
if (error) {
|
|
3682
|
+
reject(error);
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
resolve(address.port);
|
|
3686
|
+
});
|
|
3687
|
+
});
|
|
3688
|
+
});
|
|
2853
3689
|
}
|
|
2854
|
-
async function
|
|
3690
|
+
async function sleep5(ms) {
|
|
2855
3691
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2856
3692
|
}
|
|
2857
3693
|
|
|
2858
3694
|
// src/browser/pool.ts
|
|
2859
3695
|
var BrowserPool = class {
|
|
2860
3696
|
browser = null;
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
managedRuntimeProfile = null;
|
|
3697
|
+
activeSessionClose = null;
|
|
3698
|
+
closeInFlight = null;
|
|
2864
3699
|
defaults;
|
|
2865
3700
|
constructor(defaults = {}) {
|
|
2866
3701
|
this.defaults = defaults;
|
|
2867
3702
|
}
|
|
2868
3703
|
async launch(options = {}) {
|
|
2869
|
-
if (this.browser || this.
|
|
3704
|
+
if (this.browser || this.activeSessionClose) {
|
|
2870
3705
|
await this.close();
|
|
2871
3706
|
}
|
|
2872
3707
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -2913,30 +3748,26 @@ var BrowserPool = class {
|
|
|
2913
3748
|
return this.launchSandbox(options);
|
|
2914
3749
|
}
|
|
2915
3750
|
async close() {
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
this.
|
|
2922
|
-
this.launchedProcess = null;
|
|
3751
|
+
if (this.closeInFlight) {
|
|
3752
|
+
await this.closeInFlight;
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
const closeOperation = this.closeCurrent();
|
|
3756
|
+
this.closeInFlight = closeOperation;
|
|
2923
3757
|
try {
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3758
|
+
await closeOperation;
|
|
3759
|
+
this.browser = null;
|
|
3760
|
+
this.activeSessionClose = null;
|
|
2927
3761
|
} finally {
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
if (this.managedRuntimeProfile === managedRuntimeProfile) {
|
|
2936
|
-
this.managedRuntimeProfile = null;
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
3762
|
+
this.closeInFlight = null;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
async closeCurrent() {
|
|
3766
|
+
if (this.activeSessionClose) {
|
|
3767
|
+
await this.activeSessionClose();
|
|
3768
|
+
return;
|
|
2939
3769
|
}
|
|
3770
|
+
await this.browser?.close().catch(() => void 0);
|
|
2940
3771
|
}
|
|
2941
3772
|
async connectToRunning(cdpUrl, timeout) {
|
|
2942
3773
|
let browser = null;
|
|
@@ -2951,11 +3782,14 @@ var BrowserPool = class {
|
|
|
2951
3782
|
}
|
|
2952
3783
|
cdpProxy = new CDPProxy(browserWsUrl, targetId);
|
|
2953
3784
|
const proxyWsUrl = await cdpProxy.start();
|
|
2954
|
-
browser = await
|
|
3785
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
2955
3786
|
timeout: timeout ?? 3e4
|
|
2956
3787
|
});
|
|
2957
3788
|
this.browser = browser;
|
|
2958
|
-
this.
|
|
3789
|
+
this.activeSessionClose = async () => {
|
|
3790
|
+
await browser?.close().catch(() => void 0);
|
|
3791
|
+
cdpProxy?.close();
|
|
3792
|
+
};
|
|
2959
3793
|
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
2960
3794
|
return { browser, context, page, isExternal: true };
|
|
2961
3795
|
} catch (error) {
|
|
@@ -2964,7 +3798,7 @@ var BrowserPool = class {
|
|
|
2964
3798
|
}
|
|
2965
3799
|
cdpProxy?.close();
|
|
2966
3800
|
this.browser = null;
|
|
2967
|
-
this.
|
|
3801
|
+
this.activeSessionClose = null;
|
|
2968
3802
|
throw error;
|
|
2969
3803
|
}
|
|
2970
3804
|
}
|
|
@@ -2984,60 +3818,29 @@ var BrowserPool = class {
|
|
|
2984
3818
|
sourceUserDataDir,
|
|
2985
3819
|
profileDirectory
|
|
2986
3820
|
);
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
const launchArgs = buildRealBrowserLaunchArgs({
|
|
2997
|
-
userDataDir: runtimeProfile.userDataDir,
|
|
3821
|
+
const sharedSession = await acquireSharedRealBrowserSession({
|
|
3822
|
+
executablePath,
|
|
3823
|
+
headless: resolveLaunchHeadless(
|
|
3824
|
+
"real",
|
|
3825
|
+
options.headless,
|
|
3826
|
+
this.defaults.headless
|
|
3827
|
+
),
|
|
3828
|
+
initialUrl: options.initialUrl,
|
|
3829
|
+
persistentProfile,
|
|
2998
3830
|
profileDirectory,
|
|
2999
|
-
|
|
3000
|
-
headless
|
|
3001
|
-
});
|
|
3002
|
-
const processHandle = (0, import_node_child_process2.spawn)(executablePath, launchArgs, {
|
|
3003
|
-
detached: process.platform !== "win32",
|
|
3004
|
-
stdio: "ignore"
|
|
3831
|
+
timeoutMs: options.timeout ?? 3e4
|
|
3005
3832
|
});
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
timeout: options.timeout ?? 3e4
|
|
3015
|
-
});
|
|
3016
|
-
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
3017
|
-
browser
|
|
3018
|
-
);
|
|
3019
|
-
if (options.initialUrl) {
|
|
3020
|
-
await page.goto(options.initialUrl, {
|
|
3021
|
-
waitUntil: "domcontentloaded",
|
|
3022
|
-
timeout: options.timeout ?? 3e4
|
|
3023
|
-
});
|
|
3024
|
-
}
|
|
3025
|
-
this.browser = browser;
|
|
3026
|
-
this.launchedProcess = processHandle;
|
|
3027
|
-
this.managedRuntimeProfile = runtimeProfile;
|
|
3028
|
-
return { browser, context, page, isExternal: false };
|
|
3029
|
-
} catch (error) {
|
|
3030
|
-
await browser?.close().catch(() => void 0);
|
|
3031
|
-
await killProcessTree(processHandle);
|
|
3032
|
-
await (0, import_promises2.rm)(runtimeProfile.userDataDir, {
|
|
3033
|
-
recursive: true,
|
|
3034
|
-
force: true
|
|
3035
|
-
}).catch(() => void 0);
|
|
3036
|
-
throw error;
|
|
3037
|
-
}
|
|
3833
|
+
this.browser = sharedSession.browser;
|
|
3834
|
+
this.activeSessionClose = sharedSession.close;
|
|
3835
|
+
return {
|
|
3836
|
+
browser: sharedSession.browser,
|
|
3837
|
+
context: sharedSession.context,
|
|
3838
|
+
page: sharedSession.page,
|
|
3839
|
+
isExternal: false
|
|
3840
|
+
};
|
|
3038
3841
|
}
|
|
3039
3842
|
async launchSandbox(options) {
|
|
3040
|
-
const browser = await
|
|
3843
|
+
const browser = await import_playwright2.chromium.launch({
|
|
3041
3844
|
headless: resolveLaunchHeadless(
|
|
3042
3845
|
"chromium",
|
|
3043
3846
|
options.headless,
|
|
@@ -3050,11 +3853,14 @@ var BrowserPool = class {
|
|
|
3050
3853
|
const context = await browser.newContext(options.context || {});
|
|
3051
3854
|
const page = await context.newPage();
|
|
3052
3855
|
this.browser = browser;
|
|
3856
|
+
this.activeSessionClose = async () => {
|
|
3857
|
+
await browser.close().catch(() => void 0);
|
|
3858
|
+
};
|
|
3053
3859
|
return { browser, context, page, isExternal: false };
|
|
3054
3860
|
}
|
|
3055
3861
|
};
|
|
3056
3862
|
async function pickBrowserContextAndPage(browser) {
|
|
3057
|
-
const context =
|
|
3863
|
+
const context = getPrimaryBrowserContext2(browser);
|
|
3058
3864
|
const page = await getAttachedPageOrCreate(context);
|
|
3059
3865
|
return { context, page };
|
|
3060
3866
|
}
|
|
@@ -3067,11 +3873,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
|
3067
3873
|
}
|
|
3068
3874
|
return mode === "real";
|
|
3069
3875
|
}
|
|
3070
|
-
async function createOwnedBrowserContextAndPage(browser) {
|
|
3071
|
-
const context = getPrimaryBrowserContext(browser);
|
|
3072
|
-
const page = await getExistingPageOrCreate(context);
|
|
3073
|
-
return { context, page };
|
|
3074
|
-
}
|
|
3075
3876
|
async function getAttachedPageOrCreate(context) {
|
|
3076
3877
|
const pages = context.pages();
|
|
3077
3878
|
const inspectablePage = pages.find(
|
|
@@ -3086,14 +3887,7 @@ async function getAttachedPageOrCreate(context) {
|
|
|
3086
3887
|
}
|
|
3087
3888
|
return await context.newPage();
|
|
3088
3889
|
}
|
|
3089
|
-
|
|
3090
|
-
const existingPage = context.pages()[0];
|
|
3091
|
-
if (existingPage) {
|
|
3092
|
-
return existingPage;
|
|
3093
|
-
}
|
|
3094
|
-
return await context.newPage();
|
|
3095
|
-
}
|
|
3096
|
-
function getPrimaryBrowserContext(browser) {
|
|
3890
|
+
function getPrimaryBrowserContext2(browser) {
|
|
3097
3891
|
const contexts = browser.contexts();
|
|
3098
3892
|
if (contexts.length === 0) {
|
|
3099
3893
|
throw new Error(
|
|
@@ -3112,125 +3906,6 @@ function safePageUrl(page) {
|
|
|
3112
3906
|
return "";
|
|
3113
3907
|
}
|
|
3114
3908
|
}
|
|
3115
|
-
function normalizeDiscoveryUrl(cdpUrl) {
|
|
3116
|
-
let parsed;
|
|
3117
|
-
try {
|
|
3118
|
-
parsed = new URL(cdpUrl);
|
|
3119
|
-
} catch {
|
|
3120
|
-
throw new Error(
|
|
3121
|
-
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
3122
|
-
);
|
|
3123
|
-
}
|
|
3124
|
-
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
3125
|
-
return parsed;
|
|
3126
|
-
}
|
|
3127
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
3128
|
-
throw new Error(
|
|
3129
|
-
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
3130
|
-
);
|
|
3131
|
-
}
|
|
3132
|
-
const normalized = new URL(parsed.toString());
|
|
3133
|
-
normalized.pathname = "/json/version";
|
|
3134
|
-
normalized.search = "";
|
|
3135
|
-
normalized.hash = "";
|
|
3136
|
-
return normalized;
|
|
3137
|
-
}
|
|
3138
|
-
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
3139
|
-
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
3140
|
-
return cdpUrl;
|
|
3141
|
-
}
|
|
3142
|
-
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
3143
|
-
const deadline = Date.now() + timeoutMs;
|
|
3144
|
-
let lastError = "CDP discovery did not respond.";
|
|
3145
|
-
while (Date.now() < deadline) {
|
|
3146
|
-
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
3147
|
-
try {
|
|
3148
|
-
const response = await fetch(versionUrl, {
|
|
3149
|
-
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
3150
|
-
});
|
|
3151
|
-
if (!response.ok) {
|
|
3152
|
-
lastError = `${response.status} ${response.statusText}`;
|
|
3153
|
-
} else {
|
|
3154
|
-
const payload = await response.json();
|
|
3155
|
-
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
3156
|
-
if (wsUrl && wsUrl.trim()) {
|
|
3157
|
-
return wsUrl;
|
|
3158
|
-
}
|
|
3159
|
-
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
3160
|
-
}
|
|
3161
|
-
} catch (error) {
|
|
3162
|
-
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
3163
|
-
}
|
|
3164
|
-
await sleep2(100);
|
|
3165
|
-
}
|
|
3166
|
-
throw new Error(
|
|
3167
|
-
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
3168
|
-
);
|
|
3169
|
-
}
|
|
3170
|
-
async function reserveDebugPort() {
|
|
3171
|
-
return await new Promise((resolve, reject) => {
|
|
3172
|
-
const server = (0, import_node_net.createServer)();
|
|
3173
|
-
server.unref();
|
|
3174
|
-
server.on("error", reject);
|
|
3175
|
-
server.listen(0, "127.0.0.1", () => {
|
|
3176
|
-
const address = server.address();
|
|
3177
|
-
if (!address || typeof address === "string") {
|
|
3178
|
-
server.close();
|
|
3179
|
-
reject(new Error("Failed to reserve a local debug port."));
|
|
3180
|
-
return;
|
|
3181
|
-
}
|
|
3182
|
-
server.close((error) => {
|
|
3183
|
-
if (error) {
|
|
3184
|
-
reject(error);
|
|
3185
|
-
return;
|
|
3186
|
-
}
|
|
3187
|
-
resolve(address.port);
|
|
3188
|
-
});
|
|
3189
|
-
});
|
|
3190
|
-
});
|
|
3191
|
-
}
|
|
3192
|
-
function buildRealBrowserLaunchArgs(options) {
|
|
3193
|
-
const args = [
|
|
3194
|
-
`--user-data-dir=${options.userDataDir}`,
|
|
3195
|
-
`--profile-directory=${options.profileDirectory}`,
|
|
3196
|
-
`--remote-debugging-port=${options.debugPort}`,
|
|
3197
|
-
"--disable-blink-features=AutomationControlled"
|
|
3198
|
-
];
|
|
3199
|
-
if (options.headless) {
|
|
3200
|
-
args.push("--headless=new");
|
|
3201
|
-
}
|
|
3202
|
-
return args;
|
|
3203
|
-
}
|
|
3204
|
-
async function killProcessTree(processHandle) {
|
|
3205
|
-
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
3206
|
-
return;
|
|
3207
|
-
}
|
|
3208
|
-
if (process.platform === "win32") {
|
|
3209
|
-
await new Promise((resolve) => {
|
|
3210
|
-
const killer = (0, import_node_child_process2.spawn)(
|
|
3211
|
-
"taskkill",
|
|
3212
|
-
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
3213
|
-
{
|
|
3214
|
-
stdio: "ignore"
|
|
3215
|
-
}
|
|
3216
|
-
);
|
|
3217
|
-
killer.on("error", () => resolve());
|
|
3218
|
-
killer.on("exit", () => resolve());
|
|
3219
|
-
});
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
try {
|
|
3223
|
-
process.kill(-processHandle.pid, "SIGKILL");
|
|
3224
|
-
} catch {
|
|
3225
|
-
try {
|
|
3226
|
-
processHandle.kill("SIGKILL");
|
|
3227
|
-
} catch {
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
async function sleep2(ms) {
|
|
3232
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
3233
|
-
}
|
|
3234
3909
|
|
|
3235
3910
|
// src/navigation.ts
|
|
3236
3911
|
var DEFAULT_TIMEOUT = 3e4;
|
|
@@ -3610,7 +4285,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
3610
4285
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
3611
4286
|
Math.max(0, deadline - Date.now())
|
|
3612
4287
|
);
|
|
3613
|
-
await
|
|
4288
|
+
await sleep6(retryDelay);
|
|
3614
4289
|
}
|
|
3615
4290
|
}
|
|
3616
4291
|
}
|
|
@@ -3643,7 +4318,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
3643
4318
|
() => ({ kind: "resolved" }),
|
|
3644
4319
|
(error) => ({ kind: "rejected", error })
|
|
3645
4320
|
);
|
|
3646
|
-
const timeoutPromise =
|
|
4321
|
+
const timeoutPromise = sleep6(
|
|
3647
4322
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
3648
4323
|
).then(() => ({ kind: "timeout" }));
|
|
3649
4324
|
const result = await Promise.race([
|
|
@@ -3785,7 +4460,7 @@ function isIgnorableFrameError(error) {
|
|
|
3785
4460
|
const message = error.message;
|
|
3786
4461
|
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");
|
|
3787
4462
|
}
|
|
3788
|
-
function
|
|
4463
|
+
function sleep6(ms) {
|
|
3789
4464
|
return new Promise((resolve) => {
|
|
3790
4465
|
setTimeout(resolve, ms);
|
|
3791
4466
|
});
|
|
@@ -8036,7 +8711,7 @@ async function closeTab(context, activePage, index) {
|
|
|
8036
8711
|
}
|
|
8037
8712
|
|
|
8038
8713
|
// src/actions/cookies.ts
|
|
8039
|
-
var
|
|
8714
|
+
var import_promises6 = require("fs/promises");
|
|
8040
8715
|
async function getCookies(context, url) {
|
|
8041
8716
|
return context.cookies(url ? [url] : void 0);
|
|
8042
8717
|
}
|
|
@@ -8048,10 +8723,10 @@ async function clearCookies(context) {
|
|
|
8048
8723
|
}
|
|
8049
8724
|
async function exportCookies(context, filePath, url) {
|
|
8050
8725
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
8051
|
-
await (0,
|
|
8726
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
8052
8727
|
}
|
|
8053
8728
|
async function importCookies(context, filePath) {
|
|
8054
|
-
const raw = await (0,
|
|
8729
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
8055
8730
|
const cookies = JSON.parse(raw);
|
|
8056
8731
|
await context.addCookies(cookies);
|
|
8057
8732
|
}
|
|
@@ -8362,7 +9037,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8362
9037
|
this.idleSince = 0;
|
|
8363
9038
|
}
|
|
8364
9039
|
const remaining = Math.max(1, options.deadline - now);
|
|
8365
|
-
await
|
|
9040
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
8366
9041
|
}
|
|
8367
9042
|
}
|
|
8368
9043
|
handleRequestStarted = (request) => {
|
|
@@ -8407,7 +9082,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8407
9082
|
return false;
|
|
8408
9083
|
}
|
|
8409
9084
|
};
|
|
8410
|
-
async function
|
|
9085
|
+
async function sleep7(ms) {
|
|
8411
9086
|
await new Promise((resolve) => {
|
|
8412
9087
|
setTimeout(resolve, ms);
|
|
8413
9088
|
});
|
|
@@ -10109,13 +10784,13 @@ function dedupeNewest(entries) {
|
|
|
10109
10784
|
}
|
|
10110
10785
|
|
|
10111
10786
|
// src/cloud/cdp-client.ts
|
|
10112
|
-
var
|
|
10787
|
+
var import_playwright3 = require("playwright");
|
|
10113
10788
|
var CloudCdpClient = class {
|
|
10114
10789
|
async connect(args) {
|
|
10115
10790
|
const endpoint = withTokenQuery(args.wsUrl, args.token);
|
|
10116
10791
|
let browser;
|
|
10117
10792
|
try {
|
|
10118
|
-
browser = await
|
|
10793
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
10119
10794
|
} catch (error) {
|
|
10120
10795
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
10121
10796
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -11932,7 +12607,7 @@ async function executeAgentAction(page, action) {
|
|
|
11932
12607
|
}
|
|
11933
12608
|
case "wait": {
|
|
11934
12609
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
11935
|
-
await
|
|
12610
|
+
await sleep8(ms);
|
|
11936
12611
|
return;
|
|
11937
12612
|
}
|
|
11938
12613
|
case "goto": {
|
|
@@ -12097,7 +12772,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
12097
12772
|
}
|
|
12098
12773
|
}
|
|
12099
12774
|
}
|
|
12100
|
-
function
|
|
12775
|
+
function sleep8(ms) {
|
|
12101
12776
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
12102
12777
|
}
|
|
12103
12778
|
|
|
@@ -12128,7 +12803,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
12128
12803
|
if (isMutatingAgentAction(action)) {
|
|
12129
12804
|
this.onMutatingAction?.(action);
|
|
12130
12805
|
}
|
|
12131
|
-
await
|
|
12806
|
+
await sleep9(this.config.waitBetweenActionsMs);
|
|
12132
12807
|
});
|
|
12133
12808
|
try {
|
|
12134
12809
|
const result = await this.client.execute({
|
|
@@ -12190,7 +12865,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
12190
12865
|
await this.cursorController.preview({ x, y }, "agent");
|
|
12191
12866
|
}
|
|
12192
12867
|
};
|
|
12193
|
-
function
|
|
12868
|
+
function sleep9(ms) {
|
|
12194
12869
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
12195
12870
|
}
|
|
12196
12871
|
|
|
@@ -12626,7 +13301,7 @@ var CursorController = class {
|
|
|
12626
13301
|
for (const step of motion.points) {
|
|
12627
13302
|
await this.renderer.move(step, this.style);
|
|
12628
13303
|
if (motion.stepDelayMs > 0) {
|
|
12629
|
-
await
|
|
13304
|
+
await sleep10(motion.stepDelayMs);
|
|
12630
13305
|
}
|
|
12631
13306
|
}
|
|
12632
13307
|
if (shouldPulse(intent)) {
|
|
@@ -12784,7 +13459,7 @@ function clamp2(value, min, max) {
|
|
|
12784
13459
|
function shouldPulse(intent) {
|
|
12785
13460
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
12786
13461
|
}
|
|
12787
|
-
function
|
|
13462
|
+
function sleep10(ms) {
|
|
12788
13463
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12789
13464
|
}
|
|
12790
13465
|
|
|
@@ -13258,15 +13933,19 @@ var Opensteer = class _Opensteer {
|
|
|
13258
13933
|
}
|
|
13259
13934
|
return;
|
|
13260
13935
|
}
|
|
13936
|
+
let closedOwnedBrowser = false;
|
|
13261
13937
|
try {
|
|
13262
13938
|
if (this.ownsBrowser) {
|
|
13263
13939
|
await this.pool.close();
|
|
13940
|
+
closedOwnedBrowser = true;
|
|
13264
13941
|
}
|
|
13265
13942
|
} finally {
|
|
13266
13943
|
this.browser = null;
|
|
13267
13944
|
this.pageRef = null;
|
|
13268
13945
|
this.contextRef = null;
|
|
13269
|
-
this.ownsBrowser
|
|
13946
|
+
if (!this.ownsBrowser || closedOwnedBrowser) {
|
|
13947
|
+
this.ownsBrowser = false;
|
|
13948
|
+
}
|
|
13270
13949
|
if (this.cursorController) {
|
|
13271
13950
|
await this.cursorController.dispose().catch(() => void 0);
|
|
13272
13951
|
}
|
|
@@ -15597,19 +16276,19 @@ function buildLocalRunId(namespace) {
|
|
|
15597
16276
|
}
|
|
15598
16277
|
|
|
15599
16278
|
// src/browser/chromium-profile.ts
|
|
15600
|
-
var
|
|
15601
|
-
var
|
|
15602
|
-
var
|
|
15603
|
-
var
|
|
15604
|
-
var
|
|
15605
|
-
var
|
|
16279
|
+
var import_node_util3 = require("util");
|
|
16280
|
+
var import_node_child_process5 = require("child_process");
|
|
16281
|
+
var import_node_crypto5 = require("crypto");
|
|
16282
|
+
var import_promises7 = require("fs/promises");
|
|
16283
|
+
var import_node_fs4 = require("fs");
|
|
16284
|
+
var import_node_path6 = require("path");
|
|
15606
16285
|
var import_node_os2 = require("os");
|
|
15607
|
-
var
|
|
16286
|
+
var import_playwright4 = require("playwright");
|
|
15608
16287
|
|
|
15609
16288
|
// src/auth/keychain-store.ts
|
|
15610
|
-
var
|
|
16289
|
+
var import_node_child_process4 = require("child_process");
|
|
15611
16290
|
function commandExists(command) {
|
|
15612
|
-
const result = (0,
|
|
16291
|
+
const result = (0, import_node_child_process4.spawnSync)(command, ["--help"], {
|
|
15613
16292
|
encoding: "utf8",
|
|
15614
16293
|
stdio: "ignore"
|
|
15615
16294
|
});
|
|
@@ -15648,7 +16327,7 @@ function createMacosSecurityStore() {
|
|
|
15648
16327
|
return {
|
|
15649
16328
|
backend: "macos-security",
|
|
15650
16329
|
get(service, account) {
|
|
15651
|
-
const result = (0,
|
|
16330
|
+
const result = (0, import_node_child_process4.spawnSync)(
|
|
15652
16331
|
"security",
|
|
15653
16332
|
["find-generic-password", "-s", service, "-a", account, "-w"],
|
|
15654
16333
|
{ encoding: "utf8" }
|
|
@@ -15670,14 +16349,14 @@ function createMacosSecurityStore() {
|
|
|
15670
16349
|
"-w",
|
|
15671
16350
|
secret
|
|
15672
16351
|
];
|
|
15673
|
-
const result = (0,
|
|
16352
|
+
const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
|
|
15674
16353
|
if (commandFailed(result)) {
|
|
15675
16354
|
throw buildCommandError("security", args, result);
|
|
15676
16355
|
}
|
|
15677
16356
|
},
|
|
15678
16357
|
delete(service, account) {
|
|
15679
16358
|
const args = ["delete-generic-password", "-s", service, "-a", account];
|
|
15680
|
-
const result = (0,
|
|
16359
|
+
const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
|
|
15681
16360
|
if (commandFailed(result)) {
|
|
15682
16361
|
return;
|
|
15683
16362
|
}
|
|
@@ -15688,7 +16367,7 @@ function createLinuxSecretToolStore() {
|
|
|
15688
16367
|
return {
|
|
15689
16368
|
backend: "linux-secret-tool",
|
|
15690
16369
|
get(service, account) {
|
|
15691
|
-
const result = (0,
|
|
16370
|
+
const result = (0, import_node_child_process4.spawnSync)(
|
|
15692
16371
|
"secret-tool",
|
|
15693
16372
|
["lookup", "service", service, "account", account],
|
|
15694
16373
|
{
|
|
@@ -15711,7 +16390,7 @@ function createLinuxSecretToolStore() {
|
|
|
15711
16390
|
"account",
|
|
15712
16391
|
account
|
|
15713
16392
|
];
|
|
15714
|
-
const result = (0,
|
|
16393
|
+
const result = (0, import_node_child_process4.spawnSync)("secret-tool", args, {
|
|
15715
16394
|
encoding: "utf8",
|
|
15716
16395
|
input: secret
|
|
15717
16396
|
});
|
|
@@ -15721,7 +16400,7 @@ function createLinuxSecretToolStore() {
|
|
|
15721
16400
|
},
|
|
15722
16401
|
delete(service, account) {
|
|
15723
16402
|
const args = ["clear", "service", service, "account", account];
|
|
15724
|
-
(0,
|
|
16403
|
+
(0, import_node_child_process4.spawnSync)("secret-tool", args, {
|
|
15725
16404
|
encoding: "utf8"
|
|
15726
16405
|
});
|
|
15727
16406
|
}
|
|
@@ -15744,7 +16423,7 @@ function createKeychainStore() {
|
|
|
15744
16423
|
}
|
|
15745
16424
|
|
|
15746
16425
|
// src/browser/chromium-profile.ts
|
|
15747
|
-
var
|
|
16426
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
|
|
15748
16427
|
var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
|
|
15749
16428
|
var AES_BLOCK_BYTES = 16;
|
|
15750
16429
|
var MAC_KEY_ITERATIONS = 1003;
|
|
@@ -15803,20 +16482,20 @@ var CHROMIUM_BRANDS = [
|
|
|
15803
16482
|
];
|
|
15804
16483
|
function directoryExists(filePath) {
|
|
15805
16484
|
try {
|
|
15806
|
-
return (0,
|
|
16485
|
+
return (0, import_node_fs4.statSync)(filePath).isDirectory();
|
|
15807
16486
|
} catch {
|
|
15808
16487
|
return false;
|
|
15809
16488
|
}
|
|
15810
16489
|
}
|
|
15811
16490
|
function fileExists(filePath) {
|
|
15812
16491
|
try {
|
|
15813
|
-
return (0,
|
|
16492
|
+
return (0, import_node_fs4.statSync)(filePath).isFile();
|
|
15814
16493
|
} catch {
|
|
15815
16494
|
return false;
|
|
15816
16495
|
}
|
|
15817
16496
|
}
|
|
15818
16497
|
function resolveCookieDbPath(profileDir) {
|
|
15819
|
-
const candidates = [(0,
|
|
16498
|
+
const candidates = [(0, import_node_path6.join)(profileDir, "Network", "Cookies"), (0, import_node_path6.join)(profileDir, "Cookies")];
|
|
15820
16499
|
for (const candidate of candidates) {
|
|
15821
16500
|
if (fileExists(candidate)) {
|
|
15822
16501
|
return candidate;
|
|
@@ -15825,10 +16504,10 @@ function resolveCookieDbPath(profileDir) {
|
|
|
15825
16504
|
return null;
|
|
15826
16505
|
}
|
|
15827
16506
|
async function selectProfileDirFromUserDataDir(userDataDir) {
|
|
15828
|
-
const entries = await (0,
|
|
16507
|
+
const entries = await (0, import_promises7.readdir)(userDataDir, {
|
|
15829
16508
|
withFileTypes: true
|
|
15830
16509
|
}).catch(() => []);
|
|
15831
|
-
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0,
|
|
16510
|
+
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path6.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
|
|
15832
16511
|
return candidates;
|
|
15833
16512
|
}
|
|
15834
16513
|
async function resolveChromiumProfileLocation(inputPath) {
|
|
@@ -15836,16 +16515,16 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15836
16515
|
if (!expandedPath) {
|
|
15837
16516
|
throw new Error("Profile path cannot be empty.");
|
|
15838
16517
|
}
|
|
15839
|
-
if (fileExists(expandedPath) && (0,
|
|
15840
|
-
const directParent = (0,
|
|
15841
|
-
const profileDir = (0,
|
|
15842
|
-
const userDataDir = (0,
|
|
16518
|
+
if (fileExists(expandedPath) && (0, import_node_path6.basename)(expandedPath) === "Cookies") {
|
|
16519
|
+
const directParent = (0, import_node_path6.dirname)(expandedPath);
|
|
16520
|
+
const profileDir = (0, import_node_path6.basename)(directParent) === "Network" ? (0, import_node_path6.dirname)(directParent) : directParent;
|
|
16521
|
+
const userDataDir = (0, import_node_path6.dirname)(profileDir);
|
|
15843
16522
|
return {
|
|
15844
16523
|
userDataDir,
|
|
15845
16524
|
profileDir,
|
|
15846
|
-
profileDirectory: (0,
|
|
16525
|
+
profileDirectory: (0, import_node_path6.basename)(profileDir),
|
|
15847
16526
|
cookieDbPath: expandedPath,
|
|
15848
|
-
localStatePath: fileExists((0,
|
|
16527
|
+
localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
|
|
15849
16528
|
};
|
|
15850
16529
|
}
|
|
15851
16530
|
if (fileExists(expandedPath)) {
|
|
@@ -15860,16 +16539,16 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15860
16539
|
}
|
|
15861
16540
|
const directCookieDb = resolveCookieDbPath(expandedPath);
|
|
15862
16541
|
if (directCookieDb) {
|
|
15863
|
-
const userDataDir = (0,
|
|
16542
|
+
const userDataDir = (0, import_node_path6.dirname)(expandedPath);
|
|
15864
16543
|
return {
|
|
15865
16544
|
userDataDir,
|
|
15866
16545
|
profileDir: expandedPath,
|
|
15867
|
-
profileDirectory: (0,
|
|
16546
|
+
profileDirectory: (0, import_node_path6.basename)(expandedPath),
|
|
15868
16547
|
cookieDbPath: directCookieDb,
|
|
15869
|
-
localStatePath: fileExists((0,
|
|
16548
|
+
localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
|
|
15870
16549
|
};
|
|
15871
16550
|
}
|
|
15872
|
-
const localStatePath = (0,
|
|
16551
|
+
const localStatePath = (0, import_node_path6.join)(expandedPath, "Local State");
|
|
15873
16552
|
if (!fileExists(localStatePath)) {
|
|
15874
16553
|
throw new Error(
|
|
15875
16554
|
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
@@ -15882,7 +16561,7 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15882
16561
|
);
|
|
15883
16562
|
}
|
|
15884
16563
|
if (profileDirs.length > 1) {
|
|
15885
|
-
const candidates = profileDirs.map((entry) => (0,
|
|
16564
|
+
const candidates = profileDirs.map((entry) => (0, import_node_path6.basename)(entry)).join(", ");
|
|
15886
16565
|
throw new Error(
|
|
15887
16566
|
`"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
|
|
15888
16567
|
);
|
|
@@ -15897,7 +16576,7 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15897
16576
|
return {
|
|
15898
16577
|
userDataDir: expandedPath,
|
|
15899
16578
|
profileDir: selectedProfileDir,
|
|
15900
|
-
profileDirectory: (0,
|
|
16579
|
+
profileDirectory: (0, import_node_path6.basename)(selectedProfileDir),
|
|
15901
16580
|
cookieDbPath,
|
|
15902
16581
|
localStatePath
|
|
15903
16582
|
};
|
|
@@ -15912,25 +16591,25 @@ function detectChromiumBrand(location) {
|
|
|
15912
16591
|
return DEFAULT_CHROMIUM_BRAND;
|
|
15913
16592
|
}
|
|
15914
16593
|
async function createSqliteSnapshot(dbPath) {
|
|
15915
|
-
const snapshotDir = await (0,
|
|
15916
|
-
const snapshotPath = (0,
|
|
15917
|
-
await (0,
|
|
16594
|
+
const snapshotDir = await (0, import_promises7.mkdtemp)((0, import_node_path6.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
|
|
16595
|
+
const snapshotPath = (0, import_node_path6.join)(snapshotDir, "Cookies");
|
|
16596
|
+
await (0, import_promises7.copyFile)(dbPath, snapshotPath);
|
|
15918
16597
|
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
15919
16598
|
const source = `${dbPath}${suffix}`;
|
|
15920
|
-
if (!(0,
|
|
16599
|
+
if (!(0, import_node_fs4.existsSync)(source)) {
|
|
15921
16600
|
continue;
|
|
15922
16601
|
}
|
|
15923
|
-
await (0,
|
|
16602
|
+
await (0, import_promises7.copyFile)(source, `${snapshotPath}${suffix}`);
|
|
15924
16603
|
}
|
|
15925
16604
|
return {
|
|
15926
16605
|
snapshotPath,
|
|
15927
16606
|
cleanup: async () => {
|
|
15928
|
-
await (0,
|
|
16607
|
+
await (0, import_promises7.rm)(snapshotDir, { recursive: true, force: true });
|
|
15929
16608
|
}
|
|
15930
16609
|
};
|
|
15931
16610
|
}
|
|
15932
16611
|
async function querySqliteJson(dbPath, query) {
|
|
15933
|
-
const result = await
|
|
16612
|
+
const result = await execFileAsync3("sqlite3", ["-json", dbPath, query], {
|
|
15934
16613
|
encoding: "utf8",
|
|
15935
16614
|
maxBuffer: 64 * 1024 * 1024
|
|
15936
16615
|
});
|
|
@@ -15971,7 +16650,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
|
|
|
15971
16650
|
if (buffer.length < 32) {
|
|
15972
16651
|
return buffer;
|
|
15973
16652
|
}
|
|
15974
|
-
const domainHash = (0,
|
|
16653
|
+
const domainHash = (0, import_node_crypto5.createHash)("sha256").update(hostKey, "utf8").digest();
|
|
15975
16654
|
if (buffer.subarray(0, 32).equals(domainHash)) {
|
|
15976
16655
|
return buffer.subarray(32);
|
|
15977
16656
|
}
|
|
@@ -15980,7 +16659,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
|
|
|
15980
16659
|
function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
|
|
15981
16660
|
const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
|
|
15982
16661
|
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
15983
|
-
const decipher = (0,
|
|
16662
|
+
const decipher = (0, import_node_crypto5.createDecipheriv)("aes-128-cbc", key, iv);
|
|
15984
16663
|
const plaintext = Buffer.concat([
|
|
15985
16664
|
decipher.update(ciphertext),
|
|
15986
16665
|
decipher.final()
|
|
@@ -15993,7 +16672,7 @@ function decryptChromiumAes256GcmValue(encryptedValue, key) {
|
|
|
15993
16672
|
const nonce = encryptedValue.subarray(3, 15);
|
|
15994
16673
|
const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
|
|
15995
16674
|
const authTag = encryptedValue.subarray(encryptedValue.length - 16);
|
|
15996
|
-
const decipher = (0,
|
|
16675
|
+
const decipher = (0, import_node_crypto5.createDecipheriv)("aes-256-gcm", key, nonce);
|
|
15997
16676
|
decipher.setAuthTag(authTag);
|
|
15998
16677
|
return Buffer.concat([
|
|
15999
16678
|
decipher.update(ciphertext),
|
|
@@ -16010,7 +16689,7 @@ async function dpapiUnprotect(buffer) {
|
|
|
16010
16689
|
")",
|
|
16011
16690
|
"[Convert]::ToBase64String($plainBytes)"
|
|
16012
16691
|
].join("\n");
|
|
16013
|
-
const { stdout } = await
|
|
16692
|
+
const { stdout } = await execFileAsync3(
|
|
16014
16693
|
"powershell.exe",
|
|
16015
16694
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
16016
16695
|
{
|
|
@@ -16030,7 +16709,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
16030
16709
|
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
16031
16710
|
);
|
|
16032
16711
|
}
|
|
16033
|
-
const key = (0,
|
|
16712
|
+
const key = (0, import_node_crypto5.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
|
|
16034
16713
|
return async (row) => decryptChromiumAes128CbcValue(
|
|
16035
16714
|
Buffer.from(row.encrypted_value || "", "hex"),
|
|
16036
16715
|
key,
|
|
@@ -16041,7 +16720,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
16041
16720
|
const brand = detectChromiumBrand(location);
|
|
16042
16721
|
const keychainStore = createKeychainStore();
|
|
16043
16722
|
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
|
|
16044
|
-
const key = (0,
|
|
16723
|
+
const key = (0, import_node_crypto5.pbkdf2Sync)(
|
|
16045
16724
|
password || "peanuts",
|
|
16046
16725
|
KEY_SALT,
|
|
16047
16726
|
LINUX_KEY_ITERATIONS,
|
|
@@ -16061,7 +16740,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
16061
16740
|
);
|
|
16062
16741
|
}
|
|
16063
16742
|
const localState = JSON.parse(
|
|
16064
|
-
await (0,
|
|
16743
|
+
await (0, import_promises7.readFile)(location.localStatePath, "utf8")
|
|
16065
16744
|
);
|
|
16066
16745
|
const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
|
|
16067
16746
|
if (!encryptedKeyBase64) {
|
|
@@ -16155,22 +16834,22 @@ async function loadCookiesFromSqlite(location) {
|
|
|
16155
16834
|
}
|
|
16156
16835
|
}
|
|
16157
16836
|
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
16158
|
-
const snapshotRootDir = await (0,
|
|
16159
|
-
const snapshotProfileDir = (0,
|
|
16837
|
+
const snapshotRootDir = await (0, import_promises7.mkdtemp)((0, import_node_path6.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
|
|
16838
|
+
const snapshotProfileDir = (0, import_node_path6.join)(
|
|
16160
16839
|
snapshotRootDir,
|
|
16161
|
-
(0,
|
|
16840
|
+
(0, import_node_path6.basename)(location.profileDir)
|
|
16162
16841
|
);
|
|
16163
16842
|
let context = null;
|
|
16164
16843
|
try {
|
|
16165
|
-
await (0,
|
|
16844
|
+
await (0, import_promises7.cp)(location.profileDir, snapshotProfileDir, {
|
|
16166
16845
|
recursive: true
|
|
16167
16846
|
});
|
|
16168
16847
|
if (location.localStatePath) {
|
|
16169
|
-
await (0,
|
|
16848
|
+
await (0, import_promises7.copyFile)(location.localStatePath, (0, import_node_path6.join)(snapshotRootDir, "Local State"));
|
|
16170
16849
|
}
|
|
16171
16850
|
const brand = detectChromiumBrand(location);
|
|
16172
|
-
const args = [`--profile-directory=${(0,
|
|
16173
|
-
context = await
|
|
16851
|
+
const args = [`--profile-directory=${(0, import_node_path6.basename)(snapshotProfileDir)}`];
|
|
16852
|
+
context = await import_playwright4.chromium.launchPersistentContext(snapshotRootDir, {
|
|
16174
16853
|
channel: brand.playwrightChannel,
|
|
16175
16854
|
headless: options.headless ?? true,
|
|
16176
16855
|
timeout: options.timeout ?? 12e4,
|
|
@@ -16179,7 +16858,7 @@ async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
|
16179
16858
|
return await context.cookies();
|
|
16180
16859
|
} finally {
|
|
16181
16860
|
await context?.close().catch(() => void 0);
|
|
16182
|
-
await (0,
|
|
16861
|
+
await (0, import_promises7.rm)(snapshotRootDir, { recursive: true, force: true });
|
|
16183
16862
|
}
|
|
16184
16863
|
}
|
|
16185
16864
|
function isMissingSqliteBinary(error) {
|
|
@@ -16422,10 +17101,10 @@ function toResolvedCloudCredential(source, credential) {
|
|
|
16422
17101
|
}
|
|
16423
17102
|
|
|
16424
17103
|
// src/auth/machine-credential-store.ts
|
|
16425
|
-
var
|
|
16426
|
-
var
|
|
17104
|
+
var import_node_crypto6 = require("crypto");
|
|
17105
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
16427
17106
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16428
|
-
var
|
|
17107
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
16429
17108
|
var METADATA_VERSION = 2;
|
|
16430
17109
|
var ACTIVE_TARGET_VERSION = 2;
|
|
16431
17110
|
var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
|
|
@@ -16440,7 +17119,7 @@ var MachineCredentialStore = class {
|
|
|
16440
17119
|
const appName = options.appName || "opensteer";
|
|
16441
17120
|
const env = options.env ?? process.env;
|
|
16442
17121
|
const configDir = resolveConfigDir(appName, env);
|
|
16443
|
-
this.authDir =
|
|
17122
|
+
this.authDir = import_node_path7.default.join(configDir, "auth");
|
|
16444
17123
|
this.warn = options.warn ?? (() => void 0);
|
|
16445
17124
|
}
|
|
16446
17125
|
readCloudCredential(target) {
|
|
@@ -16570,18 +17249,18 @@ function createMachineCredentialStore(options = {}) {
|
|
|
16570
17249
|
}
|
|
16571
17250
|
function resolveCredentialSlot(authDir, target) {
|
|
16572
17251
|
const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
|
|
16573
|
-
const storageKey = (0,
|
|
17252
|
+
const storageKey = (0, import_node_crypto6.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
|
|
16574
17253
|
return {
|
|
16575
17254
|
keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
|
|
16576
|
-
metadataPath:
|
|
16577
|
-
fallbackSecretPath:
|
|
17255
|
+
metadataPath: import_node_path7.default.join(authDir, `cli-login.${storageKey}.json`),
|
|
17256
|
+
fallbackSecretPath: import_node_path7.default.join(
|
|
16578
17257
|
authDir,
|
|
16579
17258
|
`cli-login.${storageKey}.secret.json`
|
|
16580
17259
|
)
|
|
16581
17260
|
};
|
|
16582
17261
|
}
|
|
16583
17262
|
function resolveActiveTargetPath(authDir) {
|
|
16584
|
-
return
|
|
17263
|
+
return import_node_path7.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
|
|
16585
17264
|
}
|
|
16586
17265
|
function matchesCredentialTarget(value, target) {
|
|
16587
17266
|
return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
|
|
@@ -16600,36 +17279,36 @@ function normalizeCloudCredentialTarget(target) {
|
|
|
16600
17279
|
}
|
|
16601
17280
|
function resolveConfigDir(appName, env) {
|
|
16602
17281
|
if (process.platform === "win32") {
|
|
16603
|
-
const appData = env.APPDATA?.trim() ||
|
|
16604
|
-
return
|
|
17282
|
+
const appData = env.APPDATA?.trim() || import_node_path7.default.join(import_node_os3.default.homedir(), "AppData", "Roaming");
|
|
17283
|
+
return import_node_path7.default.join(appData, appName);
|
|
16605
17284
|
}
|
|
16606
17285
|
if (process.platform === "darwin") {
|
|
16607
|
-
return
|
|
17286
|
+
return import_node_path7.default.join(
|
|
16608
17287
|
import_node_os3.default.homedir(),
|
|
16609
17288
|
"Library",
|
|
16610
17289
|
"Application Support",
|
|
16611
17290
|
appName
|
|
16612
17291
|
);
|
|
16613
17292
|
}
|
|
16614
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() ||
|
|
16615
|
-
return
|
|
17293
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || import_node_path7.default.join(import_node_os3.default.homedir(), ".config");
|
|
17294
|
+
return import_node_path7.default.join(xdgConfigHome, appName);
|
|
16616
17295
|
}
|
|
16617
17296
|
function ensureDirectory(directoryPath) {
|
|
16618
|
-
|
|
17297
|
+
import_node_fs5.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
16619
17298
|
}
|
|
16620
17299
|
function removeFileIfExists(filePath) {
|
|
16621
17300
|
try {
|
|
16622
|
-
|
|
17301
|
+
import_node_fs5.default.rmSync(filePath, { force: true });
|
|
16623
17302
|
} catch {
|
|
16624
17303
|
return;
|
|
16625
17304
|
}
|
|
16626
17305
|
}
|
|
16627
17306
|
function readMetadata(filePath) {
|
|
16628
|
-
if (!
|
|
17307
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
16629
17308
|
return null;
|
|
16630
17309
|
}
|
|
16631
17310
|
try {
|
|
16632
|
-
const raw =
|
|
17311
|
+
const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
|
|
16633
17312
|
const parsed = JSON.parse(raw);
|
|
16634
17313
|
if (parsed.version !== METADATA_VERSION) return null;
|
|
16635
17314
|
if (parsed.secretBackend !== "keychain" && parsed.secretBackend !== "file") {
|
|
@@ -16656,11 +17335,11 @@ function readMetadata(filePath) {
|
|
|
16656
17335
|
}
|
|
16657
17336
|
}
|
|
16658
17337
|
function readActiveCloudTargetMetadata(filePath) {
|
|
16659
|
-
if (!
|
|
17338
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
16660
17339
|
return null;
|
|
16661
17340
|
}
|
|
16662
17341
|
try {
|
|
16663
|
-
const raw =
|
|
17342
|
+
const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
|
|
16664
17343
|
const parsed = JSON.parse(raw);
|
|
16665
17344
|
if (parsed.version !== ACTIVE_TARGET_VERSION) {
|
|
16666
17345
|
return null;
|
|
@@ -16690,22 +17369,22 @@ function parseSecretPayload(raw) {
|
|
|
16690
17369
|
}
|
|
16691
17370
|
}
|
|
16692
17371
|
function readSecretFile(filePath) {
|
|
16693
|
-
if (!
|
|
17372
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
16694
17373
|
return null;
|
|
16695
17374
|
}
|
|
16696
17375
|
try {
|
|
16697
|
-
return parseSecretPayload(
|
|
17376
|
+
return parseSecretPayload(import_node_fs5.default.readFileSync(filePath, "utf8"));
|
|
16698
17377
|
} catch {
|
|
16699
17378
|
return null;
|
|
16700
17379
|
}
|
|
16701
17380
|
}
|
|
16702
17381
|
function writeJsonFile(filePath, value, options = {}) {
|
|
16703
|
-
|
|
17382
|
+
import_node_fs5.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
|
|
16704
17383
|
encoding: "utf8",
|
|
16705
17384
|
mode: options.mode ?? 384
|
|
16706
17385
|
});
|
|
16707
17386
|
if (typeof options.mode === "number") {
|
|
16708
|
-
|
|
17387
|
+
import_node_fs5.default.chmodSync(filePath, options.mode);
|
|
16709
17388
|
}
|
|
16710
17389
|
}
|
|
16711
17390
|
|
|
@@ -17039,7 +17718,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
17039
17718
|
const writeProgress = options.writeProgress ?? options.writeStdout ?? ((message) => process.stdout.write(message));
|
|
17040
17719
|
const writeStderr = options.writeStderr ?? ((message) => process.stderr.write(message));
|
|
17041
17720
|
const fetchFn = options.fetchFn ?? fetch;
|
|
17042
|
-
const
|
|
17721
|
+
const sleep11 = options.sleep ?? (async (ms) => {
|
|
17043
17722
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
17044
17723
|
});
|
|
17045
17724
|
const now = options.now ?? Date.now;
|
|
@@ -17083,7 +17762,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
17083
17762
|
fetchFn,
|
|
17084
17763
|
writeProgress,
|
|
17085
17764
|
openExternalUrl,
|
|
17086
|
-
sleep:
|
|
17765
|
+
sleep: sleep11,
|
|
17087
17766
|
now,
|
|
17088
17767
|
openBrowser: true
|
|
17089
17768
|
});
|
|
@@ -17495,7 +18174,7 @@ function createDefaultDeps() {
|
|
|
17495
18174
|
loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
|
|
17496
18175
|
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
17497
18176
|
confirm: async (message) => {
|
|
17498
|
-
const rl = (0,
|
|
18177
|
+
const rl = (0, import_promises8.createInterface)({
|
|
17499
18178
|
input: process.stdin,
|
|
17500
18179
|
output: process.stderr
|
|
17501
18180
|
});
|
|
@@ -17659,7 +18338,7 @@ async function resolveTargetProfileId(args, deps, client) {
|
|
|
17659
18338
|
"Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
|
|
17660
18339
|
);
|
|
17661
18340
|
}
|
|
17662
|
-
const defaultName = `Synced ${
|
|
18341
|
+
const defaultName = `Synced ${import_node_path8.default.basename(args.fromProfileDir)}`;
|
|
17663
18342
|
const shouldCreate = await deps.confirm(
|
|
17664
18343
|
`No destination profile provided. Create a new cloud profile named "${defaultName}"?`
|
|
17665
18344
|
);
|