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.
- package/dist/{browser-profile-client-OUHaODro.d.ts → browser-profile-client-AGTsLQxT.d.ts} +1 -1
- package/dist/{browser-profile-client-D6PuRefA.d.cts → browser-profile-client-Biu6DyT6.d.cts} +1 -1
- package/dist/{chunk-G6V2DJRN.js → chunk-ACRSRDRN.js} +7916 -7644
- package/dist/cli/auth.d.cts +1 -1
- package/dist/cli/auth.d.ts +1 -1
- package/dist/cli/profile.cjs +432 -173
- package/dist/cli/profile.d.cts +2 -2
- package/dist/cli/profile.d.ts +2 -2
- package/dist/cli/profile.js +1 -1
- package/dist/cli/server.cjs +413 -154
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +423 -152
- package/dist/index.d.cts +48 -5
- package/dist/index.d.ts +48 -5
- package/dist/index.js +15 -2
- package/dist/{types-BWItZPl_.d.ts → types-Cr10igF3.d.cts} +1 -1
- package/dist/{types-BWItZPl_.d.cts → types-Cr10igF3.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/electron/SKILL.md +6 -4
- package/skills/electron/references/opensteer-electron-recipes.md +5 -3
- package/skills/electron/references/opensteer-electron-workflow.md +2 -2
- package/skills/opensteer/SKILL.md +17 -3
- package/skills/opensteer/references/cli-reference.md +4 -1
package/dist/cli/server.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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 (
|
|
922
|
-
await (0,
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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,
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
1393
|
+
async function createOwnedBrowserContextAndPage(browser) {
|
|
1055
1394
|
const context = getPrimaryBrowserContext(browser);
|
|
1056
|
-
const page = await
|
|
1395
|
+
const page = await getExistingPageOrCreate(context);
|
|
1057
1396
|
return { context, page };
|
|
1058
1397
|
}
|
|
1059
|
-
async function
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
"--
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
8002
|
+
stat2.indices.add(descriptor.index);
|
|
7744
8003
|
if (isPersistedValueNode(field.node)) {
|
|
7745
|
-
|
|
7746
|
-
|
|
8004
|
+
stat2.pathNodes.push(field.node.$path);
|
|
8005
|
+
stat2.attributes.push(field.node.attribute);
|
|
7747
8006
|
} else if (isPersistedSourceNode(field.node)) {
|
|
7748
|
-
|
|
8007
|
+
stat2.sources.push("current_url");
|
|
7749
8008
|
}
|
|
7750
|
-
keyStats.set(field.path,
|
|
8009
|
+
keyStats.set(field.path, stat2);
|
|
7751
8010
|
}
|
|
7752
8011
|
}
|
|
7753
8012
|
const mergedFields = [];
|
|
7754
|
-
for (const [fieldPath,
|
|
7755
|
-
if (
|
|
7756
|
-
if (
|
|
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 (
|
|
7759
|
-
mergedFieldPath = sanitizeElementPath(
|
|
8017
|
+
if (stat2.pathNodes.length === 1) {
|
|
8018
|
+
mergedFieldPath = sanitizeElementPath(stat2.pathNodes[0]);
|
|
7760
8019
|
} else {
|
|
7761
|
-
mergedFieldPath = mergeElementPathsByMajority(
|
|
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 =
|
|
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(
|
|
8038
|
+
attribute: pickModeString(stat2.attributes, attrThreshold)
|
|
7780
8039
|
})
|
|
7781
8040
|
});
|
|
7782
8041
|
continue;
|
|
7783
8042
|
}
|
|
7784
|
-
const dominantSource = pickModeString(
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
14743
|
+
await (0, import_promises4.writeFile)(file, buffer);
|
|
14485
14744
|
return { file };
|
|
14486
14745
|
},
|
|
14487
14746
|
async click(opensteer, args) {
|