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,11 +425,8 @@ var import_crypto = require("crypto");
425
425
 
426
426
  // src/browser/pool.ts
427
427
  var import_node_child_process = require("child_process");
428
- var import_node_fs = require("fs");
429
- var import_promises = require("fs/promises");
428
+ var import_promises2 = require("fs/promises");
430
429
  var import_node_net = require("net");
431
- var import_node_os = require("os");
432
- var import_node_path = require("path");
433
430
  var import_playwright = require("playwright");
434
431
 
435
432
  // src/browser/cdp-proxy.ts
@@ -437,6 +434,8 @@ var import_ws = __toESM(require("ws"), 1);
437
434
  var CDP_DISCOVERY_TIMEOUT_MS = 3e3;
438
435
  var LOCAL_PROXY_HOST = "127.0.0.1";
439
436
  var INTERNAL_COMMAND_ID_START = 1e9;
437
+ var CREATE_BLANK_TARGET_COMMAND_ID = 1;
438
+ var CREATE_BLANK_TARGET_TIMEOUT_MS = 5e3;
440
439
  async function discoverTargets(cdpUrl) {
441
440
  const baseUrl = resolveHttpDiscoveryBase(cdpUrl);
442
441
  const [targetsPayload, versionPayload] = await Promise.all([
@@ -448,6 +447,81 @@ async function discoverTargets(cdpUrl) {
448
447
  targets: readPageTargets(targetsPayload)
449
448
  };
450
449
  }
450
+ function createBlankTarget(browserWsUrl) {
451
+ return new Promise((resolve, reject) => {
452
+ const ws = new import_ws.default(browserWsUrl);
453
+ let settled = false;
454
+ function settle(handler) {
455
+ if (settled) {
456
+ return;
457
+ }
458
+ settled = true;
459
+ clearTimeout(timeout);
460
+ ws.close();
461
+ handler();
462
+ }
463
+ const timeout = setTimeout(() => {
464
+ settle(
465
+ () => reject(new Error("Timed out creating a blank tab via CDP."))
466
+ );
467
+ }, CREATE_BLANK_TARGET_TIMEOUT_MS);
468
+ ws.once("open", () => {
469
+ ws.send(
470
+ JSON.stringify({
471
+ id: CREATE_BLANK_TARGET_COMMAND_ID,
472
+ method: "Target.createTarget",
473
+ params: { url: "about:blank" }
474
+ })
475
+ );
476
+ });
477
+ ws.on("message", (data, isBinary) => {
478
+ if (isBinary) {
479
+ return;
480
+ }
481
+ const message = parseMessage(data);
482
+ if (!message || asNumber(message.id) !== CREATE_BLANK_TARGET_COMMAND_ID) {
483
+ return;
484
+ }
485
+ const cdpError = asObject(message.error);
486
+ if (cdpError) {
487
+ settle(
488
+ () => reject(
489
+ new Error(
490
+ `Target.createTarget failed: ${asString(cdpError.message) ?? JSON.stringify(cdpError)}`
491
+ )
492
+ )
493
+ );
494
+ return;
495
+ }
496
+ const targetId = asString(asObject(message.result)?.targetId);
497
+ if (targetId) {
498
+ settle(() => resolve(targetId));
499
+ return;
500
+ }
501
+ settle(
502
+ () => reject(
503
+ new Error(
504
+ "Target.createTarget succeeded but no targetId was returned."
505
+ )
506
+ )
507
+ );
508
+ });
509
+ ws.once("error", (err) => {
510
+ settle(
511
+ () => reject(new Error(`Failed to create blank tab: ${errorMessage(err)}`))
512
+ );
513
+ });
514
+ ws.once("close", () => {
515
+ settle(
516
+ () => reject(
517
+ new Error(
518
+ "CDP browser websocket closed before blank tab creation completed."
519
+ )
520
+ )
521
+ );
522
+ });
523
+ });
524
+ }
451
525
  var CDPProxy = class {
452
526
  constructor(browserWsUrl, targetId) {
453
527
  this.browserWsUrl = browserWsUrl;
@@ -845,18 +919,282 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
845
919
  }
846
920
  }
847
921
 
922
+ // src/browser/persistent-profile.ts
923
+ var import_node_crypto = require("crypto");
924
+ var import_node_fs = require("fs");
925
+ var import_promises = require("fs/promises");
926
+ var import_node_os = require("os");
927
+ var import_node_path = require("path");
928
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
929
+ var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
930
+ var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
931
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
932
+ "SingletonCookie",
933
+ "SingletonLock",
934
+ "SingletonSocket",
935
+ "DevToolsActivePort",
936
+ "lockfile"
937
+ ]);
938
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
939
+ ...CHROME_SINGLETON_ENTRIES,
940
+ OPENSTEER_META_FILE
941
+ ]);
942
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
943
+ "Crash Reports",
944
+ "Crashpad",
945
+ "BrowserMetrics",
946
+ "GrShaderCache",
947
+ "ShaderCache",
948
+ "GraphiteDawnCache",
949
+ "component_crx_cache",
950
+ "Crowd Deny",
951
+ "hyphen-data",
952
+ "OnDeviceHeadSuggestModel",
953
+ "OptimizationGuidePredictionModels",
954
+ "Segmentation Platform",
955
+ "SmartCardDeviceNames",
956
+ "WidevineCdm",
957
+ "pnacl"
958
+ ]);
959
+ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
960
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
961
+ const targetUserDataDir = (0, import_node_path.join)(
962
+ expandHome(profilesRootDir),
963
+ buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
964
+ );
965
+ const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
966
+ const metadata = buildPersistentProfileMetadata(
967
+ resolvedSourceUserDataDir,
968
+ profileDirectory
969
+ );
970
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
971
+ await cleanOrphanedTempDirs(
972
+ (0, import_node_path.dirname)(targetUserDataDir),
973
+ (0, import_node_path.basename)(targetUserDataDir)
974
+ );
975
+ if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
976
+ throw new Error(
977
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
978
+ );
979
+ }
980
+ const created = await createPersistentProfileClone(
981
+ resolvedSourceUserDataDir,
982
+ sourceProfileDir,
983
+ targetUserDataDir,
984
+ profileDirectory,
985
+ metadata
986
+ );
987
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
988
+ return {
989
+ created,
990
+ userDataDir: targetUserDataDir
991
+ };
992
+ }
993
+ async function clearPersistentProfileSingletons(userDataDir) {
994
+ await Promise.all(
995
+ [...CHROME_SINGLETON_ENTRIES].map(
996
+ (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
997
+ force: true,
998
+ recursive: true
999
+ }).catch(() => void 0)
1000
+ )
1001
+ );
1002
+ }
1003
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1004
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1005
+ const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1006
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1007
+ return `${sourceLabel}-${profileLabel}-${hash}`;
1008
+ }
1009
+ function defaultPersistentProfilesRootDir() {
1010
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1011
+ }
1012
+ function sanitizePathSegment(value) {
1013
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1014
+ return sanitized.replace(/^-|-$/g, "") || "profile";
1015
+ }
1016
+ function isProfileDirectory(userDataDir, entry) {
1017
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1018
+ }
1019
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1020
+ let entries;
1021
+ try {
1022
+ entries = await (0, import_promises.readdir)(sourceUserDataDir);
1023
+ } catch {
1024
+ return;
1025
+ }
1026
+ const copyTasks = [];
1027
+ for (const entry of entries) {
1028
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
1029
+ if (entry === targetProfileDirectory) continue;
1030
+ const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
1031
+ const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
1032
+ if ((0, import_node_fs.existsSync)(targetPath)) continue;
1033
+ let entryStat;
1034
+ try {
1035
+ entryStat = await (0, import_promises.stat)(sourcePath);
1036
+ } catch {
1037
+ continue;
1038
+ }
1039
+ if (entryStat.isFile()) {
1040
+ copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
1041
+ } else if (entryStat.isDirectory()) {
1042
+ if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1043
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1044
+ copyTasks.push(
1045
+ (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
1046
+ () => void 0
1047
+ )
1048
+ );
1049
+ }
1050
+ }
1051
+ await Promise.all(copyTasks);
1052
+ }
1053
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
1054
+ await (0, import_promises.writeFile)(
1055
+ (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1056
+ JSON.stringify(metadata, null, 2)
1057
+ );
1058
+ }
1059
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1060
+ return {
1061
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1062
+ profileDirectory,
1063
+ source: sourceUserDataDir
1064
+ };
1065
+ }
1066
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1067
+ if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1068
+ return false;
1069
+ }
1070
+ const tempUserDataDir = await (0, import_promises.mkdtemp)(
1071
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
1072
+ );
1073
+ let published = false;
1074
+ try {
1075
+ await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1076
+ recursive: true
1077
+ });
1078
+ await copyRootLevelEntries(
1079
+ sourceUserDataDir,
1080
+ tempUserDataDir,
1081
+ profileDirectory
1082
+ );
1083
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
1084
+ try {
1085
+ await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1086
+ } catch (error) {
1087
+ if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1088
+ return false;
1089
+ }
1090
+ throw error;
1091
+ }
1092
+ published = true;
1093
+ return true;
1094
+ } finally {
1095
+ if (!published) {
1096
+ await (0, import_promises.rm)(tempUserDataDir, {
1097
+ recursive: true,
1098
+ force: true
1099
+ }).catch(() => void 0);
1100
+ }
1101
+ }
1102
+ }
1103
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
1104
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
1105
+ return;
1106
+ }
1107
+ await writePersistentProfileMetadata(userDataDir, metadata);
1108
+ }
1109
+ function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
1110
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1111
+ return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
1112
+ }
1113
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
1114
+ return (0, import_node_path.join)(
1115
+ (0, import_node_path.dirname)(targetUserDataDir),
1116
+ `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
1117
+ );
1118
+ }
1119
+ async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
1120
+ let entries;
1121
+ try {
1122
+ entries = await (0, import_promises.readdir)(profilesDir, {
1123
+ encoding: "utf8",
1124
+ withFileTypes: true
1125
+ });
1126
+ } catch {
1127
+ return;
1128
+ }
1129
+ const tempDirPrefix = `${targetBaseName}-tmp-`;
1130
+ await Promise.all(
1131
+ entries.map(async (entry) => {
1132
+ if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
1133
+ return;
1134
+ }
1135
+ if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
1136
+ return;
1137
+ }
1138
+ await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
1139
+ recursive: true,
1140
+ force: true
1141
+ }).catch(() => void 0);
1142
+ })
1143
+ );
1144
+ }
1145
+ function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
1146
+ const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
1147
+ if (!owner) {
1148
+ return false;
1149
+ }
1150
+ if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
1151
+ return true;
1152
+ }
1153
+ return isProcessRunning(owner.pid);
1154
+ }
1155
+ function parseTempDirOwner(tempDirName, tempDirPrefix) {
1156
+ const remainder = tempDirName.slice(tempDirPrefix.length);
1157
+ const firstDashIndex = remainder.indexOf("-");
1158
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
1159
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
1160
+ return null;
1161
+ }
1162
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
1163
+ const processStartedAtMs = Number.parseInt(
1164
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
1165
+ 10
1166
+ );
1167
+ if (!Number.isInteger(pid) || pid <= 0) {
1168
+ return null;
1169
+ }
1170
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1171
+ return null;
1172
+ }
1173
+ return { pid, processStartedAtMs };
1174
+ }
1175
+ function isProcessRunning(pid) {
1176
+ try {
1177
+ process.kill(pid, 0);
1178
+ return true;
1179
+ } catch (error) {
1180
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1181
+ return code !== "ESRCH";
1182
+ }
1183
+ }
1184
+
848
1185
  // src/browser/pool.ts
849
1186
  var BrowserPool = class {
850
1187
  browser = null;
851
1188
  cdpProxy = null;
852
1189
  launchedProcess = null;
853
- tempUserDataDir = null;
1190
+ managedUserDataDir = null;
1191
+ persistentProfile = false;
854
1192
  defaults;
855
1193
  constructor(defaults = {}) {
856
1194
  this.defaults = defaults;
857
1195
  }
858
1196
  async launch(options = {}) {
859
- if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
1197
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
860
1198
  await this.close();
861
1199
  }
862
1200
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -906,11 +1244,13 @@ var BrowserPool = class {
906
1244
  const browser = this.browser;
907
1245
  const cdpProxy = this.cdpProxy;
908
1246
  const launchedProcess = this.launchedProcess;
909
- const tempUserDataDir = this.tempUserDataDir;
1247
+ const managedUserDataDir = this.managedUserDataDir;
1248
+ const persistentProfile = this.persistentProfile;
910
1249
  this.browser = null;
911
1250
  this.cdpProxy = null;
912
1251
  this.launchedProcess = null;
913
- this.tempUserDataDir = null;
1252
+ this.managedUserDataDir = null;
1253
+ this.persistentProfile = false;
914
1254
  try {
915
1255
  if (browser) {
916
1256
  await browser.close().catch(() => void 0);
@@ -918,8 +1258,8 @@ var BrowserPool = class {
918
1258
  } finally {
919
1259
  cdpProxy?.close();
920
1260
  await killProcessTree(launchedProcess);
921
- if (tempUserDataDir) {
922
- await (0, import_promises.rm)(tempUserDataDir, {
1261
+ if (managedUserDataDir && !persistentProfile) {
1262
+ await (0, import_promises2.rm)(managedUserDataDir, {
923
1263
  recursive: true,
924
1264
  force: true
925
1265
  }).catch(() => void 0);
@@ -931,12 +1271,13 @@ var BrowserPool = class {
931
1271
  let cdpProxy = null;
932
1272
  try {
933
1273
  const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
934
- if (targets.length === 0) {
935
- throw new Error(
936
- "No page targets found. Is the browser running with an open window?"
937
- );
1274
+ let targetId;
1275
+ if (targets.length > 0) {
1276
+ targetId = targets[0].id;
1277
+ } else {
1278
+ targetId = await createBlankTarget(browserWsUrl);
938
1279
  }
939
- cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
1280
+ cdpProxy = new CDPProxy(browserWsUrl, targetId);
940
1281
  const proxyWsUrl = await cdpProxy.start();
941
1282
  browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
942
1283
  timeout: timeout ?? 3e4
@@ -967,10 +1308,11 @@ var BrowserPool = class {
967
1308
  options.userDataDir ?? chromePaths.defaultUserDataDir
968
1309
  );
969
1310
  const profileDirectory = options.profileDirectory ?? "Default";
970
- const tempUserDataDir = await cloneProfileToTempDir(
1311
+ const persistentProfile = await getOrCreatePersistentProfile(
971
1312
  sourceUserDataDir,
972
1313
  profileDirectory
973
1314
  );
1315
+ await clearPersistentProfileSingletons(persistentProfile.userDataDir);
974
1316
  const debugPort = await reserveDebugPort();
975
1317
  const headless = resolveLaunchHeadless(
976
1318
  "real",
@@ -978,7 +1320,7 @@ var BrowserPool = class {
978
1320
  this.defaults.headless
979
1321
  );
980
1322
  const launchArgs = buildRealBrowserLaunchArgs({
981
- userDataDir: tempUserDataDir,
1323
+ userDataDir: persistentProfile.userDataDir,
982
1324
  profileDirectory,
983
1325
  debugPort,
984
1326
  headless
@@ -998,24 +1340,22 @@ var BrowserPool = class {
998
1340
  timeout: options.timeout ?? 3e4
999
1341
  });
1000
1342
  const { context, page } = await createOwnedBrowserContextAndPage(
1001
- browser,
1002
- {
1003
- headless,
1004
- initialUrl: options.initialUrl,
1005
- timeoutMs: options.timeout ?? 3e4
1006
- }
1343
+ browser
1007
1344
  );
1345
+ if (options.initialUrl) {
1346
+ await page.goto(options.initialUrl, {
1347
+ waitUntil: "domcontentloaded",
1348
+ timeout: options.timeout ?? 3e4
1349
+ });
1350
+ }
1008
1351
  this.browser = browser;
1009
1352
  this.launchedProcess = processHandle;
1010
- this.tempUserDataDir = tempUserDataDir;
1353
+ this.managedUserDataDir = persistentProfile.userDataDir;
1354
+ this.persistentProfile = true;
1011
1355
  return { browser, context, page, isExternal: false };
1012
1356
  } catch (error) {
1013
1357
  await browser?.close().catch(() => void 0);
1014
1358
  await killProcessTree(processHandle);
1015
- await (0, import_promises.rm)(tempUserDataDir, {
1016
- recursive: true,
1017
- force: true
1018
- }).catch(() => void 0);
1019
1359
  throw error;
1020
1360
  }
1021
1361
  }
@@ -1038,8 +1378,7 @@ var BrowserPool = class {
1038
1378
  };
1039
1379
  async function pickBrowserContextAndPage(browser) {
1040
1380
  const context = getPrimaryBrowserContext(browser);
1041
- const pages = context.pages();
1042
- const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
1381
+ const page = await getAttachedPageOrCreate(context);
1043
1382
  return { context, page };
1044
1383
  }
1045
1384
  function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
@@ -1051,77 +1390,31 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1051
1390
  }
1052
1391
  return mode === "real";
1053
1392
  }
1054
- async function createOwnedBrowserContextAndPage(browser, options) {
1393
+ async function createOwnedBrowserContextAndPage(browser) {
1055
1394
  const context = getPrimaryBrowserContext(browser);
1056
- const page = await createOwnedBrowserPage(browser, context, options);
1395
+ const page = await getExistingPageOrCreate(context);
1057
1396
  return { context, page };
1058
1397
  }
1059
- async function createOwnedBrowserPage(browser, context, options) {
1060
- const targetUrl = options.initialUrl ?? "about:blank";
1061
- const existingPages = new Set(context.pages());
1062
- const browserSession = await browser.newBrowserCDPSession();
1063
- try {
1064
- const { targetId } = await browserSession.send("Target.createTarget", {
1065
- url: targetUrl,
1066
- newWindow: !options.headless
1067
- });
1068
- await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
1069
- const page = await waitForOwnedBrowserPage(context, {
1070
- existingPages,
1071
- targetUrl,
1072
- timeoutMs: options.timeoutMs
1073
- });
1074
- if (targetUrl !== "about:blank") {
1075
- await page.waitForLoadState("domcontentloaded", {
1076
- timeout: options.timeoutMs
1077
- });
1078
- }
1079
- await closeDisposableStartupTargets(browserSession, targetId);
1080
- return page;
1081
- } finally {
1082
- await browserSession.detach().catch(() => void 0);
1083
- }
1084
- }
1085
- async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
1086
- const response = await browserSession.send("Target.getTargets").catch(() => null);
1087
- if (!response) {
1088
- return;
1398
+ async function getAttachedPageOrCreate(context) {
1399
+ const pages = context.pages();
1400
+ const inspectablePage = pages.find(
1401
+ (candidate) => isInspectablePageUrl2(safePageUrl(candidate))
1402
+ );
1403
+ if (inspectablePage) {
1404
+ return inspectablePage;
1089
1405
  }
1090
- for (const targetInfo of response.targetInfos) {
1091
- if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
1092
- continue;
1093
- }
1094
- await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
1406
+ const attachedPage = pages[0];
1407
+ if (attachedPage) {
1408
+ return attachedPage;
1095
1409
  }
1410
+ return await context.newPage();
1096
1411
  }
1097
- async function waitForOwnedBrowserPage(context, options) {
1098
- const deadline = Date.now() + options.timeoutMs;
1099
- let fallbackPage = null;
1100
- while (Date.now() < deadline) {
1101
- for (const candidate of context.pages()) {
1102
- if (options.existingPages.has(candidate)) {
1103
- continue;
1104
- }
1105
- const url = candidate.url();
1106
- if (!isInspectablePageUrl2(url)) {
1107
- continue;
1108
- }
1109
- fallbackPage ??= candidate;
1110
- if (options.targetUrl === "about:blank") {
1111
- return candidate;
1112
- }
1113
- if (pageLooselyMatchesUrl(url, options.targetUrl)) {
1114
- return candidate;
1115
- }
1116
- }
1117
- await sleep(100);
1118
- }
1119
- if (fallbackPage) {
1120
- return fallbackPage;
1412
+ async function getExistingPageOrCreate(context) {
1413
+ const existingPage = context.pages()[0];
1414
+ if (existingPage) {
1415
+ return existingPage;
1121
1416
  }
1122
- throw new Error(
1123
- `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
1124
- );
1417
+ return await context.newPage();
1125
1418
  }
1126
1419
  function getPrimaryBrowserContext(browser) {
1127
1420
  const contexts = browser.contexts();
@@ -1135,19 +1428,11 @@ function getPrimaryBrowserContext(browser) {
1135
1428
  function isInspectablePageUrl2(url) {
1136
1429
  return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
1137
1430
  }
1138
- function isDisposableStartupPageUrl(url) {
1139
- return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
1140
- }
1141
- function pageLooselyMatchesUrl(currentUrl, initialUrl) {
1431
+ function safePageUrl(page) {
1142
1432
  try {
1143
- const current = new URL(currentUrl);
1144
- const requested = new URL(initialUrl);
1145
- if (current.href === requested.href) {
1146
- return true;
1147
- }
1148
- return current.hostname === requested.hostname && current.pathname === requested.pathname;
1433
+ return page.url();
1149
1434
  } catch {
1150
- return currentUrl === initialUrl;
1435
+ return "";
1151
1436
  }
1152
1437
  }
1153
1438
  function normalizeDiscoveryUrl(cdpUrl) {
@@ -1227,38 +1512,12 @@ async function reserveDebugPort() {
1227
1512
  });
1228
1513
  });
1229
1514
  }
1230
- async function cloneProfileToTempDir(userDataDir, profileDirectory) {
1231
- const resolvedUserDataDir = expandHome(userDataDir);
1232
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
1233
- (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
1234
- );
1235
- const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
1236
- const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
1237
- if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
1238
- await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
1239
- recursive: true
1240
- });
1241
- } else {
1242
- await (0, import_promises.mkdir)(targetProfileDir, {
1243
- recursive: true
1244
- });
1245
- }
1246
- const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
1247
- if ((0, import_node_fs.existsSync)(localStatePath)) {
1248
- await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
1249
- }
1250
- return tempUserDataDir;
1251
- }
1252
1515
  function buildRealBrowserLaunchArgs(options) {
1253
1516
  const args = [
1254
1517
  `--user-data-dir=${options.userDataDir}`,
1255
1518
  `--profile-directory=${options.profileDirectory}`,
1256
1519
  `--remote-debugging-port=${options.debugPort}`,
1257
- "--no-first-run",
1258
- "--no-default-browser-check",
1259
- "--disable-background-networking",
1260
- "--disable-sync",
1261
- "--disable-popup-blocking"
1520
+ "--disable-blink-features=AutomationControlled"
1262
1521
  ];
1263
1522
  if (options.headless) {
1264
1523
  args.push("--headless=new");
@@ -6968,7 +7227,7 @@ async function closeTab(context, activePage, index) {
6968
7227
  }
6969
7228
 
6970
7229
  // src/actions/cookies.ts
6971
- var import_promises2 = require("fs/promises");
7230
+ var import_promises3 = require("fs/promises");
6972
7231
  async function getCookies(context, url) {
6973
7232
  return context.cookies(url ? [url] : void 0);
6974
7233
  }
@@ -6980,10 +7239,10 @@ async function clearCookies(context) {
6980
7239
  }
6981
7240
  async function exportCookies(context, filePath, url) {
6982
7241
  const cookies = await context.cookies(url ? [url] : void 0);
6983
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7242
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
6984
7243
  }
6985
7244
  async function importCookies(context, filePath) {
6986
- const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
7245
+ const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
6987
7246
  const cookies = JSON.parse(raw);
6988
7247
  await context.addCookies(cookies);
6989
7248
  }
@@ -7734,31 +7993,31 @@ function buildVariantDescriptorFromCluster(descriptors) {
7734
7993
  const keyStats = /* @__PURE__ */ new Map();
7735
7994
  for (const descriptor of descriptors) {
7736
7995
  for (const field of descriptor.fields) {
7737
- const stat = keyStats.get(field.path) || {
7996
+ const stat2 = keyStats.get(field.path) || {
7738
7997
  indices: /* @__PURE__ */ new Set(),
7739
7998
  pathNodes: [],
7740
7999
  attributes: [],
7741
8000
  sources: []
7742
8001
  };
7743
- stat.indices.add(descriptor.index);
8002
+ stat2.indices.add(descriptor.index);
7744
8003
  if (isPersistedValueNode(field.node)) {
7745
- stat.pathNodes.push(field.node.$path);
7746
- stat.attributes.push(field.node.attribute);
8004
+ stat2.pathNodes.push(field.node.$path);
8005
+ stat2.attributes.push(field.node.attribute);
7747
8006
  } else if (isPersistedSourceNode(field.node)) {
7748
- stat.sources.push("current_url");
8007
+ stat2.sources.push("current_url");
7749
8008
  }
7750
- keyStats.set(field.path, stat);
8009
+ keyStats.set(field.path, stat2);
7751
8010
  }
7752
8011
  }
7753
8012
  const mergedFields = [];
7754
- for (const [fieldPath, stat] of keyStats) {
7755
- if (stat.indices.size < threshold) continue;
7756
- if (stat.pathNodes.length >= threshold) {
8013
+ for (const [fieldPath, stat2] of keyStats) {
8014
+ if (stat2.indices.size < threshold) continue;
8015
+ if (stat2.pathNodes.length >= threshold) {
7757
8016
  let mergedFieldPath = null;
7758
- if (stat.pathNodes.length === 1) {
7759
- mergedFieldPath = sanitizeElementPath(stat.pathNodes[0]);
8017
+ if (stat2.pathNodes.length === 1) {
8018
+ mergedFieldPath = sanitizeElementPath(stat2.pathNodes[0]);
7760
8019
  } else {
7761
- mergedFieldPath = mergeElementPathsByMajority(stat.pathNodes);
8020
+ mergedFieldPath = mergeElementPathsByMajority(stat2.pathNodes);
7762
8021
  }
7763
8022
  if (!mergedFieldPath) continue;
7764
8023
  if (clusterSize === 1) {
@@ -7771,17 +8030,17 @@ function buildVariantDescriptorFromCluster(descriptors) {
7771
8030
  mergedFieldPath,
7772
8031
  "field"
7773
8032
  );
7774
- const attrThreshold = stat.pathNodes.length === 1 ? 1 : majorityThreshold(stat.pathNodes.length);
8033
+ const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
7775
8034
  mergedFields.push({
7776
8035
  path: fieldPath,
7777
8036
  node: createValueNode({
7778
8037
  elementPath: mergedFieldPath,
7779
- attribute: pickModeString(stat.attributes, attrThreshold)
8038
+ attribute: pickModeString(stat2.attributes, attrThreshold)
7780
8039
  })
7781
8040
  });
7782
8041
  continue;
7783
8042
  }
7784
- const dominantSource = pickModeString(stat.sources, threshold);
8043
+ const dominantSource = pickModeString(stat2.sources, threshold);
7785
8044
  if (dominantSource === "current_url") {
7786
8045
  mergedFields.push({
7787
8046
  path: fieldPath,
@@ -8982,7 +9241,7 @@ function selectPreferredContextPage(browser, contexts) {
8982
9241
  let aboutBlankCandidate = null;
8983
9242
  for (const context of contexts) {
8984
9243
  for (const page of context.pages()) {
8985
- const url = safePageUrl(page);
9244
+ const url = safePageUrl2(page);
8986
9245
  if (!isInternalOrEmptyUrl(url)) {
8987
9246
  return { browser, context, page };
8988
9247
  }
@@ -8993,7 +9252,7 @@ function selectPreferredContextPage(browser, contexts) {
8993
9252
  }
8994
9253
  return aboutBlankCandidate;
8995
9254
  }
8996
- function safePageUrl(page) {
9255
+ function safePageUrl2(page) {
8997
9256
  try {
8998
9257
  return page.url();
8999
9258
  } catch {
@@ -14427,7 +14686,7 @@ function getMetadataPath(session2) {
14427
14686
  }
14428
14687
 
14429
14688
  // src/cli/commands.ts
14430
- var import_promises3 = require("fs/promises");
14689
+ var import_promises4 = require("fs/promises");
14431
14690
  var commands = {
14432
14691
  async navigate(opensteer, args) {
14433
14692
  const url = args.url;
@@ -14481,7 +14740,7 @@ var commands = {
14481
14740
  fullPage: args.fullPage,
14482
14741
  type
14483
14742
  });
14484
- await (0, import_promises3.writeFile)(file, buffer);
14743
+ await (0, import_promises4.writeFile)(file, buffer);
14485
14744
  return { file };
14486
14745
  },
14487
14746
  async click(opensteer, args) {