opensteer 0.6.5 → 0.6.7

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.
@@ -425,7 +425,7 @@ __export(profile_exports, {
425
425
  });
426
426
  module.exports = __toCommonJS(profile_exports);
427
427
  var import_node_path4 = __toESM(require("path"), 1);
428
- var import_promises4 = require("readline/promises");
428
+ var import_promises5 = require("readline/promises");
429
429
 
430
430
  // src/config.ts
431
431
  var import_fs = __toESM(require("fs"), 1);
@@ -1324,11 +1324,8 @@ var import_crypto = require("crypto");
1324
1324
 
1325
1325
  // src/browser/pool.ts
1326
1326
  var import_node_child_process = require("child_process");
1327
- var import_node_fs = require("fs");
1328
- var import_promises = require("fs/promises");
1327
+ var import_promises2 = require("fs/promises");
1329
1328
  var import_node_net = require("net");
1330
- var import_node_os = require("os");
1331
- var import_node_path = require("path");
1332
1329
  var import_playwright = require("playwright");
1333
1330
 
1334
1331
  // src/browser/cdp-proxy.ts
@@ -1336,6 +1333,8 @@ var import_ws = __toESM(require("ws"), 1);
1336
1333
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
1337
1334
  var LOCAL_PROXY_HOST = "127.0.0.1";
1338
1335
  var INTERNAL_COMMAND_ID_START = 1e9;
1336
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
1337
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
1339
1338
  async function discoverTargets(cdpUrl) {
1340
1339
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
1341
1340
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -1347,6 +1346,81 @@ async function discoverTargets(cdpUrl) {
1347
1346
  targets: readPageTargets(targetsPayload)
1348
1347
  };
1349
1348
  }
1349
+ function createBlankTarget(browserWsUrl) {
1350
+ return new Promise((resolve, reject) => {
1351
+ const ws = new import_ws.default(browserWsUrl);
1352
+ let settled = false;
1353
+ function settle(handler) {
1354
+ if (settled) {
1355
+ return;
1356
+ }
1357
+ settled = true;
1358
+ clearTimeout(timeout);
1359
+ ws.close();
1360
+ handler();
1361
+ }
1362
+ const timeout = setTimeout(() => {
1363
+ settle(
1364
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
1365
+ );
1366
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
1367
+ ws.once("open", () => {
1368
+ ws.send(
1369
+ JSON.stringify({
1370
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
1371
+ method: "Target.createTarget",
1372
+ params: { url: "about:blank" }
1373
+ })
1374
+ );
1375
+ });
1376
+ ws.on("message", (data, isBinary) => {
1377
+ if (isBinary) {
1378
+ return;
1379
+ }
1380
+ const message = parseMessage(data);
1381
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
1382
+ return;
1383
+ }
1384
+ const cdpError = asObject(message.error);
1385
+ if (cdpError) {
1386
+ settle(
1387
+ () => reject(
1388
+ new Error(
1389
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
1390
+ )
1391
+ )
1392
+ );
1393
+ return;
1394
+ }
1395
+ const targetId = asString(asObject(message.result)?.targetId);
1396
+ if (targetId) {
1397
+ settle(() => resolve(targetId));
1398
+ return;
1399
+ }
1400
+ settle(
1401
+ () => reject(
1402
+ new Error(
1403
+ "Target.createTarget succeeded but no targetId was returned."
1404
+ )
1405
+ )
1406
+ );
1407
+ });
1408
+ ws.once("error", (err) => {
1409
+ settle(
1410
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
1411
+ );
1412
+ });
1413
+ ws.once("close", () => {
1414
+ settle(
1415
+ () => reject(
1416
+ new Error(
1417
+ "CDP browser websocket closed before blank tab creation completed."
1418
+ )
1419
+ )
1420
+ );
1421
+ });
1422
+ });
1423
+ }
1350
1424
  var CDPProxy = class {
1351
1425
  constructor(browserWsUrl, targetId) {
1352
1426
  this.browserWsUrl = browserWsUrl;
@@ -1744,18 +1818,282 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
1744
1818
  }
1745
1819
  }
1746
1820
 
1821
+ // src/browser/persistent-profile.ts
1822
+ var import_node_crypto = require("crypto");
1823
+ var import_node_fs = require("fs");
1824
+ var import_promises = require("fs/promises");
1825
+ var import_node_os = require("os");
1826
+ var import_node_path = require("path");
1827
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
1828
+ var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1829
+ var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1830
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1831
+ "SingletonCookie",
1832
+ "SingletonLock",
1833
+ "SingletonSocket",
1834
+ "DevToolsActivePort",
1835
+ "lockfile"
1836
+ ]);
1837
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1838
+ ...CHROME_SINGLETON_ENTRIES,
1839
+ OPENSTEER_META_FILE
1840
+ ]);
1841
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1842
+ "Crash Reports",
1843
+ "Crashpad",
1844
+ "BrowserMetrics",
1845
+ "GrShaderCache",
1846
+ "ShaderCache",
1847
+ "GraphiteDawnCache",
1848
+ "component_crx_cache",
1849
+ "Crowd Deny",
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
+ );
1878
+ }
1879
+ const created = await createPersistentProfileClone(
1880
+ resolvedSourceUserDataDir,
1881
+ sourceProfileDir,
1882
+ targetUserDataDir,
1883
+ profileDirectory,
1884
+ metadata
1885
+ );
1886
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1887
+ return {
1888
+ created,
1889
+ userDataDir: targetUserDataDir
1890
+ };
1891
+ }
1892
+ async function clearPersistentProfileSingletons(userDataDir) {
1893
+ await Promise.all(
1894
+ [...CHROME_SINGLETON_ENTRIES].map(
1895
+ (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
1896
+ force: true,
1897
+ recursive: true
1898
+ }).catch(() => void 0)
1899
+ )
1900
+ );
1901
+ }
1902
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1903
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1904
+ const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1905
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1906
+ return `${sourceLabel}-${profileLabel}-${hash}`;
1907
+ }
1908
+ function defaultPersistentProfilesRootDir() {
1909
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1910
+ }
1911
+ function sanitizePathSegment(value) {
1912
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1913
+ return sanitized.replace(/^-|-$/g, "") || "profile";
1914
+ }
1915
+ function isProfileDirectory(userDataDir, entry) {
1916
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1917
+ }
1918
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1919
+ let entries;
1920
+ try {
1921
+ entries = await (0, import_promises.readdir)(sourceUserDataDir);
1922
+ } catch {
1923
+ return;
1924
+ }
1925
+ const copyTasks = [];
1926
+ for (const entry of entries) {
1927
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
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
+ }
1949
+ }
1950
+ await Promise.all(copyTasks);
1951
+ }
1952
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
1953
+ await (0, import_promises.writeFile)(
1954
+ (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1955
+ JSON.stringify(metadata, null, 2)
1956
+ );
1957
+ }
1958
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1959
+ return {
1960
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1961
+ profileDirectory,
1962
+ source: sourceUserDataDir
1963
+ };
1964
+ }
1965
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1966
+ if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1967
+ return false;
1968
+ }
1969
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1970
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
1971
+ );
1972
+ let published = false;
1973
+ try {
1974
+ await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1975
+ recursive: true
1976
+ });
1977
+ await copyRootLevelEntries(
1978
+ sourceUserDataDir,
1979
+ tempUserDataDir,
1980
+ profileDirectory
1981
+ );
1982
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
1983
+ try {
1984
+ await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1985
+ } catch (error) {
1986
+ if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1987
+ return false;
1988
+ }
1989
+ throw error;
1990
+ }
1991
+ published = true;
1992
+ return true;
1993
+ } finally {
1994
+ if (!published) {
1995
+ await (0, import_promises.rm)(tempUserDataDir, {
1996
+ recursive: true,
1997
+ force: true
1998
+ }).catch(() => void 0);
1999
+ }
2000
+ }
2001
+ }
2002
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
2003
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
2004
+ return;
2005
+ }
2006
+ await writePersistentProfileMetadata(userDataDir, metadata);
2007
+ }
2008
+ function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
2009
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2010
+ return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2011
+ }
2012
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
2013
+ return (0, import_node_path.join)(
2014
+ (0, import_node_path.dirname)(targetUserDataDir),
2015
+ `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
2016
+ );
2017
+ }
2018
+ async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
2019
+ let entries;
2020
+ try {
2021
+ entries = await (0, import_promises.readdir)(profilesDir, {
2022
+ encoding: "utf8",
2023
+ withFileTypes: true
2024
+ });
2025
+ } catch {
2026
+ return;
2027
+ }
2028
+ const tempDirPrefix = `${targetBaseName}-tmp-`;
2029
+ await Promise.all(
2030
+ entries.map(async (entry) => {
2031
+ if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
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
+ })
2042
+ );
2043
+ }
2044
+ function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
2045
+ const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
2046
+ if (!owner) {
2047
+ return false;
2048
+ }
2049
+ if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
2050
+ return true;
2051
+ }
2052
+ return isProcessRunning(owner.pid);
2053
+ }
2054
+ function parseTempDirOwner(tempDirName, tempDirPrefix) {
2055
+ const remainder = tempDirName.slice(tempDirPrefix.length);
2056
+ const firstDashIndex = remainder.indexOf("-");
2057
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
2058
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
2059
+ return null;
2060
+ }
2061
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
2062
+ const processStartedAtMs = Number.parseInt(
2063
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
2064
+ 10
2065
+ );
2066
+ if (!Number.isInteger(pid) || pid <= 0) {
2067
+ return null;
2068
+ }
2069
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
2070
+ return null;
2071
+ }
2072
+ return { pid, processStartedAtMs };
2073
+ }
2074
+ function isProcessRunning(pid) {
2075
+ try {
2076
+ process.kill(pid, 0);
2077
+ return true;
2078
+ } catch (error) {
2079
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2080
+ return code !== "ESRCH";
2081
+ }
2082
+ }
2083
+
1747
2084
  // src/browser/pool.ts
1748
2085
  var BrowserPool = class {
1749
2086
  browser = null;
1750
2087
  cdpProxy = null;
1751
2088
  launchedProcess = null;
1752
- tempUserDataDir = null;
2089
+ managedUserDataDir = null;
2090
+ persistentProfile = false;
1753
2091
  defaults;
1754
2092
  constructor(defaults = {}) {
1755
2093
  this.defaults = defaults;
1756
2094
  }
1757
2095
  async launch(options = {}) {
1758
- if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
2096
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
1759
2097
  await this.close();
1760
2098
  }
1761
2099
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -1805,11 +2143,13 @@ var BrowserPool = class {
1805
2143
  const browser = this.browser;
1806
2144
  const cdpProxy = this.cdpProxy;
1807
2145
  const launchedProcess = this.launchedProcess;
1808
- const tempUserDataDir = this.tempUserDataDir;
2146
+ const managedUserDataDir = this.managedUserDataDir;
2147
+ const persistentProfile = this.persistentProfile;
1809
2148
  this.browser = null;
1810
2149
  this.cdpProxy = null;
1811
2150
  this.launchedProcess = null;
1812
- this.tempUserDataDir = null;
2151
+ this.managedUserDataDir = null;
2152
+ this.persistentProfile = false;
1813
2153
  try {
1814
2154
  if (browser) {
1815
2155
  await browser.close().catch(() => void 0);
@@ -1817,8 +2157,8 @@ var BrowserPool = class {
1817
2157
  } finally {
1818
2158
  cdpProxy?.close();
1819
2159
  await killProcessTree(launchedProcess);
1820
- if (tempUserDataDir) {
1821
- await (0, import_promises.rm)(tempUserDataDir, {
2160
+ if (managedUserDataDir && !persistentProfile) {
2161
+ await (0, import_promises2.rm)(managedUserDataDir, {
1822
2162
  recursive: true,
1823
2163
  force: true
1824
2164
  }).catch(() => void 0);
@@ -1830,12 +2170,13 @@ var BrowserPool = class {
1830
2170
  let cdpProxy = null;
1831
2171
  try {
1832
2172
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
1833
- if (targets.length === 0) {
1834
- throw new Error(
1835
- "No page targets found. Is the browser running with an open window?"
1836
- );
2173
+ let targetId;
2174
+ if (targets.length > 0) {
2175
+ targetId = targets[0].id;
2176
+ } else {
2177
+ targetId = await createBlankTarget(browserWsUrl);
1837
2178
  }
1838
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
2179
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
1839
2180
  const proxyWsUrl = await cdpProxy.start();
1840
2181
  browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
1841
2182
  timeout: timeout ?? 3e4
@@ -1866,10 +2207,11 @@ var BrowserPool = class {
1866
2207
  options.userDataDir ?? chromePaths.defaultUserDataDir
1867
2208
  );
1868
2209
  const profileDirectory = options.profileDirectory ?? "Default";
1869
- const tempUserDataDir = await cloneProfileToTempDir(
2210
+ const persistentProfile = await getOrCreatePersistentProfile(
1870
2211
  sourceUserDataDir,
1871
2212
  profileDirectory
1872
2213
  );
2214
+ await clearPersistentProfileSingletons(persistentProfile.userDataDir);
1873
2215
  const debugPort = await reserveDebugPort();
1874
2216
  const headless = resolveLaunchHeadless(
1875
2217
  "real",
@@ -1877,7 +2219,7 @@ var BrowserPool = class {
1877
2219
  this.defaults.headless
1878
2220
  );
1879
2221
  const launchArgs = buildRealBrowserLaunchArgs({
1880
- userDataDir: tempUserDataDir,
2222
+ userDataDir: persistentProfile.userDataDir,
1881
2223
  profileDirectory,
1882
2224
  debugPort,
1883
2225
  headless
@@ -1897,24 +2239,22 @@ var BrowserPool = class {
1897
2239
  timeout: options.timeout ?? 3e4
1898
2240
  });
1899
2241
  const { context, page } = await createOwnedBrowserContextAndPage(
1900
- browser,
1901
- {
1902
- headless,
1903
- initialUrl: options.initialUrl,
1904
- timeoutMs: options.timeout ?? 3e4
1905
- }
2242
+ browser
1906
2243
  );
2244
+ if (options.initialUrl) {
2245
+ await page.goto(options.initialUrl, {
2246
+ waitUntil: "domcontentloaded",
2247
+ timeout: options.timeout ?? 3e4
2248
+ });
2249
+ }
1907
2250
  this.browser = browser;
1908
2251
  this.launchedProcess = processHandle;
1909
- this.tempUserDataDir = tempUserDataDir;
2252
+ this.managedUserDataDir = persistentProfile.userDataDir;
2253
+ this.persistentProfile = true;
1910
2254
  return { browser, context, page, isExternal: false };
1911
2255
  } catch (error) {
1912
2256
  await browser?.close().catch(() => void 0);
1913
2257
  await killProcessTree(processHandle);
1914
- await (0, import_promises.rm)(tempUserDataDir, {
1915
- recursive: true,
1916
- force: true
1917
- }).catch(() => void 0);
1918
2258
  throw error;
1919
2259
  }
1920
2260
  }
@@ -1937,8 +2277,7 @@ var BrowserPool = class {
1937
2277
  };
1938
2278
  async function pickBrowserContextAndPage(browser) {
1939
2279
  const context = getPrimaryBrowserContext(browser);
1940
- const pages = context.pages();
1941
- const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
2280
+ const page = await getAttachedPageOrCreate(context);
1942
2281
  return { context, page };
1943
2282
  }
1944
2283
  function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
@@ -1950,77 +2289,31 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1950
2289
  }
1951
2290
  return mode === "real";
1952
2291
  }
1953
- async function createOwnedBrowserContextAndPage(browser, options) {
2292
+ async function createOwnedBrowserContextAndPage(browser) {
1954
2293
  const context = getPrimaryBrowserContext(browser);
1955
- const page = await createOwnedBrowserPage(browser, context, options);
2294
+ const page = await getExistingPageOrCreate(context);
1956
2295
  return { context, page };
1957
2296
  }
1958
- async function createOwnedBrowserPage(browser, context, options) {
1959
- const targetUrl = options.initialUrl ?? "about:blank";
1960
- const existingPages = new Set(context.pages());
1961
- const browserSession = await browser.newBrowserCDPSession();
1962
- try {
1963
- const { targetId } = await browserSession.send("Target.createTarget", {
1964
- url: targetUrl,
1965
- newWindow: !options.headless
1966
- });
1967
- await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
1968
- const page = await waitForOwnedBrowserPage(context, {
1969
- existingPages,
1970
- targetUrl,
1971
- timeoutMs: options.timeoutMs
1972
- });
1973
- if (targetUrl !== "about:blank") {
1974
- await page.waitForLoadState("domcontentloaded", {
1975
- timeout: options.timeoutMs
1976
- });
1977
- }
1978
- await closeDisposableStartupTargets(browserSession, targetId);
1979
- return page;
1980
- } finally {
1981
- await browserSession.detach().catch(() => void 0);
1982
- }
1983
- }
1984
- async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
1985
- const response = await browserSession.send("Target.getTargets").catch(() => null);
1986
- if (!response) {
1987
- return;
2297
+ async function getAttachedPageOrCreate(context) {
2298
+ const pages = context.pages();
2299
+ const inspectablePage = pages.find(
2300
+ (candidate) => isInspectablePageUrl2(safePageUrl(candidate))
2301
+ );
2302
+ if (inspectablePage) {
2303
+ return inspectablePage;
1988
2304
  }
1989
- for (const targetInfo of response.targetInfos) {
1990
- if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
1991
- continue;
1992
- }
1993
- await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
2305
+ const attachedPage = pages[0];
2306
+ if (attachedPage) {
2307
+ return attachedPage;
1994
2308
  }
2309
+ return await context.newPage();
1995
2310
  }
1996
- async function waitForOwnedBrowserPage(context, options) {
1997
- const deadline = Date.now() + options.timeoutMs;
1998
- let fallbackPage = null;
1999
- while (Date.now() < deadline) {
2000
- for (const candidate of context.pages()) {
2001
- if (options.existingPages.has(candidate)) {
2002
- continue;
2003
- }
2004
- const url = candidate.url();
2005
- if (!isInspectablePageUrl2(url)) {
2006
- continue;
2007
- }
2008
- fallbackPage ??= candidate;
2009
- if (options.targetUrl === "about:blank") {
2010
- return candidate;
2011
- }
2012
- if (pageLooselyMatchesUrl(url, options.targetUrl)) {
2013
- return candidate;
2014
- }
2015
- }
2016
- await sleep(100);
2017
- }
2018
- if (fallbackPage) {
2019
- return fallbackPage;
2311
+ async function getExistingPageOrCreate(context) {
2312
+ const existingPage = context.pages()[0];
2313
+ if (existingPage) {
2314
+ return existingPage;
2020
2315
  }
2021
- throw new Error(
2022
- `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
2023
- );
2316
+ return await context.newPage();
2024
2317
  }
2025
2318
  function getPrimaryBrowserContext(browser) {
2026
2319
  const contexts = browser.contexts();
@@ -2034,19 +2327,11 @@ function getPrimaryBrowserContext(browser) {
2034
2327
  function isInspectablePageUrl2(url) {
2035
2328
  return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
2036
2329
  }
2037
- function isDisposableStartupPageUrl(url) {
2038
- return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
2039
- }
2040
- function pageLooselyMatchesUrl(currentUrl, initialUrl) {
2330
+ function safePageUrl(page) {
2041
2331
  try {
2042
- const current = new URL(currentUrl);
2043
- const requested = new URL(initialUrl);
2044
- if (current.href === requested.href) {
2045
- return true;
2046
- }
2047
- return current.hostname === requested.hostname && current.pathname === requested.pathname;
2332
+ return page.url();
2048
2333
  } catch {
2049
- return currentUrl === initialUrl;
2334
+ return "";
2050
2335
  }
2051
2336
  }
2052
2337
  function normalizeDiscoveryUrl(cdpUrl) {
@@ -2126,38 +2411,12 @@ async function reserveDebugPort() {
2126
2411
  });
2127
2412
  });
2128
2413
  }
2129
- async function cloneProfileToTempDir(userDataDir, profileDirectory) {
2130
- const resolvedUserDataDir = expandHome(userDataDir);
2131
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
2132
- (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
2133
- );
2134
- const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
2135
- const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
2136
- if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
2137
- await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
2138
- recursive: true
2139
- });
2140
- } else {
2141
- await (0, import_promises.mkdir)(targetProfileDir, {
2142
- recursive: true
2143
- });
2144
- }
2145
- const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
2146
- if ((0, import_node_fs.existsSync)(localStatePath)) {
2147
- await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
2148
- }
2149
- return tempUserDataDir;
2150
- }
2151
2414
  function buildRealBrowserLaunchArgs(options) {
2152
2415
  const args = [
2153
2416
  `--user-data-dir=${options.userDataDir}`,
2154
2417
  `--profile-directory=${options.profileDirectory}`,
2155
2418
  `--remote-debugging-port=${options.debugPort}`,
2156
- "--no-first-run",
2157
- "--no-default-browser-check",
2158
- "--disable-background-networking",
2159
- "--disable-sync",
2160
- "--disable-popup-blocking"
2419
+ "--disable-blink-features=AutomationControlled"
2161
2420
  ];
2162
2421
  if (options.headless) {
2163
2422
  args.push("--headless=new");
@@ -6975,7 +7234,7 @@ async function closeTab(context, activePage, index) {
6975
7234
  }
6976
7235
 
6977
7236
  // src/actions/cookies.ts
6978
- var import_promises2 = require("fs/promises");
7237
+ var import_promises3 = require("fs/promises");
6979
7238
  async function getCookies(context, url) {
6980
7239
  return context.cookies(url ? [url] : void 0);
6981
7240
  }
@@ -6987,10 +7246,10 @@ async function clearCookies(context) {
6987
7246
  }
6988
7247
  async function exportCookies(context, filePath, url) {
6989
7248
  const cookies = await context.cookies(url ? [url] : void 0);
6990
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7249
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
6991
7250
  }
6992
7251
  async function importCookies(context, filePath) {
6993
- const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
7252
+ const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
6994
7253
  const cookies = JSON.parse(raw);
6995
7254
  await context.addCookies(cookies);
6996
7255
  }
@@ -7741,31 +8000,31 @@ function buildVariantDescriptorFromCluster(descriptors) {
7741
8000
  const keyStats = /* @__PURE__ */ new Map();
7742
8001
  for (const descriptor of descriptors) {
7743
8002
  for (const field of descriptor.fields) {
7744
- const stat = keyStats.get(field.path) || {
8003
+ const stat2 = keyStats.get(field.path) || {
7745
8004
  indices: /* @__PURE__ */ new Set(),
7746
8005
  pathNodes: [],
7747
8006
  attributes: [],
7748
8007
  sources: []
7749
8008
  };
7750
- stat.indices.add(descriptor.index);
8009
+ stat2.indices.add(descriptor.index);
7751
8010
  if (isPersistedValueNode(field.node)) {
7752
- stat.pathNodes.push(field.node.$path);
7753
- stat.attributes.push(field.node.attribute);
8011
+ stat2.pathNodes.push(field.node.$path);
8012
+ stat2.attributes.push(field.node.attribute);
7754
8013
  } else if (isPersistedSourceNode(field.node)) {
7755
- stat.sources.push("current_url");
8014
+ stat2.sources.push("current_url");
7756
8015
  }
7757
- keyStats.set(field.path, stat);
8016
+ keyStats.set(field.path, stat2);
7758
8017
  }
7759
8018
  }
7760
8019
  const mergedFields = [];
7761
- for (const [fieldPath, stat] of keyStats) {
7762
- if (stat.indices.size < threshold) continue;
7763
- if (stat.pathNodes.length >= threshold) {
8020
+ for (const [fieldPath, stat2] of keyStats) {
8021
+ if (stat2.indices.size < threshold) continue;
8022
+ if (stat2.pathNodes.length >= threshold) {
7764
8023
  let mergedFieldPath = null;
7765
- if (stat.pathNodes.length === 1) {
7766
- mergedFieldPath = sanitizeElementPath(stat.pathNodes[0]);
8024
+ if (stat2.pathNodes.length === 1) {
8025
+ mergedFieldPath = sanitizeElementPath(stat2.pathNodes[0]);
7767
8026
  } else {
7768
- mergedFieldPath = mergeElementPathsByMajority(stat.pathNodes);
8027
+ mergedFieldPath = mergeElementPathsByMajority(stat2.pathNodes);
7769
8028
  }
7770
8029
  if (!mergedFieldPath) continue;
7771
8030
  if (clusterSize === 1) {
@@ -7778,17 +8037,17 @@ function buildVariantDescriptorFromCluster(descriptors) {
7778
8037
  mergedFieldPath,
7779
8038
  "field"
7780
8039
  );
7781
- const attrThreshold = stat.pathNodes.length === 1 ? 1 : majorityThreshold(stat.pathNodes.length);
8040
+ const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
7782
8041
  mergedFields.push({
7783
8042
  path: fieldPath,
7784
8043
  node: createValueNode({
7785
8044
  elementPath: mergedFieldPath,
7786
- attribute: pickModeString(stat.attributes, attrThreshold)
8045
+ attribute: pickModeString(stat2.attributes, attrThreshold)
7787
8046
  })
7788
8047
  });
7789
8048
  continue;
7790
8049
  }
7791
- const dominantSource = pickModeString(stat.sources, threshold);
8050
+ const dominantSource = pickModeString(stat2.sources, threshold);
7792
8051
  if (dominantSource === "current_url") {
7793
8052
  mergedFields.push({
7794
8053
  path: fieldPath,
@@ -8989,7 +9248,7 @@ function selectPreferredContextPage(browser, contexts) {
8989
9248
  let aboutBlankCandidate = null;
8990
9249
  for (const context of contexts) {
8991
9250
  for (const page of context.pages()) {
8992
- const url = safePageUrl(page);
9251
+ const url = safePageUrl2(page);
8993
9252
  if (!isInternalOrEmptyUrl(url)) {
8994
9253
  return { browser, context, page };
8995
9254
  }
@@ -9000,7 +9259,7 @@ function selectPreferredContextPage(browser, contexts) {
9000
9259
  }
9001
9260
  return aboutBlankCandidate;
9002
9261
  }
9003
- function safePageUrl(page) {
9262
+ function safePageUrl2(page) {
9004
9263
  try {
9005
9264
  return page.url();
9006
9265
  } catch {
@@ -14420,8 +14679,8 @@ function buildLocalRunId(namespace) {
14420
14679
  // src/browser/chromium-profile.ts
14421
14680
  var import_node_util = require("util");
14422
14681
  var import_node_child_process3 = require("child_process");
14423
- var import_node_crypto = require("crypto");
14424
- var import_promises3 = require("fs/promises");
14682
+ var import_node_crypto2 = require("crypto");
14683
+ var import_promises4 = require("fs/promises");
14425
14684
  var import_node_fs2 = require("fs");
14426
14685
  var import_node_path2 = require("path");
14427
14686
  var import_node_os2 = require("os");
@@ -14646,7 +14905,7 @@ function resolveCookieDbPath(profileDir) {
14646
14905
  return null;
14647
14906
  }
14648
14907
  async function selectProfileDirFromUserDataDir(userDataDir) {
14649
- const entries = await (0, import_promises3.readdir)(userDataDir, {
14908
+ const entries = await (0, import_promises4.readdir)(userDataDir, {
14650
14909
  withFileTypes: true
14651
14910
  }).catch(() => []);
14652
14911
  const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path2.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
@@ -14733,20 +14992,20 @@ function detectChromiumBrand(location) {
14733
14992
  return DEFAULT_CHROMIUM_BRAND;
14734
14993
  }
14735
14994
  async function createSqliteSnapshot(dbPath) {
14736
- const snapshotDir = await (0, import_promises3.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
14995
+ const snapshotDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
14737
14996
  const snapshotPath = (0, import_node_path2.join)(snapshotDir, "Cookies");
14738
- await (0, import_promises3.copyFile)(dbPath, snapshotPath);
14997
+ await (0, import_promises4.copyFile)(dbPath, snapshotPath);
14739
14998
  for (const suffix of ["-wal", "-shm", "-journal"]) {
14740
14999
  const source = `${dbPath}${suffix}`;
14741
15000
  if (!(0, import_node_fs2.existsSync)(source)) {
14742
15001
  continue;
14743
15002
  }
14744
- await (0, import_promises3.copyFile)(source, `${snapshotPath}${suffix}`);
15003
+ await (0, import_promises4.copyFile)(source, `${snapshotPath}${suffix}`);
14745
15004
  }
14746
15005
  return {
14747
15006
  snapshotPath,
14748
15007
  cleanup: async () => {
14749
- await (0, import_promises3.rm)(snapshotDir, { recursive: true, force: true });
15008
+ await (0, import_promises4.rm)(snapshotDir, { recursive: true, force: true });
14750
15009
  }
14751
15010
  };
14752
15011
  }
@@ -14792,7 +15051,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
14792
15051
  if (buffer.length < 32) {
14793
15052
  return buffer;
14794
15053
  }
14795
- const domainHash = (0, import_node_crypto.createHash)("sha256").update(hostKey, "utf8").digest();
15054
+ const domainHash = (0, import_node_crypto2.createHash)("sha256").update(hostKey, "utf8").digest();
14796
15055
  if (buffer.subarray(0, 32).equals(domainHash)) {
14797
15056
  return buffer.subarray(32);
14798
15057
  }
@@ -14801,7 +15060,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
14801
15060
  function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
14802
15061
  const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
14803
15062
  const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
14804
- const decipher = (0, import_node_crypto.createDecipheriv)("aes-128-cbc", key, iv);
15063
+ const decipher = (0, import_node_crypto2.createDecipheriv)("aes-128-cbc", key, iv);
14805
15064
  const plaintext = Buffer.concat([
14806
15065
  decipher.update(ciphertext),
14807
15066
  decipher.final()
@@ -14814,7 +15073,7 @@ function decryptChromiumAes256GcmValue(encryptedValue, key) {
14814
15073
  const nonce = encryptedValue.subarray(3, 15);
14815
15074
  const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
14816
15075
  const authTag = encryptedValue.subarray(encryptedValue.length - 16);
14817
- const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, nonce);
15076
+ const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, nonce);
14818
15077
  decipher.setAuthTag(authTag);
14819
15078
  return Buffer.concat([
14820
15079
  decipher.update(ciphertext),
@@ -14851,7 +15110,7 @@ async function buildChromiumDecryptor(location) {
14851
15110
  `Unable to read ${brand.macService} from macOS Keychain.`
14852
15111
  );
14853
15112
  }
14854
- const key = (0, import_node_crypto.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
15113
+ const key = (0, import_node_crypto2.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
14855
15114
  return async (row) => decryptChromiumAes128CbcValue(
14856
15115
  Buffer.from(row.encrypted_value || "", "hex"),
14857
15116
  key,
@@ -14862,7 +15121,7 @@ async function buildChromiumDecryptor(location) {
14862
15121
  const brand = detectChromiumBrand(location);
14863
15122
  const keychainStore = createKeychainStore();
14864
15123
  const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
14865
- const key = (0, import_node_crypto.pbkdf2Sync)(
15124
+ const key = (0, import_node_crypto2.pbkdf2Sync)(
14866
15125
  password || "peanuts",
14867
15126
  KEY_SALT,
14868
15127
  LINUX_KEY_ITERATIONS,
@@ -14882,7 +15141,7 @@ async function buildChromiumDecryptor(location) {
14882
15141
  );
14883
15142
  }
14884
15143
  const localState = JSON.parse(
14885
- await (0, import_promises3.readFile)(location.localStatePath, "utf8")
15144
+ await (0, import_promises4.readFile)(location.localStatePath, "utf8")
14886
15145
  );
14887
15146
  const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
14888
15147
  if (!encryptedKeyBase64) {
@@ -14976,18 +15235,18 @@ async function loadCookiesFromSqlite(location) {
14976
15235
  }
14977
15236
  }
14978
15237
  async function loadCookiesFromBrowserSnapshot(location, options) {
14979
- const snapshotRootDir = await (0, import_promises3.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
15238
+ const snapshotRootDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
14980
15239
  const snapshotProfileDir = (0, import_node_path2.join)(
14981
15240
  snapshotRootDir,
14982
15241
  (0, import_node_path2.basename)(location.profileDir)
14983
15242
  );
14984
15243
  let context = null;
14985
15244
  try {
14986
- await (0, import_promises3.cp)(location.profileDir, snapshotProfileDir, {
15245
+ await (0, import_promises4.cp)(location.profileDir, snapshotProfileDir, {
14987
15246
  recursive: true
14988
15247
  });
14989
15248
  if (location.localStatePath) {
14990
- await (0, import_promises3.copyFile)(location.localStatePath, (0, import_node_path2.join)(snapshotRootDir, "Local State"));
15249
+ await (0, import_promises4.copyFile)(location.localStatePath, (0, import_node_path2.join)(snapshotRootDir, "Local State"));
14991
15250
  }
14992
15251
  const brand = detectChromiumBrand(location);
14993
15252
  const args = [`--profile-directory=${(0, import_node_path2.basename)(snapshotProfileDir)}`];
@@ -15000,7 +15259,7 @@ async function loadCookiesFromBrowserSnapshot(location, options) {
15000
15259
  return await context.cookies();
15001
15260
  } finally {
15002
15261
  await context?.close().catch(() => void 0);
15003
- await (0, import_promises3.rm)(snapshotRootDir, { recursive: true, force: true });
15262
+ await (0, import_promises4.rm)(snapshotRootDir, { recursive: true, force: true });
15004
15263
  }
15005
15264
  }
15006
15265
  function isMissingSqliteBinary(error) {
@@ -15243,7 +15502,7 @@ function toResolvedCloudCredential(source, credential) {
15243
15502
  }
15244
15503
 
15245
15504
  // src/auth/machine-credential-store.ts
15246
- var import_node_crypto2 = require("crypto");
15505
+ var import_node_crypto3 = require("crypto");
15247
15506
  var import_node_fs3 = __toESM(require("fs"), 1);
15248
15507
  var import_node_os3 = __toESM(require("os"), 1);
15249
15508
  var import_node_path3 = __toESM(require("path"), 1);
@@ -15391,7 +15650,7 @@ function createMachineCredentialStore(options = {}) {
15391
15650
  }
15392
15651
  function resolveCredentialSlot(authDir, target) {
15393
15652
  const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
15394
- const storageKey = (0, import_node_crypto2.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
15653
+ const storageKey = (0, import_node_crypto3.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
15395
15654
  return {
15396
15655
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
15397
15656
  metadataPath: import_node_path3.default.join(authDir, `cli-login.${storageKey}.json`),
@@ -16316,7 +16575,7 @@ function createDefaultDeps() {
16316
16575
  loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
16317
16576
  isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
16318
16577
  confirm: async (message) => {
16319
- const rl = (0, import_promises4.createInterface)({
16578
+ const rl = (0, import_promises5.createInterface)({
16320
16579
  input: process.stdin,
16321
16580
  output: process.stderr
16322
16581
  });