opensteer 0.6.11 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JZF2WC7U.js → chunk-HBTSQ2V4.js} +2680 -455
- package/dist/cli/profile.cjs +1996 -536
- package/dist/cli/profile.js +1 -1
- package/dist/cli/server.cjs +1916 -456
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +2676 -456
- package/dist/index.d.cts +26 -12
- package/dist/index.d.ts +26 -12
- package/dist/index.js +9 -1
- package/package.json +1 -1
package/dist/cli/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);
|
|
@@ -732,9 +732,9 @@ function resolveNamespaceDir(rootDir, namespace) {
|
|
|
732
732
|
const selectorsRoot = import_path.default.resolve(rootDir, ".opensteer", "selectors");
|
|
733
733
|
const normalizedNamespace = normalizeNamespace(namespace);
|
|
734
734
|
const namespaceDir = import_path.default.resolve(selectorsRoot, normalizedNamespace);
|
|
735
|
-
const
|
|
736
|
-
if (
|
|
737
|
-
if (
|
|
735
|
+
const relative2 = import_path.default.relative(selectorsRoot, namespaceDir);
|
|
736
|
+
if (relative2 === "" || relative2 === ".") return namespaceDir;
|
|
737
|
+
if (relative2.startsWith("..") || import_path.default.isAbsolute(relative2)) {
|
|
738
738
|
throw new Error(
|
|
739
739
|
`Namespace "${namespace}" resolves outside selectors root.`
|
|
740
740
|
);
|
|
@@ -1291,8 +1291,8 @@ function resolveNamespace(config, rootDir) {
|
|
|
1291
1291
|
}
|
|
1292
1292
|
const caller = getCallerFilePath();
|
|
1293
1293
|
if (!caller) return normalizeNamespace("default");
|
|
1294
|
-
const
|
|
1295
|
-
const cleaned =
|
|
1294
|
+
const relative2 = import_path2.default.relative(rootDir, caller);
|
|
1295
|
+
const cleaned = relative2.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
|
|
1296
1296
|
return normalizeNamespace(cleaned || "default");
|
|
1297
1297
|
}
|
|
1298
1298
|
function getCallerFilePath() {
|
|
@@ -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,281 +1816,1892 @@ 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
1832
|
var import_node_fs = require("fs");
|
|
1824
|
-
var
|
|
1825
|
-
var import_node_os = require("os");
|
|
1833
|
+
var import_promises2 = require("fs/promises");
|
|
1826
1834
|
var import_node_path = require("path");
|
|
1827
|
-
|
|
1835
|
+
|
|
1836
|
+
// src/browser/process-owner.ts
|
|
1837
|
+
var import_node_child_process = require("child_process");
|
|
1838
|
+
var import_promises = require("fs/promises");
|
|
1839
|
+
var import_node_util = require("util");
|
|
1840
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
1828
1841
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
1829
1842
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
1830
|
-
var
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
var
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
"hyphen-data",
|
|
1851
|
-
"OnDeviceHeadSuggestModel",
|
|
1852
|
-
"OptimizationGuidePredictionModels",
|
|
1853
|
-
"Segmentation Platform",
|
|
1854
|
-
"SmartCardDeviceNames",
|
|
1855
|
-
"WidevineCdm",
|
|
1856
|
-
"pnacl"
|
|
1857
|
-
]);
|
|
1858
|
-
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
1859
|
-
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
1860
|
-
const targetUserDataDir = (0, import_node_path.join)(
|
|
1861
|
-
expandHome(profilesRootDir),
|
|
1862
|
-
buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
|
|
1863
|
-
);
|
|
1864
|
-
const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
|
|
1865
|
-
const metadata = buildPersistentProfileMetadata(
|
|
1866
|
-
resolvedSourceUserDataDir,
|
|
1867
|
-
profileDirectory
|
|
1868
|
-
);
|
|
1869
|
-
await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
|
|
1870
|
-
await cleanOrphanedTempDirs(
|
|
1871
|
-
(0, import_node_path.dirname)(targetUserDataDir),
|
|
1872
|
-
(0, import_node_path.basename)(targetUserDataDir)
|
|
1873
|
-
);
|
|
1874
|
-
if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
1875
|
-
throw new Error(
|
|
1876
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
1877
|
-
);
|
|
1843
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
1844
|
+
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
1845
|
+
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
1846
|
+
var CURRENT_PROCESS_OWNER = {
|
|
1847
|
+
pid: process.pid,
|
|
1848
|
+
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
1849
|
+
};
|
|
1850
|
+
var linuxClockTicksPerSecondPromise = null;
|
|
1851
|
+
function parseProcessOwner(value) {
|
|
1852
|
+
if (!value || typeof value !== "object") {
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
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;
|
|
1860
|
+
}
|
|
1861
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
1862
|
+
return null;
|
|
1878
1863
|
}
|
|
1879
|
-
const created = await createPersistentProfileClone(
|
|
1880
|
-
resolvedSourceUserDataDir,
|
|
1881
|
-
sourceProfileDir,
|
|
1882
|
-
targetUserDataDir,
|
|
1883
|
-
profileDirectory,
|
|
1884
|
-
metadata
|
|
1885
|
-
);
|
|
1886
|
-
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
1887
1864
|
return {
|
|
1888
|
-
|
|
1889
|
-
|
|
1865
|
+
pid,
|
|
1866
|
+
processStartedAtMs
|
|
1890
1867
|
};
|
|
1891
1868
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
recursive: true
|
|
1898
|
-
}).catch(() => void 0)
|
|
1899
|
-
)
|
|
1900
|
-
);
|
|
1869
|
+
function processOwnersEqual(left, right) {
|
|
1870
|
+
if (!left || !right) {
|
|
1871
|
+
return left === right;
|
|
1872
|
+
}
|
|
1873
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
1901
1874
|
}
|
|
1902
|
-
function
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1875
|
+
async function getProcessLiveness(owner) {
|
|
1876
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
1877
|
+
owner.processStartedAtMs,
|
|
1878
|
+
PROCESS_STARTED_AT_MS
|
|
1879
|
+
)) {
|
|
1880
|
+
return "live";
|
|
1881
|
+
}
|
|
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";
|
|
1907
1890
|
}
|
|
1908
|
-
function
|
|
1909
|
-
|
|
1891
|
+
function isProcessRunning(pid) {
|
|
1892
|
+
try {
|
|
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";
|
|
1898
|
+
}
|
|
1910
1899
|
}
|
|
1911
|
-
function
|
|
1912
|
-
const
|
|
1913
|
-
|
|
1900
|
+
async function readProcessOwner(pid) {
|
|
1901
|
+
const processStartedAtMs = await readProcessStartedAtMs(pid);
|
|
1902
|
+
if (processStartedAtMs === null) {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
return {
|
|
1906
|
+
pid,
|
|
1907
|
+
processStartedAtMs
|
|
1908
|
+
};
|
|
1914
1909
|
}
|
|
1915
|
-
function
|
|
1916
|
-
return
|
|
1910
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
1911
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
1917
1912
|
}
|
|
1918
|
-
async function
|
|
1919
|
-
|
|
1913
|
+
async function readProcessStartedAtMs(pid) {
|
|
1914
|
+
if (pid <= 0) {
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1917
|
+
if (process.platform === "linux") {
|
|
1918
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
1919
|
+
}
|
|
1920
|
+
if (process.platform === "win32") {
|
|
1921
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
1922
|
+
}
|
|
1923
|
+
return await readPsProcessStartedAtMs(pid);
|
|
1924
|
+
}
|
|
1925
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
1926
|
+
let statRaw;
|
|
1920
1927
|
try {
|
|
1921
|
-
|
|
1928
|
+
statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
1922
1929
|
} catch {
|
|
1923
|
-
return;
|
|
1930
|
+
return null;
|
|
1924
1931
|
}
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
if (entry === targetProfileDirectory) continue;
|
|
1929
|
-
const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
|
|
1930
|
-
const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
|
|
1931
|
-
if ((0, import_node_fs.existsSync)(targetPath)) continue;
|
|
1932
|
-
let entryStat;
|
|
1933
|
-
try {
|
|
1934
|
-
entryStat = await (0, import_promises.stat)(sourcePath);
|
|
1935
|
-
} catch {
|
|
1936
|
-
continue;
|
|
1937
|
-
}
|
|
1938
|
-
if (entryStat.isFile()) {
|
|
1939
|
-
copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
|
|
1940
|
-
} else if (entryStat.isDirectory()) {
|
|
1941
|
-
if (isProfileDirectory(sourceUserDataDir, entry)) continue;
|
|
1942
|
-
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
|
|
1943
|
-
copyTasks.push(
|
|
1944
|
-
(0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
|
|
1945
|
-
() => void 0
|
|
1946
|
-
)
|
|
1947
|
-
);
|
|
1948
|
-
}
|
|
1932
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
1933
|
+
if (startTicks === null) {
|
|
1934
|
+
return null;
|
|
1949
1935
|
}
|
|
1950
|
-
await Promise.all(
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1936
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
1937
|
+
readLinuxBootTimeMs(),
|
|
1938
|
+
readLinuxClockTicksPerSecond()
|
|
1939
|
+
]);
|
|
1940
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
return Math.floor(
|
|
1944
|
+
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
1956
1945
|
);
|
|
1957
1946
|
}
|
|
1958
|
-
function
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1947
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1948
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1949
|
+
if (closingParenIndex === -1) {
|
|
1950
|
+
return null;
|
|
1951
|
+
}
|
|
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;
|
|
1964
1955
|
}
|
|
1965
|
-
async function
|
|
1966
|
-
|
|
1967
|
-
|
|
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;
|
|
1962
|
+
}
|
|
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;
|
|
1968
1970
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
)
|
|
1972
|
-
|
|
1971
|
+
}
|
|
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);
|
|
1981
|
+
}
|
|
1982
|
+
return await linuxClockTicksPerSecondPromise;
|
|
1983
|
+
}
|
|
1984
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
1973
1985
|
try {
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
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
|
+
}
|
|
1981
1997
|
);
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1998
|
+
const isoTimestamp = stdout.trim();
|
|
1999
|
+
if (!isoTimestamp) {
|
|
2000
|
+
return null;
|
|
2001
|
+
}
|
|
2002
|
+
const startedAtMs = Date.parse(isoTimestamp);
|
|
2003
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
2004
|
+
} catch {
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
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
|
|
1988
2017
|
}
|
|
1989
|
-
|
|
2018
|
+
);
|
|
2019
|
+
const startedAt = stdout.trim();
|
|
2020
|
+
if (!startedAt) {
|
|
2021
|
+
return null;
|
|
1990
2022
|
}
|
|
1991
|
-
|
|
1992
|
-
return
|
|
2023
|
+
const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
|
|
2024
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
2025
|
+
} catch {
|
|
2026
|
+
return null;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
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);
|
|
2036
|
+
try {
|
|
2037
|
+
return await action();
|
|
1993
2038
|
} finally {
|
|
1994
|
-
|
|
1995
|
-
|
|
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;
|
|
2047
|
+
}
|
|
2048
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
async function tryAcquireDirLock(lockDirPath) {
|
|
2052
|
+
await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
|
|
2053
|
+
while (true) {
|
|
2054
|
+
const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
2055
|
+
try {
|
|
2056
|
+
await (0, import_promises2.mkdir)(tempLockDirPath);
|
|
2057
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
|
|
2058
|
+
try {
|
|
2059
|
+
await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
|
|
2060
|
+
break;
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
2063
|
+
throw error;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
} finally {
|
|
2067
|
+
await (0, import_promises2.rm)(tempLockDirPath, {
|
|
1996
2068
|
recursive: true,
|
|
1997
2069
|
force: true
|
|
1998
2070
|
}).catch(() => void 0);
|
|
1999
2071
|
}
|
|
2072
|
+
const owner = await readLockOwner(lockDirPath);
|
|
2073
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
return null;
|
|
2000
2077
|
}
|
|
2078
|
+
return async () => {
|
|
2079
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
2080
|
+
recursive: true,
|
|
2081
|
+
force: true
|
|
2082
|
+
}).catch(() => void 0);
|
|
2083
|
+
};
|
|
2001
2084
|
}
|
|
2002
|
-
async function
|
|
2003
|
-
if ((0, import_node_fs.existsSync)(
|
|
2004
|
-
return;
|
|
2085
|
+
async function isDirLockHeld(lockDirPath) {
|
|
2086
|
+
if (!(0, import_node_fs.existsSync)(lockDirPath)) {
|
|
2087
|
+
return false;
|
|
2005
2088
|
}
|
|
2006
|
-
await
|
|
2089
|
+
const owner = await readLockOwner(lockDirPath);
|
|
2090
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
return (0, import_node_fs.existsSync)(lockDirPath);
|
|
2007
2094
|
}
|
|
2008
|
-
function
|
|
2009
|
-
|
|
2010
|
-
return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2095
|
+
function getErrorCode(error) {
|
|
2096
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2011
2097
|
}
|
|
2012
|
-
function
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
`${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
|
|
2016
|
-
);
|
|
2098
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
2099
|
+
const code = getErrorCode(error);
|
|
2100
|
+
return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
2017
2101
|
}
|
|
2018
|
-
async function
|
|
2019
|
-
|
|
2102
|
+
async function writeLockOwner(lockDirPath, owner) {
|
|
2103
|
+
await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
|
|
2104
|
+
}
|
|
2105
|
+
async function readLockOwner(lockDirPath) {
|
|
2106
|
+
return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
|
|
2107
|
+
}
|
|
2108
|
+
async function readLockParticipant(filePath) {
|
|
2109
|
+
return (await readLockParticipantRecord(filePath)).owner;
|
|
2110
|
+
}
|
|
2111
|
+
async function readLockParticipantRecord(filePath) {
|
|
2020
2112
|
try {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2113
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf8");
|
|
2114
|
+
const owner = parseProcessOwner(JSON.parse(raw));
|
|
2115
|
+
return {
|
|
2116
|
+
exists: true,
|
|
2117
|
+
owner
|
|
2118
|
+
};
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
return {
|
|
2121
|
+
exists: getErrorCode(error) !== "ENOENT",
|
|
2122
|
+
owner: null
|
|
2123
|
+
};
|
|
2027
2124
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
return;
|
|
2033
|
-
}
|
|
2034
|
-
if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
|
|
2035
|
-
return;
|
|
2036
|
-
}
|
|
2037
|
-
await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
|
|
2038
|
-
recursive: true,
|
|
2039
|
-
force: true
|
|
2040
|
-
}).catch(() => void 0);
|
|
2041
|
-
})
|
|
2125
|
+
}
|
|
2126
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
2127
|
+
return await readLockParticipantRecord(
|
|
2128
|
+
(0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
2042
2129
|
);
|
|
2043
2130
|
}
|
|
2044
|
-
function
|
|
2045
|
-
|
|
2046
|
-
if (!owner) {
|
|
2131
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
2132
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
2047
2133
|
return false;
|
|
2048
2134
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2135
|
+
let reclaimed = false;
|
|
2136
|
+
try {
|
|
2137
|
+
const owner = await readLockOwner(lockDirPath);
|
|
2138
|
+
if (!processOwnersEqual(owner, expectedOwner)) {
|
|
2139
|
+
return false;
|
|
2140
|
+
}
|
|
2141
|
+
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
await (0, import_promises2.rm)(lockDirPath, {
|
|
2145
|
+
recursive: true,
|
|
2146
|
+
force: true
|
|
2147
|
+
}).catch(() => void 0);
|
|
2148
|
+
reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
|
|
2149
|
+
return reclaimed;
|
|
2150
|
+
} finally {
|
|
2151
|
+
if (!reclaimed) {
|
|
2152
|
+
await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
|
|
2153
|
+
recursive: true,
|
|
2154
|
+
force: true
|
|
2155
|
+
}).catch(() => void 0);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
2160
|
+
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
2161
|
+
while (true) {
|
|
2162
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
|
|
2163
|
+
try {
|
|
2164
|
+
await (0, import_promises2.mkdir)(tempReclaimerDirPath);
|
|
2165
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
|
|
2166
|
+
try {
|
|
2167
|
+
await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
|
|
2168
|
+
return true;
|
|
2169
|
+
} catch (error) {
|
|
2170
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
2171
|
+
return false;
|
|
2172
|
+
}
|
|
2173
|
+
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
2174
|
+
throw error;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
const code = getErrorCode(error);
|
|
2179
|
+
if (code === "ENOENT") {
|
|
2180
|
+
return false;
|
|
2181
|
+
}
|
|
2182
|
+
throw error;
|
|
2183
|
+
} finally {
|
|
2184
|
+
await (0, import_promises2.rm)(tempReclaimerDirPath, {
|
|
2185
|
+
recursive: true,
|
|
2186
|
+
force: true
|
|
2187
|
+
}).catch(() => void 0);
|
|
2188
|
+
}
|
|
2189
|
+
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
2190
|
+
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
2191
|
+
return false;
|
|
2192
|
+
}
|
|
2193
|
+
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
2194
|
+
return false;
|
|
2195
|
+
}
|
|
2196
|
+
await (0, import_promises2.rm)(reclaimerDirPath, {
|
|
2197
|
+
recursive: true,
|
|
2198
|
+
force: true
|
|
2199
|
+
}).catch(() => void 0);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
2203
|
+
return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
2204
|
+
}
|
|
2205
|
+
async function sleep(ms) {
|
|
2206
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2207
|
+
}
|
|
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
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
async function acquirePersistentProfileWriteLock(targetUserDataDir) {
|
|
2218
|
+
const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
|
|
2219
|
+
const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
|
|
2220
|
+
targetUserDataDir
|
|
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
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
async function isPersistentProfileWriteLocked(targetUserDataDir) {
|
|
2237
|
+
return await isDirLockHeld(
|
|
2238
|
+
buildPersistentProfileWriteLockDirPath(targetUserDataDir)
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
|
|
2242
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
|
|
2243
|
+
}
|
|
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`
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
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) {
|
|
2838
|
+
let entries;
|
|
2839
|
+
try {
|
|
2840
|
+
entries = await (0, import_promises4.readdir)(rootDir, {
|
|
2841
|
+
encoding: "utf8",
|
|
2842
|
+
withFileTypes: true
|
|
2843
|
+
});
|
|
2844
|
+
} catch {
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
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);
|
|
3275
|
+
}
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
3279
|
+
if (shouldPreserveLiveBrowser) {
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
|
|
3283
|
+
await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
|
|
3284
|
+
}
|
|
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;
|
|
3293
|
+
}
|
|
3294
|
+
await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
|
|
3295
|
+
}
|
|
3296
|
+
async function waitForSharedSessionReady(metadata, timeoutMs) {
|
|
3297
|
+
await resolveCdpWebSocketUrl(
|
|
3298
|
+
buildSharedSessionDiscoveryUrl(metadata.debugPort),
|
|
3299
|
+
timeoutMs
|
|
3300
|
+
);
|
|
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");
|
|
3311
|
+
}
|
|
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);
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
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
|
+
}
|
|
3361
|
+
try {
|
|
3362
|
+
process.kill(pid, "SIGKILL");
|
|
3363
|
+
} catch {
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
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);
|
|
3373
|
+
}
|
|
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);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
async function waitForSpawnedProcessOwner(pid, timeoutMs) {
|
|
3385
|
+
if (!pid || pid <= 0) {
|
|
3386
|
+
throw new Error("Chrome did not expose a child process id.");
|
|
3387
|
+
}
|
|
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
|
+
);
|
|
3399
|
+
}
|
|
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;
|
|
3433
|
+
try {
|
|
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 {
|
|
3465
|
+
return null;
|
|
3466
|
+
}
|
|
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") {
|
|
3477
|
+
return null;
|
|
3478
|
+
}
|
|
3479
|
+
const parsed = value;
|
|
3480
|
+
const owner = parseProcessOwner(parsed.owner);
|
|
3481
|
+
if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
|
|
3482
|
+
return null;
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
clientId: parsed.clientId,
|
|
3486
|
+
createdAt: parsed.createdAt,
|
|
3487
|
+
owner
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
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
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
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";
|
|
3638
|
+
}
|
|
3639
|
+
await sleep5(100);
|
|
2051
3640
|
}
|
|
2052
|
-
|
|
3641
|
+
throw new Error(
|
|
3642
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
3643
|
+
);
|
|
2053
3644
|
}
|
|
2054
|
-
function
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
3645
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
3646
|
+
let parsed;
|
|
3647
|
+
try {
|
|
3648
|
+
parsed = new URL(cdpUrl);
|
|
3649
|
+
} catch {
|
|
3650
|
+
throw new Error(
|
|
3651
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
3652
|
+
);
|
|
2060
3653
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
remainder.slice(firstDashIndex + 1, secondDashIndex),
|
|
2064
|
-
10
|
|
2065
|
-
);
|
|
2066
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
2067
|
-
return null;
|
|
3654
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
3655
|
+
return parsed;
|
|
2068
3656
|
}
|
|
2069
|
-
if (
|
|
2070
|
-
|
|
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).`
|
|
3660
|
+
);
|
|
2071
3661
|
}
|
|
2072
|
-
|
|
3662
|
+
const normalized = new URL(parsed.toString());
|
|
3663
|
+
normalized.pathname = "/json/version";
|
|
3664
|
+
normalized.search = "";
|
|
3665
|
+
normalized.hash = "";
|
|
3666
|
+
return normalized;
|
|
2073
3667
|
}
|
|
2074
|
-
function
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
async function sleep5(ms) {
|
|
3691
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2082
3692
|
}
|
|
2083
3693
|
|
|
2084
3694
|
// src/browser/pool.ts
|
|
2085
3695
|
var BrowserPool = class {
|
|
2086
3696
|
browser = null;
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
managedUserDataDir = null;
|
|
2090
|
-
persistentProfile = false;
|
|
3697
|
+
activeSessionClose = null;
|
|
3698
|
+
closeInFlight = null;
|
|
2091
3699
|
defaults;
|
|
2092
3700
|
constructor(defaults = {}) {
|
|
2093
3701
|
this.defaults = defaults;
|
|
2094
3702
|
}
|
|
2095
3703
|
async launch(options = {}) {
|
|
2096
|
-
if (this.browser || this.
|
|
3704
|
+
if (this.browser || this.activeSessionClose) {
|
|
2097
3705
|
await this.close();
|
|
2098
3706
|
}
|
|
2099
3707
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -2140,30 +3748,26 @@ var BrowserPool = class {
|
|
|
2140
3748
|
return this.launchSandbox(options);
|
|
2141
3749
|
}
|
|
2142
3750
|
async close() {
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
const
|
|
2148
|
-
this.
|
|
2149
|
-
this.cdpProxy = null;
|
|
2150
|
-
this.launchedProcess = null;
|
|
2151
|
-
this.managedUserDataDir = null;
|
|
2152
|
-
this.persistentProfile = false;
|
|
3751
|
+
if (this.closeInFlight) {
|
|
3752
|
+
await this.closeInFlight;
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
const closeOperation = this.closeCurrent();
|
|
3756
|
+
this.closeInFlight = closeOperation;
|
|
2153
3757
|
try {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
3758
|
+
await closeOperation;
|
|
3759
|
+
this.browser = null;
|
|
3760
|
+
this.activeSessionClose = null;
|
|
2157
3761
|
} finally {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
3762
|
+
this.closeInFlight = null;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
async closeCurrent() {
|
|
3766
|
+
if (this.activeSessionClose) {
|
|
3767
|
+
await this.activeSessionClose();
|
|
3768
|
+
return;
|
|
2166
3769
|
}
|
|
3770
|
+
await this.browser?.close().catch(() => void 0);
|
|
2167
3771
|
}
|
|
2168
3772
|
async connectToRunning(cdpUrl, timeout) {
|
|
2169
3773
|
let browser = null;
|
|
@@ -2178,11 +3782,14 @@ var BrowserPool = class {
|
|
|
2178
3782
|
}
|
|
2179
3783
|
cdpProxy = new CDPProxy(browserWsUrl, targetId);
|
|
2180
3784
|
const proxyWsUrl = await cdpProxy.start();
|
|
2181
|
-
browser = await
|
|
3785
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
2182
3786
|
timeout: timeout ?? 3e4
|
|
2183
3787
|
});
|
|
2184
3788
|
this.browser = browser;
|
|
2185
|
-
this.
|
|
3789
|
+
this.activeSessionClose = async () => {
|
|
3790
|
+
await browser?.close().catch(() => void 0);
|
|
3791
|
+
cdpProxy?.close();
|
|
3792
|
+
};
|
|
2186
3793
|
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
2187
3794
|
return { browser, context, page, isExternal: true };
|
|
2188
3795
|
} catch (error) {
|
|
@@ -2191,7 +3798,7 @@ var BrowserPool = class {
|
|
|
2191
3798
|
}
|
|
2192
3799
|
cdpProxy?.close();
|
|
2193
3800
|
this.browser = null;
|
|
2194
|
-
this.
|
|
3801
|
+
this.activeSessionClose = null;
|
|
2195
3802
|
throw error;
|
|
2196
3803
|
}
|
|
2197
3804
|
}
|
|
@@ -2211,55 +3818,29 @@ var BrowserPool = class {
|
|
|
2211
3818
|
sourceUserDataDir,
|
|
2212
3819
|
profileDirectory
|
|
2213
3820
|
);
|
|
2214
|
-
await
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
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,
|
|
2223
3830
|
profileDirectory,
|
|
2224
|
-
|
|
2225
|
-
headless
|
|
2226
|
-
});
|
|
2227
|
-
const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
|
|
2228
|
-
detached: process.platform !== "win32",
|
|
2229
|
-
stdio: "ignore"
|
|
3831
|
+
timeoutMs: options.timeout ?? 3e4
|
|
2230
3832
|
});
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
timeout: options.timeout ?? 3e4
|
|
2240
|
-
});
|
|
2241
|
-
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
2242
|
-
browser
|
|
2243
|
-
);
|
|
2244
|
-
if (options.initialUrl) {
|
|
2245
|
-
await page.goto(options.initialUrl, {
|
|
2246
|
-
waitUntil: "domcontentloaded",
|
|
2247
|
-
timeout: options.timeout ?? 3e4
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
this.browser = browser;
|
|
2251
|
-
this.launchedProcess = processHandle;
|
|
2252
|
-
this.managedUserDataDir = persistentProfile.userDataDir;
|
|
2253
|
-
this.persistentProfile = true;
|
|
2254
|
-
return { browser, context, page, isExternal: false };
|
|
2255
|
-
} catch (error) {
|
|
2256
|
-
await browser?.close().catch(() => void 0);
|
|
2257
|
-
await killProcessTree(processHandle);
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
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
|
+
};
|
|
2260
3841
|
}
|
|
2261
3842
|
async launchSandbox(options) {
|
|
2262
|
-
const browser = await
|
|
3843
|
+
const browser = await import_playwright2.chromium.launch({
|
|
2263
3844
|
headless: resolveLaunchHeadless(
|
|
2264
3845
|
"chromium",
|
|
2265
3846
|
options.headless,
|
|
@@ -2272,11 +3853,14 @@ var BrowserPool = class {
|
|
|
2272
3853
|
const context = await browser.newContext(options.context || {});
|
|
2273
3854
|
const page = await context.newPage();
|
|
2274
3855
|
this.browser = browser;
|
|
3856
|
+
this.activeSessionClose = async () => {
|
|
3857
|
+
await browser.close().catch(() => void 0);
|
|
3858
|
+
};
|
|
2275
3859
|
return { browser, context, page, isExternal: false };
|
|
2276
3860
|
}
|
|
2277
3861
|
};
|
|
2278
3862
|
async function pickBrowserContextAndPage(browser) {
|
|
2279
|
-
const context =
|
|
3863
|
+
const context = getPrimaryBrowserContext2(browser);
|
|
2280
3864
|
const page = await getAttachedPageOrCreate(context);
|
|
2281
3865
|
return { context, page };
|
|
2282
3866
|
}
|
|
@@ -2289,11 +3873,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
|
2289
3873
|
}
|
|
2290
3874
|
return mode === "real";
|
|
2291
3875
|
}
|
|
2292
|
-
async function createOwnedBrowserContextAndPage(browser) {
|
|
2293
|
-
const context = getPrimaryBrowserContext(browser);
|
|
2294
|
-
const page = await getExistingPageOrCreate(context);
|
|
2295
|
-
return { context, page };
|
|
2296
|
-
}
|
|
2297
3876
|
async function getAttachedPageOrCreate(context) {
|
|
2298
3877
|
const pages = context.pages();
|
|
2299
3878
|
const inspectablePage = pages.find(
|
|
@@ -2308,14 +3887,7 @@ async function getAttachedPageOrCreate(context) {
|
|
|
2308
3887
|
}
|
|
2309
3888
|
return await context.newPage();
|
|
2310
3889
|
}
|
|
2311
|
-
|
|
2312
|
-
const existingPage = context.pages()[0];
|
|
2313
|
-
if (existingPage) {
|
|
2314
|
-
return existingPage;
|
|
2315
|
-
}
|
|
2316
|
-
return await context.newPage();
|
|
2317
|
-
}
|
|
2318
|
-
function getPrimaryBrowserContext(browser) {
|
|
3890
|
+
function getPrimaryBrowserContext2(browser) {
|
|
2319
3891
|
const contexts = browser.contexts();
|
|
2320
3892
|
if (contexts.length === 0) {
|
|
2321
3893
|
throw new Error(
|
|
@@ -2334,125 +3906,6 @@ function safePageUrl(page) {
|
|
|
2334
3906
|
return "";
|
|
2335
3907
|
}
|
|
2336
3908
|
}
|
|
2337
|
-
function normalizeDiscoveryUrl(cdpUrl) {
|
|
2338
|
-
let parsed;
|
|
2339
|
-
try {
|
|
2340
|
-
parsed = new URL(cdpUrl);
|
|
2341
|
-
} catch {
|
|
2342
|
-
throw new Error(
|
|
2343
|
-
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
2344
|
-
);
|
|
2345
|
-
}
|
|
2346
|
-
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
2347
|
-
return parsed;
|
|
2348
|
-
}
|
|
2349
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2350
|
-
throw new Error(
|
|
2351
|
-
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
2352
|
-
);
|
|
2353
|
-
}
|
|
2354
|
-
const normalized = new URL(parsed.toString());
|
|
2355
|
-
normalized.pathname = "/json/version";
|
|
2356
|
-
normalized.search = "";
|
|
2357
|
-
normalized.hash = "";
|
|
2358
|
-
return normalized;
|
|
2359
|
-
}
|
|
2360
|
-
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
2361
|
-
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
2362
|
-
return cdpUrl;
|
|
2363
|
-
}
|
|
2364
|
-
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
2365
|
-
const deadline = Date.now() + timeoutMs;
|
|
2366
|
-
let lastError = "CDP discovery did not respond.";
|
|
2367
|
-
while (Date.now() < deadline) {
|
|
2368
|
-
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
2369
|
-
try {
|
|
2370
|
-
const response = await fetch(versionUrl, {
|
|
2371
|
-
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
2372
|
-
});
|
|
2373
|
-
if (!response.ok) {
|
|
2374
|
-
lastError = `${response.status} ${response.statusText}`;
|
|
2375
|
-
} else {
|
|
2376
|
-
const payload = await response.json();
|
|
2377
|
-
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
2378
|
-
if (wsUrl && wsUrl.trim()) {
|
|
2379
|
-
return wsUrl;
|
|
2380
|
-
}
|
|
2381
|
-
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
2382
|
-
}
|
|
2383
|
-
} catch (error) {
|
|
2384
|
-
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2385
|
-
}
|
|
2386
|
-
await sleep(100);
|
|
2387
|
-
}
|
|
2388
|
-
throw new Error(
|
|
2389
|
-
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2390
|
-
);
|
|
2391
|
-
}
|
|
2392
|
-
async function reserveDebugPort() {
|
|
2393
|
-
return await new Promise((resolve, reject) => {
|
|
2394
|
-
const server = (0, import_node_net.createServer)();
|
|
2395
|
-
server.unref();
|
|
2396
|
-
server.on("error", reject);
|
|
2397
|
-
server.listen(0, "127.0.0.1", () => {
|
|
2398
|
-
const address = server.address();
|
|
2399
|
-
if (!address || typeof address === "string") {
|
|
2400
|
-
server.close();
|
|
2401
|
-
reject(new Error("Failed to reserve a local debug port."));
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
server.close((error) => {
|
|
2405
|
-
if (error) {
|
|
2406
|
-
reject(error);
|
|
2407
|
-
return;
|
|
2408
|
-
}
|
|
2409
|
-
resolve(address.port);
|
|
2410
|
-
});
|
|
2411
|
-
});
|
|
2412
|
-
});
|
|
2413
|
-
}
|
|
2414
|
-
function buildRealBrowserLaunchArgs(options) {
|
|
2415
|
-
const args = [
|
|
2416
|
-
`--user-data-dir=${options.userDataDir}`,
|
|
2417
|
-
`--profile-directory=${options.profileDirectory}`,
|
|
2418
|
-
`--remote-debugging-port=${options.debugPort}`,
|
|
2419
|
-
"--disable-blink-features=AutomationControlled"
|
|
2420
|
-
];
|
|
2421
|
-
if (options.headless) {
|
|
2422
|
-
args.push("--headless=new");
|
|
2423
|
-
}
|
|
2424
|
-
return args;
|
|
2425
|
-
}
|
|
2426
|
-
async function killProcessTree(processHandle) {
|
|
2427
|
-
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
2428
|
-
return;
|
|
2429
|
-
}
|
|
2430
|
-
if (process.platform === "win32") {
|
|
2431
|
-
await new Promise((resolve) => {
|
|
2432
|
-
const killer = (0, import_node_child_process.spawn)(
|
|
2433
|
-
"taskkill",
|
|
2434
|
-
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
2435
|
-
{
|
|
2436
|
-
stdio: "ignore"
|
|
2437
|
-
}
|
|
2438
|
-
);
|
|
2439
|
-
killer.on("error", () => resolve());
|
|
2440
|
-
killer.on("exit", () => resolve());
|
|
2441
|
-
});
|
|
2442
|
-
return;
|
|
2443
|
-
}
|
|
2444
|
-
try {
|
|
2445
|
-
process.kill(-processHandle.pid, "SIGKILL");
|
|
2446
|
-
} catch {
|
|
2447
|
-
try {
|
|
2448
|
-
processHandle.kill("SIGKILL");
|
|
2449
|
-
} catch {
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
async function sleep(ms) {
|
|
2454
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2455
|
-
}
|
|
2456
3909
|
|
|
2457
3910
|
// src/navigation.ts
|
|
2458
3911
|
var DEFAULT_TIMEOUT = 3e4;
|
|
@@ -2832,7 +4285,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2832
4285
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
2833
4286
|
Math.max(0, deadline - Date.now())
|
|
2834
4287
|
);
|
|
2835
|
-
await
|
|
4288
|
+
await sleep6(retryDelay);
|
|
2836
4289
|
}
|
|
2837
4290
|
}
|
|
2838
4291
|
}
|
|
@@ -2865,7 +4318,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2865
4318
|
() => ({ kind: "resolved" }),
|
|
2866
4319
|
(error) => ({ kind: "rejected", error })
|
|
2867
4320
|
);
|
|
2868
|
-
const timeoutPromise =
|
|
4321
|
+
const timeoutPromise = sleep6(
|
|
2869
4322
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
2870
4323
|
).then(() => ({ kind: "timeout" }));
|
|
2871
4324
|
const result = await Promise.race([
|
|
@@ -3007,7 +4460,7 @@ function isIgnorableFrameError(error) {
|
|
|
3007
4460
|
const message = error.message;
|
|
3008
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");
|
|
3009
4462
|
}
|
|
3010
|
-
function
|
|
4463
|
+
function sleep6(ms) {
|
|
3011
4464
|
return new Promise((resolve) => {
|
|
3012
4465
|
setTimeout(resolve, ms);
|
|
3013
4466
|
});
|
|
@@ -7258,7 +8711,7 @@ async function closeTab(context, activePage, index) {
|
|
|
7258
8711
|
}
|
|
7259
8712
|
|
|
7260
8713
|
// src/actions/cookies.ts
|
|
7261
|
-
var
|
|
8714
|
+
var import_promises6 = require("fs/promises");
|
|
7262
8715
|
async function getCookies(context, url) {
|
|
7263
8716
|
return context.cookies(url ? [url] : void 0);
|
|
7264
8717
|
}
|
|
@@ -7270,10 +8723,10 @@ async function clearCookies(context) {
|
|
|
7270
8723
|
}
|
|
7271
8724
|
async function exportCookies(context, filePath, url) {
|
|
7272
8725
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
7273
|
-
await (0,
|
|
8726
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
7274
8727
|
}
|
|
7275
8728
|
async function importCookies(context, filePath) {
|
|
7276
|
-
const raw = await (0,
|
|
8729
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
7277
8730
|
const cookies = JSON.parse(raw);
|
|
7278
8731
|
await context.addCookies(cookies);
|
|
7279
8732
|
}
|
|
@@ -7584,7 +9037,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7584
9037
|
this.idleSince = 0;
|
|
7585
9038
|
}
|
|
7586
9039
|
const remaining = Math.max(1, options.deadline - now);
|
|
7587
|
-
await
|
|
9040
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
7588
9041
|
}
|
|
7589
9042
|
}
|
|
7590
9043
|
handleRequestStarted = (request) => {
|
|
@@ -7629,7 +9082,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7629
9082
|
return false;
|
|
7630
9083
|
}
|
|
7631
9084
|
};
|
|
7632
|
-
async function
|
|
9085
|
+
async function sleep7(ms) {
|
|
7633
9086
|
await new Promise((resolve) => {
|
|
7634
9087
|
setTimeout(resolve, ms);
|
|
7635
9088
|
});
|
|
@@ -9331,13 +10784,13 @@ function dedupeNewest(entries) {
|
|
|
9331
10784
|
}
|
|
9332
10785
|
|
|
9333
10786
|
// src/cloud/cdp-client.ts
|
|
9334
|
-
var
|
|
10787
|
+
var import_playwright3 = require("playwright");
|
|
9335
10788
|
var CloudCdpClient = class {
|
|
9336
10789
|
async connect(args) {
|
|
9337
10790
|
const endpoint = withTokenQuery(args.wsUrl, args.token);
|
|
9338
10791
|
let browser;
|
|
9339
10792
|
try {
|
|
9340
|
-
browser = await
|
|
10793
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
9341
10794
|
} catch (error) {
|
|
9342
10795
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
9343
10796
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -11154,7 +12607,7 @@ async function executeAgentAction(page, action) {
|
|
|
11154
12607
|
}
|
|
11155
12608
|
case "wait": {
|
|
11156
12609
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
11157
|
-
await
|
|
12610
|
+
await sleep8(ms);
|
|
11158
12611
|
return;
|
|
11159
12612
|
}
|
|
11160
12613
|
case "goto": {
|
|
@@ -11319,7 +12772,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
11319
12772
|
}
|
|
11320
12773
|
}
|
|
11321
12774
|
}
|
|
11322
|
-
function
|
|
12775
|
+
function sleep8(ms) {
|
|
11323
12776
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11324
12777
|
}
|
|
11325
12778
|
|
|
@@ -11350,7 +12803,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11350
12803
|
if (isMutatingAgentAction(action)) {
|
|
11351
12804
|
this.onMutatingAction?.(action);
|
|
11352
12805
|
}
|
|
11353
|
-
await
|
|
12806
|
+
await sleep9(this.config.waitBetweenActionsMs);
|
|
11354
12807
|
});
|
|
11355
12808
|
try {
|
|
11356
12809
|
const result = await this.client.execute({
|
|
@@ -11412,7 +12865,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11412
12865
|
await this.cursorController.preview({ x, y }, "agent");
|
|
11413
12866
|
}
|
|
11414
12867
|
};
|
|
11415
|
-
function
|
|
12868
|
+
function sleep9(ms) {
|
|
11416
12869
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11417
12870
|
}
|
|
11418
12871
|
|
|
@@ -11848,7 +13301,7 @@ var CursorController = class {
|
|
|
11848
13301
|
for (const step of motion.points) {
|
|
11849
13302
|
await this.renderer.move(step, this.style);
|
|
11850
13303
|
if (motion.stepDelayMs > 0) {
|
|
11851
|
-
await
|
|
13304
|
+
await sleep10(motion.stepDelayMs);
|
|
11852
13305
|
}
|
|
11853
13306
|
}
|
|
11854
13307
|
if (shouldPulse(intent)) {
|
|
@@ -12006,7 +13459,7 @@ function clamp2(value, min, max) {
|
|
|
12006
13459
|
function shouldPulse(intent) {
|
|
12007
13460
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
12008
13461
|
}
|
|
12009
|
-
function
|
|
13462
|
+
function sleep10(ms) {
|
|
12010
13463
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12011
13464
|
}
|
|
12012
13465
|
|
|
@@ -12480,15 +13933,22 @@ var Opensteer = class _Opensteer {
|
|
|
12480
13933
|
}
|
|
12481
13934
|
return;
|
|
12482
13935
|
}
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
13936
|
+
let closedOwnedBrowser = false;
|
|
13937
|
+
try {
|
|
13938
|
+
if (this.ownsBrowser) {
|
|
13939
|
+
await this.pool.close();
|
|
13940
|
+
closedOwnedBrowser = true;
|
|
13941
|
+
}
|
|
13942
|
+
} finally {
|
|
13943
|
+
this.browser = null;
|
|
13944
|
+
this.pageRef = null;
|
|
13945
|
+
this.contextRef = null;
|
|
13946
|
+
if (!this.ownsBrowser || closedOwnedBrowser) {
|
|
13947
|
+
this.ownsBrowser = false;
|
|
13948
|
+
}
|
|
13949
|
+
if (this.cursorController) {
|
|
13950
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
13951
|
+
}
|
|
12492
13952
|
}
|
|
12493
13953
|
}
|
|
12494
13954
|
async syncLocalSelectorCacheToCloud() {
|
|
@@ -14816,19 +16276,19 @@ function buildLocalRunId(namespace) {
|
|
|
14816
16276
|
}
|
|
14817
16277
|
|
|
14818
16278
|
// src/browser/chromium-profile.ts
|
|
14819
|
-
var
|
|
14820
|
-
var
|
|
14821
|
-
var
|
|
14822
|
-
var
|
|
14823
|
-
var
|
|
14824
|
-
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");
|
|
14825
16285
|
var import_node_os2 = require("os");
|
|
14826
|
-
var
|
|
16286
|
+
var import_playwright4 = require("playwright");
|
|
14827
16287
|
|
|
14828
16288
|
// src/auth/keychain-store.ts
|
|
14829
|
-
var
|
|
16289
|
+
var import_node_child_process4 = require("child_process");
|
|
14830
16290
|
function commandExists(command) {
|
|
14831
|
-
const result = (0,
|
|
16291
|
+
const result = (0, import_node_child_process4.spawnSync)(command, ["--help"], {
|
|
14832
16292
|
encoding: "utf8",
|
|
14833
16293
|
stdio: "ignore"
|
|
14834
16294
|
});
|
|
@@ -14867,7 +16327,7 @@ function createMacosSecurityStore() {
|
|
|
14867
16327
|
return {
|
|
14868
16328
|
backend: "macos-security",
|
|
14869
16329
|
get(service, account) {
|
|
14870
|
-
const result = (0,
|
|
16330
|
+
const result = (0, import_node_child_process4.spawnSync)(
|
|
14871
16331
|
"security",
|
|
14872
16332
|
["find-generic-password", "-s", service, "-a", account, "-w"],
|
|
14873
16333
|
{ encoding: "utf8" }
|
|
@@ -14889,14 +16349,14 @@ function createMacosSecurityStore() {
|
|
|
14889
16349
|
"-w",
|
|
14890
16350
|
secret
|
|
14891
16351
|
];
|
|
14892
|
-
const result = (0,
|
|
16352
|
+
const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
|
|
14893
16353
|
if (commandFailed(result)) {
|
|
14894
16354
|
throw buildCommandError("security", args, result);
|
|
14895
16355
|
}
|
|
14896
16356
|
},
|
|
14897
16357
|
delete(service, account) {
|
|
14898
16358
|
const args = ["delete-generic-password", "-s", service, "-a", account];
|
|
14899
|
-
const result = (0,
|
|
16359
|
+
const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
|
|
14900
16360
|
if (commandFailed(result)) {
|
|
14901
16361
|
return;
|
|
14902
16362
|
}
|
|
@@ -14907,7 +16367,7 @@ function createLinuxSecretToolStore() {
|
|
|
14907
16367
|
return {
|
|
14908
16368
|
backend: "linux-secret-tool",
|
|
14909
16369
|
get(service, account) {
|
|
14910
|
-
const result = (0,
|
|
16370
|
+
const result = (0, import_node_child_process4.spawnSync)(
|
|
14911
16371
|
"secret-tool",
|
|
14912
16372
|
["lookup", "service", service, "account", account],
|
|
14913
16373
|
{
|
|
@@ -14930,7 +16390,7 @@ function createLinuxSecretToolStore() {
|
|
|
14930
16390
|
"account",
|
|
14931
16391
|
account
|
|
14932
16392
|
];
|
|
14933
|
-
const result = (0,
|
|
16393
|
+
const result = (0, import_node_child_process4.spawnSync)("secret-tool", args, {
|
|
14934
16394
|
encoding: "utf8",
|
|
14935
16395
|
input: secret
|
|
14936
16396
|
});
|
|
@@ -14940,7 +16400,7 @@ function createLinuxSecretToolStore() {
|
|
|
14940
16400
|
},
|
|
14941
16401
|
delete(service, account) {
|
|
14942
16402
|
const args = ["clear", "service", service, "account", account];
|
|
14943
|
-
(0,
|
|
16403
|
+
(0, import_node_child_process4.spawnSync)("secret-tool", args, {
|
|
14944
16404
|
encoding: "utf8"
|
|
14945
16405
|
});
|
|
14946
16406
|
}
|
|
@@ -14963,7 +16423,7 @@ function createKeychainStore() {
|
|
|
14963
16423
|
}
|
|
14964
16424
|
|
|
14965
16425
|
// src/browser/chromium-profile.ts
|
|
14966
|
-
var
|
|
16426
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
|
|
14967
16427
|
var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
|
|
14968
16428
|
var AES_BLOCK_BYTES = 16;
|
|
14969
16429
|
var MAC_KEY_ITERATIONS = 1003;
|
|
@@ -15022,20 +16482,20 @@ var CHROMIUM_BRANDS = [
|
|
|
15022
16482
|
];
|
|
15023
16483
|
function directoryExists(filePath) {
|
|
15024
16484
|
try {
|
|
15025
|
-
return (0,
|
|
16485
|
+
return (0, import_node_fs4.statSync)(filePath).isDirectory();
|
|
15026
16486
|
} catch {
|
|
15027
16487
|
return false;
|
|
15028
16488
|
}
|
|
15029
16489
|
}
|
|
15030
16490
|
function fileExists(filePath) {
|
|
15031
16491
|
try {
|
|
15032
|
-
return (0,
|
|
16492
|
+
return (0, import_node_fs4.statSync)(filePath).isFile();
|
|
15033
16493
|
} catch {
|
|
15034
16494
|
return false;
|
|
15035
16495
|
}
|
|
15036
16496
|
}
|
|
15037
16497
|
function resolveCookieDbPath(profileDir) {
|
|
15038
|
-
const candidates = [(0,
|
|
16498
|
+
const candidates = [(0, import_node_path6.join)(profileDir, "Network", "Cookies"), (0, import_node_path6.join)(profileDir, "Cookies")];
|
|
15039
16499
|
for (const candidate of candidates) {
|
|
15040
16500
|
if (fileExists(candidate)) {
|
|
15041
16501
|
return candidate;
|
|
@@ -15044,10 +16504,10 @@ function resolveCookieDbPath(profileDir) {
|
|
|
15044
16504
|
return null;
|
|
15045
16505
|
}
|
|
15046
16506
|
async function selectProfileDirFromUserDataDir(userDataDir) {
|
|
15047
|
-
const entries = await (0,
|
|
16507
|
+
const entries = await (0, import_promises7.readdir)(userDataDir, {
|
|
15048
16508
|
withFileTypes: true
|
|
15049
16509
|
}).catch(() => []);
|
|
15050
|
-
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));
|
|
15051
16511
|
return candidates;
|
|
15052
16512
|
}
|
|
15053
16513
|
async function resolveChromiumProfileLocation(inputPath) {
|
|
@@ -15055,16 +16515,16 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15055
16515
|
if (!expandedPath) {
|
|
15056
16516
|
throw new Error("Profile path cannot be empty.");
|
|
15057
16517
|
}
|
|
15058
|
-
if (fileExists(expandedPath) && (0,
|
|
15059
|
-
const directParent = (0,
|
|
15060
|
-
const profileDir = (0,
|
|
15061
|
-
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);
|
|
15062
16522
|
return {
|
|
15063
16523
|
userDataDir,
|
|
15064
16524
|
profileDir,
|
|
15065
|
-
profileDirectory: (0,
|
|
16525
|
+
profileDirectory: (0, import_node_path6.basename)(profileDir),
|
|
15066
16526
|
cookieDbPath: expandedPath,
|
|
15067
|
-
localStatePath: fileExists((0,
|
|
16527
|
+
localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
|
|
15068
16528
|
};
|
|
15069
16529
|
}
|
|
15070
16530
|
if (fileExists(expandedPath)) {
|
|
@@ -15079,16 +16539,16 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15079
16539
|
}
|
|
15080
16540
|
const directCookieDb = resolveCookieDbPath(expandedPath);
|
|
15081
16541
|
if (directCookieDb) {
|
|
15082
|
-
const userDataDir = (0,
|
|
16542
|
+
const userDataDir = (0, import_node_path6.dirname)(expandedPath);
|
|
15083
16543
|
return {
|
|
15084
16544
|
userDataDir,
|
|
15085
16545
|
profileDir: expandedPath,
|
|
15086
|
-
profileDirectory: (0,
|
|
16546
|
+
profileDirectory: (0, import_node_path6.basename)(expandedPath),
|
|
15087
16547
|
cookieDbPath: directCookieDb,
|
|
15088
|
-
localStatePath: fileExists((0,
|
|
16548
|
+
localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
|
|
15089
16549
|
};
|
|
15090
16550
|
}
|
|
15091
|
-
const localStatePath = (0,
|
|
16551
|
+
const localStatePath = (0, import_node_path6.join)(expandedPath, "Local State");
|
|
15092
16552
|
if (!fileExists(localStatePath)) {
|
|
15093
16553
|
throw new Error(
|
|
15094
16554
|
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
@@ -15101,7 +16561,7 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15101
16561
|
);
|
|
15102
16562
|
}
|
|
15103
16563
|
if (profileDirs.length > 1) {
|
|
15104
|
-
const candidates = profileDirs.map((entry) => (0,
|
|
16564
|
+
const candidates = profileDirs.map((entry) => (0, import_node_path6.basename)(entry)).join(", ");
|
|
15105
16565
|
throw new Error(
|
|
15106
16566
|
`"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
|
|
15107
16567
|
);
|
|
@@ -15116,7 +16576,7 @@ async function resolveChromiumProfileLocation(inputPath) {
|
|
|
15116
16576
|
return {
|
|
15117
16577
|
userDataDir: expandedPath,
|
|
15118
16578
|
profileDir: selectedProfileDir,
|
|
15119
|
-
profileDirectory: (0,
|
|
16579
|
+
profileDirectory: (0, import_node_path6.basename)(selectedProfileDir),
|
|
15120
16580
|
cookieDbPath,
|
|
15121
16581
|
localStatePath
|
|
15122
16582
|
};
|
|
@@ -15131,25 +16591,25 @@ function detectChromiumBrand(location) {
|
|
|
15131
16591
|
return DEFAULT_CHROMIUM_BRAND;
|
|
15132
16592
|
}
|
|
15133
16593
|
async function createSqliteSnapshot(dbPath) {
|
|
15134
|
-
const snapshotDir = await (0,
|
|
15135
|
-
const snapshotPath = (0,
|
|
15136
|
-
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);
|
|
15137
16597
|
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
15138
16598
|
const source = `${dbPath}${suffix}`;
|
|
15139
|
-
if (!(0,
|
|
16599
|
+
if (!(0, import_node_fs4.existsSync)(source)) {
|
|
15140
16600
|
continue;
|
|
15141
16601
|
}
|
|
15142
|
-
await (0,
|
|
16602
|
+
await (0, import_promises7.copyFile)(source, `${snapshotPath}${suffix}`);
|
|
15143
16603
|
}
|
|
15144
16604
|
return {
|
|
15145
16605
|
snapshotPath,
|
|
15146
16606
|
cleanup: async () => {
|
|
15147
|
-
await (0,
|
|
16607
|
+
await (0, import_promises7.rm)(snapshotDir, { recursive: true, force: true });
|
|
15148
16608
|
}
|
|
15149
16609
|
};
|
|
15150
16610
|
}
|
|
15151
16611
|
async function querySqliteJson(dbPath, query) {
|
|
15152
|
-
const result = await
|
|
16612
|
+
const result = await execFileAsync3("sqlite3", ["-json", dbPath, query], {
|
|
15153
16613
|
encoding: "utf8",
|
|
15154
16614
|
maxBuffer: 64 * 1024 * 1024
|
|
15155
16615
|
});
|
|
@@ -15190,7 +16650,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
|
|
|
15190
16650
|
if (buffer.length < 32) {
|
|
15191
16651
|
return buffer;
|
|
15192
16652
|
}
|
|
15193
|
-
const domainHash = (0,
|
|
16653
|
+
const domainHash = (0, import_node_crypto5.createHash)("sha256").update(hostKey, "utf8").digest();
|
|
15194
16654
|
if (buffer.subarray(0, 32).equals(domainHash)) {
|
|
15195
16655
|
return buffer.subarray(32);
|
|
15196
16656
|
}
|
|
@@ -15199,7 +16659,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
|
|
|
15199
16659
|
function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
|
|
15200
16660
|
const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
|
|
15201
16661
|
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
15202
|
-
const decipher = (0,
|
|
16662
|
+
const decipher = (0, import_node_crypto5.createDecipheriv)("aes-128-cbc", key, iv);
|
|
15203
16663
|
const plaintext = Buffer.concat([
|
|
15204
16664
|
decipher.update(ciphertext),
|
|
15205
16665
|
decipher.final()
|
|
@@ -15212,7 +16672,7 @@ function decryptChromiumAes256GcmValue(encryptedValue, key) {
|
|
|
15212
16672
|
const nonce = encryptedValue.subarray(3, 15);
|
|
15213
16673
|
const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
|
|
15214
16674
|
const authTag = encryptedValue.subarray(encryptedValue.length - 16);
|
|
15215
|
-
const decipher = (0,
|
|
16675
|
+
const decipher = (0, import_node_crypto5.createDecipheriv)("aes-256-gcm", key, nonce);
|
|
15216
16676
|
decipher.setAuthTag(authTag);
|
|
15217
16677
|
return Buffer.concat([
|
|
15218
16678
|
decipher.update(ciphertext),
|
|
@@ -15229,7 +16689,7 @@ async function dpapiUnprotect(buffer) {
|
|
|
15229
16689
|
")",
|
|
15230
16690
|
"[Convert]::ToBase64String($plainBytes)"
|
|
15231
16691
|
].join("\n");
|
|
15232
|
-
const { stdout } = await
|
|
16692
|
+
const { stdout } = await execFileAsync3(
|
|
15233
16693
|
"powershell.exe",
|
|
15234
16694
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
15235
16695
|
{
|
|
@@ -15249,7 +16709,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
15249
16709
|
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
15250
16710
|
);
|
|
15251
16711
|
}
|
|
15252
|
-
const key = (0,
|
|
16712
|
+
const key = (0, import_node_crypto5.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
|
|
15253
16713
|
return async (row) => decryptChromiumAes128CbcValue(
|
|
15254
16714
|
Buffer.from(row.encrypted_value || "", "hex"),
|
|
15255
16715
|
key,
|
|
@@ -15260,7 +16720,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
15260
16720
|
const brand = detectChromiumBrand(location);
|
|
15261
16721
|
const keychainStore = createKeychainStore();
|
|
15262
16722
|
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
|
|
15263
|
-
const key = (0,
|
|
16723
|
+
const key = (0, import_node_crypto5.pbkdf2Sync)(
|
|
15264
16724
|
password || "peanuts",
|
|
15265
16725
|
KEY_SALT,
|
|
15266
16726
|
LINUX_KEY_ITERATIONS,
|
|
@@ -15280,7 +16740,7 @@ async function buildChromiumDecryptor(location) {
|
|
|
15280
16740
|
);
|
|
15281
16741
|
}
|
|
15282
16742
|
const localState = JSON.parse(
|
|
15283
|
-
await (0,
|
|
16743
|
+
await (0, import_promises7.readFile)(location.localStatePath, "utf8")
|
|
15284
16744
|
);
|
|
15285
16745
|
const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
|
|
15286
16746
|
if (!encryptedKeyBase64) {
|
|
@@ -15374,22 +16834,22 @@ async function loadCookiesFromSqlite(location) {
|
|
|
15374
16834
|
}
|
|
15375
16835
|
}
|
|
15376
16836
|
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
15377
|
-
const snapshotRootDir = await (0,
|
|
15378
|
-
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)(
|
|
15379
16839
|
snapshotRootDir,
|
|
15380
|
-
(0,
|
|
16840
|
+
(0, import_node_path6.basename)(location.profileDir)
|
|
15381
16841
|
);
|
|
15382
16842
|
let context = null;
|
|
15383
16843
|
try {
|
|
15384
|
-
await (0,
|
|
16844
|
+
await (0, import_promises7.cp)(location.profileDir, snapshotProfileDir, {
|
|
15385
16845
|
recursive: true
|
|
15386
16846
|
});
|
|
15387
16847
|
if (location.localStatePath) {
|
|
15388
|
-
await (0,
|
|
16848
|
+
await (0, import_promises7.copyFile)(location.localStatePath, (0, import_node_path6.join)(snapshotRootDir, "Local State"));
|
|
15389
16849
|
}
|
|
15390
16850
|
const brand = detectChromiumBrand(location);
|
|
15391
|
-
const args = [`--profile-directory=${(0,
|
|
15392
|
-
context = await
|
|
16851
|
+
const args = [`--profile-directory=${(0, import_node_path6.basename)(snapshotProfileDir)}`];
|
|
16852
|
+
context = await import_playwright4.chromium.launchPersistentContext(snapshotRootDir, {
|
|
15393
16853
|
channel: brand.playwrightChannel,
|
|
15394
16854
|
headless: options.headless ?? true,
|
|
15395
16855
|
timeout: options.timeout ?? 12e4,
|
|
@@ -15398,7 +16858,7 @@ async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
|
15398
16858
|
return await context.cookies();
|
|
15399
16859
|
} finally {
|
|
15400
16860
|
await context?.close().catch(() => void 0);
|
|
15401
|
-
await (0,
|
|
16861
|
+
await (0, import_promises7.rm)(snapshotRootDir, { recursive: true, force: true });
|
|
15402
16862
|
}
|
|
15403
16863
|
}
|
|
15404
16864
|
function isMissingSqliteBinary(error) {
|
|
@@ -15641,10 +17101,10 @@ function toResolvedCloudCredential(source, credential) {
|
|
|
15641
17101
|
}
|
|
15642
17102
|
|
|
15643
17103
|
// src/auth/machine-credential-store.ts
|
|
15644
|
-
var
|
|
15645
|
-
var
|
|
17104
|
+
var import_node_crypto6 = require("crypto");
|
|
17105
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
15646
17106
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
15647
|
-
var
|
|
17107
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
15648
17108
|
var METADATA_VERSION = 2;
|
|
15649
17109
|
var ACTIVE_TARGET_VERSION = 2;
|
|
15650
17110
|
var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
|
|
@@ -15659,7 +17119,7 @@ var MachineCredentialStore = class {
|
|
|
15659
17119
|
const appName = options.appName || "opensteer";
|
|
15660
17120
|
const env = options.env ?? process.env;
|
|
15661
17121
|
const configDir = resolveConfigDir(appName, env);
|
|
15662
|
-
this.authDir =
|
|
17122
|
+
this.authDir = import_node_path7.default.join(configDir, "auth");
|
|
15663
17123
|
this.warn = options.warn ?? (() => void 0);
|
|
15664
17124
|
}
|
|
15665
17125
|
readCloudCredential(target) {
|
|
@@ -15789,18 +17249,18 @@ function createMachineCredentialStore(options = {}) {
|
|
|
15789
17249
|
}
|
|
15790
17250
|
function resolveCredentialSlot(authDir, target) {
|
|
15791
17251
|
const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
|
|
15792
|
-
const storageKey = (0,
|
|
17252
|
+
const storageKey = (0, import_node_crypto6.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
|
|
15793
17253
|
return {
|
|
15794
17254
|
keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
|
|
15795
|
-
metadataPath:
|
|
15796
|
-
fallbackSecretPath:
|
|
17255
|
+
metadataPath: import_node_path7.default.join(authDir, `cli-login.${storageKey}.json`),
|
|
17256
|
+
fallbackSecretPath: import_node_path7.default.join(
|
|
15797
17257
|
authDir,
|
|
15798
17258
|
`cli-login.${storageKey}.secret.json`
|
|
15799
17259
|
)
|
|
15800
17260
|
};
|
|
15801
17261
|
}
|
|
15802
17262
|
function resolveActiveTargetPath(authDir) {
|
|
15803
|
-
return
|
|
17263
|
+
return import_node_path7.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
|
|
15804
17264
|
}
|
|
15805
17265
|
function matchesCredentialTarget(value, target) {
|
|
15806
17266
|
return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
|
|
@@ -15819,36 +17279,36 @@ function normalizeCloudCredentialTarget(target) {
|
|
|
15819
17279
|
}
|
|
15820
17280
|
function resolveConfigDir(appName, env) {
|
|
15821
17281
|
if (process.platform === "win32") {
|
|
15822
|
-
const appData = env.APPDATA?.trim() ||
|
|
15823
|
-
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);
|
|
15824
17284
|
}
|
|
15825
17285
|
if (process.platform === "darwin") {
|
|
15826
|
-
return
|
|
17286
|
+
return import_node_path7.default.join(
|
|
15827
17287
|
import_node_os3.default.homedir(),
|
|
15828
17288
|
"Library",
|
|
15829
17289
|
"Application Support",
|
|
15830
17290
|
appName
|
|
15831
17291
|
);
|
|
15832
17292
|
}
|
|
15833
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() ||
|
|
15834
|
-
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);
|
|
15835
17295
|
}
|
|
15836
17296
|
function ensureDirectory(directoryPath) {
|
|
15837
|
-
|
|
17297
|
+
import_node_fs5.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
15838
17298
|
}
|
|
15839
17299
|
function removeFileIfExists(filePath) {
|
|
15840
17300
|
try {
|
|
15841
|
-
|
|
17301
|
+
import_node_fs5.default.rmSync(filePath, { force: true });
|
|
15842
17302
|
} catch {
|
|
15843
17303
|
return;
|
|
15844
17304
|
}
|
|
15845
17305
|
}
|
|
15846
17306
|
function readMetadata(filePath) {
|
|
15847
|
-
if (!
|
|
17307
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
15848
17308
|
return null;
|
|
15849
17309
|
}
|
|
15850
17310
|
try {
|
|
15851
|
-
const raw =
|
|
17311
|
+
const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
|
|
15852
17312
|
const parsed = JSON.parse(raw);
|
|
15853
17313
|
if (parsed.version !== METADATA_VERSION) return null;
|
|
15854
17314
|
if (parsed.secretBackend !== "keychain" && parsed.secretBackend !== "file") {
|
|
@@ -15875,11 +17335,11 @@ function readMetadata(filePath) {
|
|
|
15875
17335
|
}
|
|
15876
17336
|
}
|
|
15877
17337
|
function readActiveCloudTargetMetadata(filePath) {
|
|
15878
|
-
if (!
|
|
17338
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
15879
17339
|
return null;
|
|
15880
17340
|
}
|
|
15881
17341
|
try {
|
|
15882
|
-
const raw =
|
|
17342
|
+
const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
|
|
15883
17343
|
const parsed = JSON.parse(raw);
|
|
15884
17344
|
if (parsed.version !== ACTIVE_TARGET_VERSION) {
|
|
15885
17345
|
return null;
|
|
@@ -15909,22 +17369,22 @@ function parseSecretPayload(raw) {
|
|
|
15909
17369
|
}
|
|
15910
17370
|
}
|
|
15911
17371
|
function readSecretFile(filePath) {
|
|
15912
|
-
if (!
|
|
17372
|
+
if (!import_node_fs5.default.existsSync(filePath)) {
|
|
15913
17373
|
return null;
|
|
15914
17374
|
}
|
|
15915
17375
|
try {
|
|
15916
|
-
return parseSecretPayload(
|
|
17376
|
+
return parseSecretPayload(import_node_fs5.default.readFileSync(filePath, "utf8"));
|
|
15917
17377
|
} catch {
|
|
15918
17378
|
return null;
|
|
15919
17379
|
}
|
|
15920
17380
|
}
|
|
15921
17381
|
function writeJsonFile(filePath, value, options = {}) {
|
|
15922
|
-
|
|
17382
|
+
import_node_fs5.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
|
|
15923
17383
|
encoding: "utf8",
|
|
15924
17384
|
mode: options.mode ?? 384
|
|
15925
17385
|
});
|
|
15926
17386
|
if (typeof options.mode === "number") {
|
|
15927
|
-
|
|
17387
|
+
import_node_fs5.default.chmodSync(filePath, options.mode);
|
|
15928
17388
|
}
|
|
15929
17389
|
}
|
|
15930
17390
|
|
|
@@ -16258,7 +17718,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
16258
17718
|
const writeProgress = options.writeProgress ?? options.writeStdout ?? ((message) => process.stdout.write(message));
|
|
16259
17719
|
const writeStderr = options.writeStderr ?? ((message) => process.stderr.write(message));
|
|
16260
17720
|
const fetchFn = options.fetchFn ?? fetch;
|
|
16261
|
-
const
|
|
17721
|
+
const sleep11 = options.sleep ?? (async (ms) => {
|
|
16262
17722
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
16263
17723
|
});
|
|
16264
17724
|
const now = options.now ?? Date.now;
|
|
@@ -16302,7 +17762,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
16302
17762
|
fetchFn,
|
|
16303
17763
|
writeProgress,
|
|
16304
17764
|
openExternalUrl,
|
|
16305
|
-
sleep:
|
|
17765
|
+
sleep: sleep11,
|
|
16306
17766
|
now,
|
|
16307
17767
|
openBrowser: true
|
|
16308
17768
|
});
|
|
@@ -16714,7 +18174,7 @@ function createDefaultDeps() {
|
|
|
16714
18174
|
loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
|
|
16715
18175
|
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
16716
18176
|
confirm: async (message) => {
|
|
16717
|
-
const rl = (0,
|
|
18177
|
+
const rl = (0, import_promises8.createInterface)({
|
|
16718
18178
|
input: process.stdin,
|
|
16719
18179
|
output: process.stderr
|
|
16720
18180
|
});
|
|
@@ -16878,7 +18338,7 @@ async function resolveTargetProfileId(args, deps, client) {
|
|
|
16878
18338
|
"Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
|
|
16879
18339
|
);
|
|
16880
18340
|
}
|
|
16881
|
-
const defaultName = `Synced ${
|
|
18341
|
+
const defaultName = `Synced ${import_node_path8.default.basename(args.fromProfileDir)}`;
|
|
16882
18342
|
const shouldCreate = await deps.confirm(
|
|
16883
18343
|
`No destination profile provided. Create a new cloud profile named "${defaultName}"?`
|
|
16884
18344
|
);
|