opensteer 0.6.3 → 0.6.5
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/bin/opensteer.mjs +94 -8
- package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
- package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
- package/dist/{chunk-SCNX4NN3.js → chunk-54KNQTOL.js} +141 -2
- package/dist/{chunk-FTKWQ6X3.js → chunk-6B6LOYU3.js} +1 -1
- package/dist/{chunk-3OMXCBPD.js → chunk-G6V2DJRN.js} +442 -591
- package/dist/chunk-K5CL76MG.js +81 -0
- package/dist/{chunk-KE35RQOJ.js → chunk-KPPOTU3D.js} +53 -144
- package/dist/cli/auth.cjs +53 -6
- package/dist/cli/auth.d.cts +1 -1
- package/dist/cli/auth.d.ts +1 -1
- package/dist/cli/auth.js +2 -2
- package/dist/cli/local-profile.cjs +197 -0
- package/dist/cli/local-profile.d.cts +18 -0
- package/dist/cli/local-profile.d.ts +18 -0
- package/dist/cli/local-profile.js +97 -0
- package/dist/cli/profile.cjs +2844 -2412
- package/dist/cli/profile.d.cts +2 -2
- package/dist/cli/profile.d.ts +2 -2
- package/dist/cli/profile.js +469 -7
- package/dist/cli/server.cjs +649 -204
- package/dist/cli/server.js +69 -16
- package/dist/index.cjs +578 -185
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
- package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
- package/package.json +2 -2
- package/skills/opensteer/SKILL.md +34 -14
- package/skills/opensteer/references/cli-reference.md +1 -1
- package/skills/opensteer/references/examples.md +5 -3
- package/skills/opensteer/references/sdk-reference.md +16 -14
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
cloudNotLaunchedError,
|
|
4
4
|
cloudUnsupportedMethodError,
|
|
5
5
|
createCloudRuntimeState,
|
|
6
|
-
createKeychainStore,
|
|
7
6
|
extractErrorMessage,
|
|
8
7
|
normalizeError,
|
|
9
8
|
normalizeNamespace,
|
|
@@ -13,506 +12,16 @@ import {
|
|
|
13
12
|
resolveNamespace,
|
|
14
13
|
resolveNamespaceDir,
|
|
15
14
|
selectCloudCredential
|
|
16
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KPPOTU3D.js";
|
|
16
|
+
import {
|
|
17
|
+
detectChromePaths,
|
|
18
|
+
expandHome,
|
|
19
|
+
listLocalChromeProfiles
|
|
20
|
+
} from "./chunk-K5CL76MG.js";
|
|
17
21
|
import {
|
|
18
22
|
flattenExtractionDataToFieldPlan
|
|
19
23
|
} from "./chunk-3H5RRIMZ.js";
|
|
20
24
|
|
|
21
|
-
// src/browser/chrome.ts
|
|
22
|
-
import { homedir, platform } from "os";
|
|
23
|
-
import { join } from "path";
|
|
24
|
-
function expandHome(p) {
|
|
25
|
-
if (p.startsWith("~/") || p === "~") {
|
|
26
|
-
return join(homedir(), p.slice(1));
|
|
27
|
-
}
|
|
28
|
-
return p;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/browser/chromium-profile.ts
|
|
32
|
-
import { promisify } from "util";
|
|
33
|
-
import { execFile } from "child_process";
|
|
34
|
-
import { createDecipheriv, createHash, pbkdf2Sync } from "crypto";
|
|
35
|
-
import {
|
|
36
|
-
cp,
|
|
37
|
-
copyFile,
|
|
38
|
-
mkdtemp,
|
|
39
|
-
readdir,
|
|
40
|
-
readFile,
|
|
41
|
-
rm
|
|
42
|
-
} from "fs/promises";
|
|
43
|
-
import { existsSync, statSync } from "fs";
|
|
44
|
-
import { basename, dirname, join as join2 } from "path";
|
|
45
|
-
import { tmpdir } from "os";
|
|
46
|
-
import { chromium } from "playwright";
|
|
47
|
-
var execFileAsync = promisify(execFile);
|
|
48
|
-
var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
|
|
49
|
-
var AES_BLOCK_BYTES = 16;
|
|
50
|
-
var MAC_KEY_ITERATIONS = 1003;
|
|
51
|
-
var LINUX_KEY_ITERATIONS = 1;
|
|
52
|
-
var KEY_LENGTH = 16;
|
|
53
|
-
var KEY_SALT = "saltysalt";
|
|
54
|
-
var DEFAULT_CHROMIUM_BRAND = {
|
|
55
|
-
macService: "Chrome Safe Storage",
|
|
56
|
-
macAccount: "Chrome",
|
|
57
|
-
linuxApplications: ["chrome", "google-chrome"]
|
|
58
|
-
};
|
|
59
|
-
var CHROMIUM_BRANDS = [
|
|
60
|
-
{
|
|
61
|
-
match: ["bravesoftware", "brave-browser"],
|
|
62
|
-
brand: {
|
|
63
|
-
macService: "Brave Safe Storage",
|
|
64
|
-
macAccount: "Brave",
|
|
65
|
-
linuxApplications: ["brave-browser", "brave"]
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
match: ["microsoft", "edge"],
|
|
70
|
-
brand: {
|
|
71
|
-
macService: "Microsoft Edge Safe Storage",
|
|
72
|
-
macAccount: "Microsoft Edge",
|
|
73
|
-
linuxApplications: ["microsoft-edge"],
|
|
74
|
-
playwrightChannel: "msedge"
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
match: ["google", "chrome beta"],
|
|
79
|
-
brand: {
|
|
80
|
-
macService: "Chrome Beta Safe Storage",
|
|
81
|
-
macAccount: "Chrome Beta",
|
|
82
|
-
linuxApplications: ["chrome-beta"],
|
|
83
|
-
playwrightChannel: "chrome-beta"
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
match: ["google", "chrome"],
|
|
88
|
-
brand: {
|
|
89
|
-
macService: "Chrome Safe Storage",
|
|
90
|
-
macAccount: "Chrome",
|
|
91
|
-
linuxApplications: ["chrome", "google-chrome"],
|
|
92
|
-
playwrightChannel: "chrome"
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
match: ["chromium"],
|
|
97
|
-
brand: {
|
|
98
|
-
macService: "Chromium Safe Storage",
|
|
99
|
-
macAccount: "Chromium",
|
|
100
|
-
linuxApplications: ["chromium"]
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
];
|
|
104
|
-
function directoryExists(filePath) {
|
|
105
|
-
try {
|
|
106
|
-
return statSync(filePath).isDirectory();
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function fileExists(filePath) {
|
|
112
|
-
try {
|
|
113
|
-
return statSync(filePath).isFile();
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function resolveCookieDbPath(profileDir) {
|
|
119
|
-
const candidates = [join2(profileDir, "Network", "Cookies"), join2(profileDir, "Cookies")];
|
|
120
|
-
for (const candidate of candidates) {
|
|
121
|
-
if (fileExists(candidate)) {
|
|
122
|
-
return candidate;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
async function selectProfileDirFromUserDataDir(userDataDir) {
|
|
128
|
-
const entries = await readdir(userDataDir, {
|
|
129
|
-
withFileTypes: true
|
|
130
|
-
}).catch(() => []);
|
|
131
|
-
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => join2(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
|
|
132
|
-
return candidates;
|
|
133
|
-
}
|
|
134
|
-
async function resolveChromiumProfileLocation(inputPath) {
|
|
135
|
-
const expandedPath = expandHome(inputPath.trim());
|
|
136
|
-
if (!expandedPath) {
|
|
137
|
-
throw new Error("Profile path cannot be empty.");
|
|
138
|
-
}
|
|
139
|
-
if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
|
|
140
|
-
const directParent = dirname(expandedPath);
|
|
141
|
-
const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
|
|
142
|
-
const userDataDir = dirname(profileDir);
|
|
143
|
-
return {
|
|
144
|
-
userDataDir,
|
|
145
|
-
profileDir,
|
|
146
|
-
profileDirectory: basename(profileDir),
|
|
147
|
-
cookieDbPath: expandedPath,
|
|
148
|
-
localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (fileExists(expandedPath)) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
if (!directoryExists(expandedPath)) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`Could not find a Chromium profile at "${inputPath}".`
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
const directCookieDb = resolveCookieDbPath(expandedPath);
|
|
162
|
-
if (directCookieDb) {
|
|
163
|
-
const userDataDir = dirname(expandedPath);
|
|
164
|
-
return {
|
|
165
|
-
userDataDir,
|
|
166
|
-
profileDir: expandedPath,
|
|
167
|
-
profileDirectory: basename(expandedPath),
|
|
168
|
-
cookieDbPath: directCookieDb,
|
|
169
|
-
localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const localStatePath = join2(expandedPath, "Local State");
|
|
173
|
-
if (!fileExists(localStatePath)) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
const profileDirs = await selectProfileDirFromUserDataDir(expandedPath);
|
|
179
|
-
if (profileDirs.length === 0) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
`No Chromium profile with a Cookies database was found under "${inputPath}".`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
if (profileDirs.length > 1) {
|
|
185
|
-
const candidates = profileDirs.map((entry) => basename(entry)).join(", ");
|
|
186
|
-
throw new Error(
|
|
187
|
-
`"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
const selectedProfileDir = profileDirs[0];
|
|
191
|
-
const cookieDbPath = resolveCookieDbPath(selectedProfileDir);
|
|
192
|
-
if (!cookieDbPath) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`No Chromium Cookies database was found for "${inputPath}".`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
userDataDir: expandedPath,
|
|
199
|
-
profileDir: selectedProfileDir,
|
|
200
|
-
profileDirectory: basename(selectedProfileDir),
|
|
201
|
-
cookieDbPath,
|
|
202
|
-
localStatePath
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
function resolvePersistentChromiumLaunchProfile(inputPath) {
|
|
206
|
-
const expandedPath = expandHome(inputPath.trim());
|
|
207
|
-
if (!expandedPath) {
|
|
208
|
-
return {
|
|
209
|
-
userDataDir: inputPath
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
|
|
213
|
-
const directParent = dirname(expandedPath);
|
|
214
|
-
const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
|
|
215
|
-
return {
|
|
216
|
-
userDataDir: dirname(profileDir),
|
|
217
|
-
profileDirectory: basename(profileDir)
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists(join2(dirname(expandedPath), "Local State"))) {
|
|
221
|
-
return {
|
|
222
|
-
userDataDir: dirname(expandedPath),
|
|
223
|
-
profileDirectory: basename(expandedPath)
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
userDataDir: expandedPath
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function detectChromiumBrand(location) {
|
|
231
|
-
const normalizedPath = location.userDataDir.toLowerCase();
|
|
232
|
-
for (const candidate of CHROMIUM_BRANDS) {
|
|
233
|
-
if (candidate.match.every((fragment) => normalizedPath.includes(fragment))) {
|
|
234
|
-
return candidate.brand;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return DEFAULT_CHROMIUM_BRAND;
|
|
238
|
-
}
|
|
239
|
-
async function createSqliteSnapshot(dbPath) {
|
|
240
|
-
const snapshotDir = await mkdtemp(join2(tmpdir(), "opensteer-cookie-db-"));
|
|
241
|
-
const snapshotPath = join2(snapshotDir, "Cookies");
|
|
242
|
-
await copyFile(dbPath, snapshotPath);
|
|
243
|
-
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
244
|
-
const source = `${dbPath}${suffix}`;
|
|
245
|
-
if (!existsSync(source)) {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
await copyFile(source, `${snapshotPath}${suffix}`);
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
snapshotPath,
|
|
252
|
-
cleanup: async () => {
|
|
253
|
-
await rm(snapshotDir, { recursive: true, force: true });
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
async function querySqliteJson(dbPath, query) {
|
|
258
|
-
const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
|
|
259
|
-
encoding: "utf8",
|
|
260
|
-
maxBuffer: 64 * 1024 * 1024
|
|
261
|
-
});
|
|
262
|
-
const stdout = result.stdout;
|
|
263
|
-
const trimmed = stdout.trim();
|
|
264
|
-
if (!trimmed) {
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
return JSON.parse(trimmed);
|
|
268
|
-
}
|
|
269
|
-
function convertChromiumTimestampToUnixSeconds(value) {
|
|
270
|
-
if (!value || value === "0") {
|
|
271
|
-
return -1;
|
|
272
|
-
}
|
|
273
|
-
const micros = BigInt(value);
|
|
274
|
-
if (micros <= CHROMIUM_EPOCH_MICROS) {
|
|
275
|
-
return -1;
|
|
276
|
-
}
|
|
277
|
-
return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
|
|
278
|
-
}
|
|
279
|
-
function mapChromiumSameSite(value) {
|
|
280
|
-
if (value === 2) {
|
|
281
|
-
return "Strict";
|
|
282
|
-
}
|
|
283
|
-
if (value === 0) {
|
|
284
|
-
return "None";
|
|
285
|
-
}
|
|
286
|
-
return "Lax";
|
|
287
|
-
}
|
|
288
|
-
function stripChromiumPadding(buffer) {
|
|
289
|
-
const paddingLength = buffer[buffer.length - 1];
|
|
290
|
-
if (paddingLength <= 0 || paddingLength > AES_BLOCK_BYTES) {
|
|
291
|
-
return buffer;
|
|
292
|
-
}
|
|
293
|
-
return buffer.subarray(0, buffer.length - paddingLength);
|
|
294
|
-
}
|
|
295
|
-
function stripDomainHashPrefix(buffer, hostKey) {
|
|
296
|
-
if (buffer.length < 32) {
|
|
297
|
-
return buffer;
|
|
298
|
-
}
|
|
299
|
-
const domainHash = createHash("sha256").update(hostKey, "utf8").digest();
|
|
300
|
-
if (buffer.subarray(0, 32).equals(domainHash)) {
|
|
301
|
-
return buffer.subarray(32);
|
|
302
|
-
}
|
|
303
|
-
return buffer;
|
|
304
|
-
}
|
|
305
|
-
function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
|
|
306
|
-
const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
|
|
307
|
-
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
308
|
-
const decipher = createDecipheriv("aes-128-cbc", key, iv);
|
|
309
|
-
const plaintext = Buffer.concat([
|
|
310
|
-
decipher.update(ciphertext),
|
|
311
|
-
decipher.final()
|
|
312
|
-
]);
|
|
313
|
-
return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
|
|
314
|
-
"utf8"
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
function decryptChromiumAes256GcmValue(encryptedValue, key) {
|
|
318
|
-
const nonce = encryptedValue.subarray(3, 15);
|
|
319
|
-
const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
|
|
320
|
-
const authTag = encryptedValue.subarray(encryptedValue.length - 16);
|
|
321
|
-
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
|
322
|
-
decipher.setAuthTag(authTag);
|
|
323
|
-
return Buffer.concat([
|
|
324
|
-
decipher.update(ciphertext),
|
|
325
|
-
decipher.final()
|
|
326
|
-
]).toString("utf8");
|
|
327
|
-
}
|
|
328
|
-
async function dpapiUnprotect(buffer) {
|
|
329
|
-
const script = [
|
|
330
|
-
`$inputBytes = [Convert]::FromBase64String('${buffer.toString("base64")}')`,
|
|
331
|
-
"$plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(",
|
|
332
|
-
" $inputBytes,",
|
|
333
|
-
" $null,",
|
|
334
|
-
" [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
|
|
335
|
-
")",
|
|
336
|
-
"[Convert]::ToBase64String($plainBytes)"
|
|
337
|
-
].join("\n");
|
|
338
|
-
const { stdout } = await execFileAsync(
|
|
339
|
-
"powershell.exe",
|
|
340
|
-
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
341
|
-
{
|
|
342
|
-
encoding: "utf8",
|
|
343
|
-
maxBuffer: 8 * 1024 * 1024
|
|
344
|
-
}
|
|
345
|
-
);
|
|
346
|
-
return Buffer.from(stdout.trim(), "base64");
|
|
347
|
-
}
|
|
348
|
-
async function buildChromiumDecryptor(location) {
|
|
349
|
-
if (process.platform === "darwin") {
|
|
350
|
-
const brand = detectChromiumBrand(location);
|
|
351
|
-
const keychainStore = createKeychainStore();
|
|
352
|
-
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? null;
|
|
353
|
-
if (!password) {
|
|
354
|
-
throw new Error(
|
|
355
|
-
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
const key = pbkdf2Sync(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
|
|
359
|
-
return async (row) => decryptChromiumAes128CbcValue(
|
|
360
|
-
Buffer.from(row.encrypted_value || "", "hex"),
|
|
361
|
-
key,
|
|
362
|
-
row.host_key
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if (process.platform === "linux") {
|
|
366
|
-
const brand = detectChromiumBrand(location);
|
|
367
|
-
const keychainStore = createKeychainStore();
|
|
368
|
-
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
|
|
369
|
-
const key = pbkdf2Sync(
|
|
370
|
-
password || "peanuts",
|
|
371
|
-
KEY_SALT,
|
|
372
|
-
LINUX_KEY_ITERATIONS,
|
|
373
|
-
KEY_LENGTH,
|
|
374
|
-
"sha1"
|
|
375
|
-
);
|
|
376
|
-
return async (row) => decryptChromiumAes128CbcValue(
|
|
377
|
-
Buffer.from(row.encrypted_value || "", "hex"),
|
|
378
|
-
key,
|
|
379
|
-
row.host_key
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
if (process.platform === "win32") {
|
|
383
|
-
if (!location.localStatePath) {
|
|
384
|
-
throw new Error(
|
|
385
|
-
`Unable to locate Chromium Local State for profile: ${location.profileDir}`
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
const localState = JSON.parse(
|
|
389
|
-
await readFile(location.localStatePath, "utf8")
|
|
390
|
-
);
|
|
391
|
-
const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
|
|
392
|
-
if (!encryptedKeyBase64) {
|
|
393
|
-
throw new Error(
|
|
394
|
-
`Local State did not include os_crypt.encrypted_key for ${location.userDataDir}`
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
|
|
398
|
-
const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
|
|
399
|
-
return async (row) => {
|
|
400
|
-
const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
|
|
401
|
-
if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
|
|
402
|
-
const decrypted = await dpapiUnprotect(encryptedValue);
|
|
403
|
-
return decrypted.toString("utf8");
|
|
404
|
-
}
|
|
405
|
-
return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
throw new Error(
|
|
409
|
-
`Local Chromium cookie sync is not supported on ${process.platform}.`
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
function buildPlaywrightCookie(row, value) {
|
|
413
|
-
if (!row.name.trim()) {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
if (!row.host_key.trim()) {
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
|
|
420
|
-
if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
return {
|
|
424
|
-
name: row.name,
|
|
425
|
-
value,
|
|
426
|
-
domain: row.host_key,
|
|
427
|
-
path: row.path || "/",
|
|
428
|
-
expires,
|
|
429
|
-
httpOnly: row.is_httponly === 1,
|
|
430
|
-
secure: row.is_secure === 1,
|
|
431
|
-
sameSite: mapChromiumSameSite(row.samesite)
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
|
|
435
|
-
const location = await resolveChromiumProfileLocation(inputPath);
|
|
436
|
-
try {
|
|
437
|
-
return await loadCookiesFromSqlite(location);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
if (!isMissingSqliteBinary(error)) {
|
|
440
|
-
throw error;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return await loadCookiesFromBrowserSnapshot(location, options);
|
|
444
|
-
}
|
|
445
|
-
async function loadCookiesFromSqlite(location) {
|
|
446
|
-
const snapshot = await createSqliteSnapshot(location.cookieDbPath);
|
|
447
|
-
try {
|
|
448
|
-
const rows = await querySqliteJson(
|
|
449
|
-
snapshot.snapshotPath,
|
|
450
|
-
[
|
|
451
|
-
"SELECT",
|
|
452
|
-
" host_key,",
|
|
453
|
-
" name,",
|
|
454
|
-
" value,",
|
|
455
|
-
" hex(encrypted_value) AS encrypted_value,",
|
|
456
|
-
" path,",
|
|
457
|
-
" CAST(expires_utc AS TEXT) AS expires_utc,",
|
|
458
|
-
" is_secure,",
|
|
459
|
-
" is_httponly,",
|
|
460
|
-
" has_expires,",
|
|
461
|
-
" samesite",
|
|
462
|
-
"FROM cookies"
|
|
463
|
-
].join(" ")
|
|
464
|
-
);
|
|
465
|
-
const decryptValue = await buildChromiumDecryptor(location);
|
|
466
|
-
const cookies = [];
|
|
467
|
-
for (const row of rows) {
|
|
468
|
-
let value = row.value || "";
|
|
469
|
-
if (!value && row.encrypted_value) {
|
|
470
|
-
value = await decryptValue(row);
|
|
471
|
-
}
|
|
472
|
-
const cookie = buildPlaywrightCookie(row, value);
|
|
473
|
-
if (cookie) {
|
|
474
|
-
cookies.push(cookie);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return cookies;
|
|
478
|
-
} finally {
|
|
479
|
-
await snapshot.cleanup();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
483
|
-
const snapshotRootDir = await mkdtemp(join2(tmpdir(), "opensteer-profile-"));
|
|
484
|
-
const snapshotProfileDir = join2(
|
|
485
|
-
snapshotRootDir,
|
|
486
|
-
basename(location.profileDir)
|
|
487
|
-
);
|
|
488
|
-
let context = null;
|
|
489
|
-
try {
|
|
490
|
-
await cp(location.profileDir, snapshotProfileDir, {
|
|
491
|
-
recursive: true
|
|
492
|
-
});
|
|
493
|
-
if (location.localStatePath) {
|
|
494
|
-
await copyFile(location.localStatePath, join2(snapshotRootDir, "Local State"));
|
|
495
|
-
}
|
|
496
|
-
const brand = detectChromiumBrand(location);
|
|
497
|
-
const args = [`--profile-directory=${basename(snapshotProfileDir)}`];
|
|
498
|
-
context = await chromium.launchPersistentContext(snapshotRootDir, {
|
|
499
|
-
channel: brand.playwrightChannel,
|
|
500
|
-
headless: options.headless ?? true,
|
|
501
|
-
timeout: options.timeout ?? 12e4,
|
|
502
|
-
args
|
|
503
|
-
});
|
|
504
|
-
return await context.cookies();
|
|
505
|
-
} finally {
|
|
506
|
-
await context?.close().catch(() => void 0);
|
|
507
|
-
await rm(snapshotRootDir, { recursive: true, force: true });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function isMissingSqliteBinary(error) {
|
|
511
|
-
return Boolean(
|
|
512
|
-
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
25
|
// src/navigation.ts
|
|
517
26
|
var DEFAULT_TIMEOUT = 3e4;
|
|
518
27
|
var DEFAULT_SETTLE_MS = 750;
|
|
@@ -5294,7 +4803,7 @@ async function closeTab(context, activePage, index) {
|
|
|
5294
4803
|
}
|
|
5295
4804
|
|
|
5296
4805
|
// src/actions/cookies.ts
|
|
5297
|
-
import { readFile
|
|
4806
|
+
import { readFile, writeFile } from "fs/promises";
|
|
5298
4807
|
async function getCookies(context, url) {
|
|
5299
4808
|
return context.cookies(url ? [url] : void 0);
|
|
5300
4809
|
}
|
|
@@ -5309,7 +4818,7 @@ async function exportCookies(context, filePath, url) {
|
|
|
5309
4818
|
await writeFile(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
5310
4819
|
}
|
|
5311
4820
|
async function importCookies(context, filePath) {
|
|
5312
|
-
const raw = await
|
|
4821
|
+
const raw = await readFile(filePath, "utf-8");
|
|
5313
4822
|
const cookies = JSON.parse(raw);
|
|
5314
4823
|
await context.addCookies(cookies);
|
|
5315
4824
|
}
|
|
@@ -8002,11 +7511,17 @@ function sleep4(ms) {
|
|
|
8002
7511
|
}
|
|
8003
7512
|
|
|
8004
7513
|
// src/opensteer.ts
|
|
8005
|
-
import { createHash
|
|
7514
|
+
import { createHash, randomUUID } from "crypto";
|
|
8006
7515
|
|
|
8007
7516
|
// src/browser/pool.ts
|
|
7517
|
+
import { spawn } from "child_process";
|
|
7518
|
+
import { existsSync } from "fs";
|
|
7519
|
+
import { copyFile, cp, mkdir, mkdtemp, rm } from "fs/promises";
|
|
7520
|
+
import { createServer } from "net";
|
|
7521
|
+
import { tmpdir } from "os";
|
|
7522
|
+
import { join } from "path";
|
|
8008
7523
|
import {
|
|
8009
|
-
chromium
|
|
7524
|
+
chromium
|
|
8010
7525
|
} from "playwright";
|
|
8011
7526
|
|
|
8012
7527
|
// src/browser/cdp-proxy.ts
|
|
@@ -8349,132 +7864,184 @@ function errorMessage(error) {
|
|
|
8349
7864
|
// src/browser/pool.ts
|
|
8350
7865
|
var BrowserPool = class {
|
|
8351
7866
|
browser = null;
|
|
8352
|
-
persistentContext = null;
|
|
8353
7867
|
cdpProxy = null;
|
|
7868
|
+
launchedProcess = null;
|
|
7869
|
+
tempUserDataDir = null;
|
|
8354
7870
|
defaults;
|
|
8355
7871
|
constructor(defaults = {}) {
|
|
8356
7872
|
this.defaults = defaults;
|
|
8357
7873
|
}
|
|
8358
7874
|
async launch(options = {}) {
|
|
8359
|
-
if (this.browser || this.cdpProxy) {
|
|
7875
|
+
if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
|
|
8360
7876
|
await this.close();
|
|
8361
7877
|
}
|
|
8362
|
-
const
|
|
8363
|
-
const
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
|
|
7878
|
+
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
7879
|
+
const cdpUrl = options.cdpUrl ?? this.defaults.cdpUrl;
|
|
7880
|
+
const userDataDir = options.userDataDir ?? this.defaults.userDataDir;
|
|
7881
|
+
const profileDirectory = options.profileDirectory ?? this.defaults.profileDirectory;
|
|
7882
|
+
const executablePath = options.executablePath ?? this.defaults.executablePath;
|
|
7883
|
+
if (cdpUrl) {
|
|
7884
|
+
if (mode === "real") {
|
|
7885
|
+
throw new Error(
|
|
7886
|
+
'cdpUrl cannot be combined with mode "real". Use one browser launch path at a time.'
|
|
7887
|
+
);
|
|
7888
|
+
}
|
|
7889
|
+
if (userDataDir || profileDirectory) {
|
|
7890
|
+
throw new Error(
|
|
7891
|
+
"userDataDir/profileDirectory cannot be combined with cdpUrl."
|
|
7892
|
+
);
|
|
7893
|
+
}
|
|
7894
|
+
if (options.context && Object.keys(options.context).length > 0) {
|
|
7895
|
+
throw new Error(
|
|
7896
|
+
"context launch options are not supported when attaching over CDP."
|
|
7897
|
+
);
|
|
7898
|
+
}
|
|
7899
|
+
return this.connectToRunning(cdpUrl, options.timeout);
|
|
8367
7900
|
}
|
|
8368
|
-
if (
|
|
8369
|
-
|
|
7901
|
+
if (mode !== "real" && (userDataDir || profileDirectory)) {
|
|
7902
|
+
throw new Error(
|
|
7903
|
+
'userDataDir/profileDirectory require mode "real".'
|
|
7904
|
+
);
|
|
8370
7905
|
}
|
|
8371
|
-
if (
|
|
8372
|
-
|
|
7906
|
+
if (mode === "real") {
|
|
7907
|
+
if (options.context && Object.keys(options.context).length > 0) {
|
|
7908
|
+
throw new Error(
|
|
7909
|
+
"context launch options are not supported for real-browser mode."
|
|
7910
|
+
);
|
|
7911
|
+
}
|
|
7912
|
+
return this.launchOwnedRealBrowser({
|
|
7913
|
+
...options,
|
|
7914
|
+
executablePath,
|
|
7915
|
+
userDataDir,
|
|
7916
|
+
profileDirectory
|
|
7917
|
+
});
|
|
8373
7918
|
}
|
|
8374
7919
|
return this.launchSandbox(options);
|
|
8375
7920
|
}
|
|
8376
7921
|
async close() {
|
|
8377
7922
|
const browser = this.browser;
|
|
8378
|
-
const
|
|
7923
|
+
const cdpProxy = this.cdpProxy;
|
|
7924
|
+
const launchedProcess = this.launchedProcess;
|
|
7925
|
+
const tempUserDataDir = this.tempUserDataDir;
|
|
8379
7926
|
this.browser = null;
|
|
8380
|
-
this.
|
|
7927
|
+
this.cdpProxy = null;
|
|
7928
|
+
this.launchedProcess = null;
|
|
7929
|
+
this.tempUserDataDir = null;
|
|
8381
7930
|
try {
|
|
8382
|
-
if (
|
|
8383
|
-
await
|
|
8384
|
-
} else if (browser) {
|
|
8385
|
-
await browser.close();
|
|
7931
|
+
if (browser) {
|
|
7932
|
+
await browser.close().catch(() => void 0);
|
|
8386
7933
|
}
|
|
8387
7934
|
} finally {
|
|
8388
|
-
|
|
8389
|
-
|
|
7935
|
+
cdpProxy?.close();
|
|
7936
|
+
await killProcessTree(launchedProcess);
|
|
7937
|
+
if (tempUserDataDir) {
|
|
7938
|
+
await rm(tempUserDataDir, {
|
|
7939
|
+
recursive: true,
|
|
7940
|
+
force: true
|
|
7941
|
+
}).catch(() => void 0);
|
|
7942
|
+
}
|
|
8390
7943
|
}
|
|
8391
7944
|
}
|
|
8392
|
-
async connectToRunning(
|
|
8393
|
-
this.cdpProxy?.close();
|
|
8394
|
-
this.cdpProxy = null;
|
|
7945
|
+
async connectToRunning(cdpUrl, timeout) {
|
|
8395
7946
|
let browser = null;
|
|
7947
|
+
let cdpProxy = null;
|
|
8396
7948
|
try {
|
|
8397
|
-
const { browserWsUrl, targets } = await discoverTargets(
|
|
7949
|
+
const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
|
|
8398
7950
|
if (targets.length === 0) {
|
|
8399
7951
|
throw new Error(
|
|
8400
7952
|
"No page targets found. Is the browser running with an open window?"
|
|
8401
7953
|
);
|
|
8402
7954
|
}
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
browser = await chromium2.connectOverCDP(proxyWsUrl, {
|
|
7955
|
+
cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
|
|
7956
|
+
const proxyWsUrl = await cdpProxy.start();
|
|
7957
|
+
browser = await chromium.connectOverCDP(proxyWsUrl, {
|
|
8407
7958
|
timeout: timeout ?? 3e4
|
|
8408
7959
|
});
|
|
8409
7960
|
this.browser = browser;
|
|
8410
|
-
this.
|
|
8411
|
-
const
|
|
8412
|
-
if (contexts.length === 0) {
|
|
8413
|
-
throw new Error(
|
|
8414
|
-
"Connection succeeded but no browser contexts found. Is the browser running with an open window?"
|
|
8415
|
-
);
|
|
8416
|
-
}
|
|
8417
|
-
const context = contexts[0];
|
|
8418
|
-
const pages = context.pages();
|
|
8419
|
-
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
7961
|
+
this.cdpProxy = cdpProxy;
|
|
7962
|
+
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
8420
7963
|
return { browser, context, page, isExternal: true };
|
|
8421
7964
|
} catch (error) {
|
|
8422
7965
|
if (browser) {
|
|
8423
7966
|
await browser.close().catch(() => void 0);
|
|
8424
7967
|
}
|
|
7968
|
+
cdpProxy?.close();
|
|
8425
7969
|
this.browser = null;
|
|
8426
|
-
this.persistentContext = null;
|
|
8427
|
-
this.cdpProxy?.close();
|
|
8428
7970
|
this.cdpProxy = null;
|
|
8429
7971
|
throw error;
|
|
8430
7972
|
}
|
|
8431
7973
|
}
|
|
8432
|
-
async
|
|
8433
|
-
const
|
|
8434
|
-
const
|
|
8435
|
-
if (
|
|
8436
|
-
|
|
7974
|
+
async launchOwnedRealBrowser(options) {
|
|
7975
|
+
const chromePaths = detectChromePaths();
|
|
7976
|
+
const executablePath = options.executablePath ?? chromePaths.executable ?? void 0;
|
|
7977
|
+
if (!executablePath) {
|
|
7978
|
+
throw new Error(
|
|
7979
|
+
"Chrome was not found. Set browser.executablePath or install Chrome in a supported location."
|
|
7980
|
+
);
|
|
8437
7981
|
}
|
|
8438
|
-
const
|
|
8439
|
-
|
|
8440
|
-
{
|
|
8441
|
-
channel,
|
|
8442
|
-
headless: options.headless ?? this.defaults.headless,
|
|
8443
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
8444
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8445
|
-
timeout: options.timeout,
|
|
8446
|
-
...options.context || {},
|
|
8447
|
-
args
|
|
8448
|
-
}
|
|
7982
|
+
const sourceUserDataDir = expandHome(
|
|
7983
|
+
options.userDataDir ?? chromePaths.defaultUserDataDir
|
|
8449
7984
|
);
|
|
8450
|
-
const
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8467
|
-
timeout: options.timeout
|
|
7985
|
+
const profileDirectory = options.profileDirectory ?? "Default";
|
|
7986
|
+
const tempUserDataDir = await cloneProfileToTempDir(
|
|
7987
|
+
sourceUserDataDir,
|
|
7988
|
+
profileDirectory
|
|
7989
|
+
);
|
|
7990
|
+
const debugPort = await reserveDebugPort();
|
|
7991
|
+
const headless = resolveLaunchHeadless(
|
|
7992
|
+
"real",
|
|
7993
|
+
options.headless,
|
|
7994
|
+
this.defaults.headless
|
|
7995
|
+
);
|
|
7996
|
+
const launchArgs = buildRealBrowserLaunchArgs({
|
|
7997
|
+
userDataDir: tempUserDataDir,
|
|
7998
|
+
profileDirectory,
|
|
7999
|
+
debugPort,
|
|
8000
|
+
headless
|
|
8468
8001
|
});
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8002
|
+
const processHandle = spawn(executablePath, launchArgs, {
|
|
8003
|
+
detached: process.platform !== "win32",
|
|
8004
|
+
stdio: "ignore"
|
|
8005
|
+
});
|
|
8006
|
+
processHandle.unref();
|
|
8007
|
+
let browser = null;
|
|
8008
|
+
try {
|
|
8009
|
+
const wsUrl = await resolveCdpWebSocketUrl(
|
|
8010
|
+
`http://127.0.0.1:${debugPort}`,
|
|
8011
|
+
options.timeout ?? 3e4
|
|
8012
|
+
);
|
|
8013
|
+
browser = await chromium.connectOverCDP(wsUrl, {
|
|
8014
|
+
timeout: options.timeout ?? 3e4
|
|
8015
|
+
});
|
|
8016
|
+
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
8017
|
+
browser,
|
|
8018
|
+
{
|
|
8019
|
+
headless,
|
|
8020
|
+
initialUrl: options.initialUrl,
|
|
8021
|
+
timeoutMs: options.timeout ?? 3e4
|
|
8022
|
+
}
|
|
8023
|
+
);
|
|
8024
|
+
this.browser = browser;
|
|
8025
|
+
this.launchedProcess = processHandle;
|
|
8026
|
+
this.tempUserDataDir = tempUserDataDir;
|
|
8027
|
+
return { browser, context, page, isExternal: false };
|
|
8028
|
+
} catch (error) {
|
|
8029
|
+
await browser?.close().catch(() => void 0);
|
|
8030
|
+
await killProcessTree(processHandle);
|
|
8031
|
+
await rm(tempUserDataDir, {
|
|
8032
|
+
recursive: true,
|
|
8033
|
+
force: true
|
|
8034
|
+
}).catch(() => void 0);
|
|
8035
|
+
throw error;
|
|
8036
|
+
}
|
|
8474
8037
|
}
|
|
8475
8038
|
async launchSandbox(options) {
|
|
8476
|
-
const browser = await
|
|
8477
|
-
headless:
|
|
8039
|
+
const browser = await chromium.launch({
|
|
8040
|
+
headless: resolveLaunchHeadless(
|
|
8041
|
+
"chromium",
|
|
8042
|
+
options.headless,
|
|
8043
|
+
this.defaults.headless
|
|
8044
|
+
),
|
|
8478
8045
|
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
8479
8046
|
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8480
8047
|
timeout: options.timeout
|
|
@@ -8482,10 +8049,268 @@ var BrowserPool = class {
|
|
|
8482
8049
|
const context = await browser.newContext(options.context || {});
|
|
8483
8050
|
const page = await context.newPage();
|
|
8484
8051
|
this.browser = browser;
|
|
8485
|
-
this.persistentContext = null;
|
|
8486
8052
|
return { browser, context, page, isExternal: false };
|
|
8487
8053
|
}
|
|
8488
8054
|
};
|
|
8055
|
+
async function pickBrowserContextAndPage(browser) {
|
|
8056
|
+
const context = getPrimaryBrowserContext(browser);
|
|
8057
|
+
const pages = context.pages();
|
|
8058
|
+
const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
|
|
8059
|
+
return { context, page };
|
|
8060
|
+
}
|
|
8061
|
+
function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
8062
|
+
if (requestedHeadless !== void 0) {
|
|
8063
|
+
return requestedHeadless;
|
|
8064
|
+
}
|
|
8065
|
+
if (defaultHeadless !== void 0) {
|
|
8066
|
+
return defaultHeadless;
|
|
8067
|
+
}
|
|
8068
|
+
return mode === "real";
|
|
8069
|
+
}
|
|
8070
|
+
async function createOwnedBrowserContextAndPage(browser, options) {
|
|
8071
|
+
const context = getPrimaryBrowserContext(browser);
|
|
8072
|
+
const page = await createOwnedBrowserPage(browser, context, options);
|
|
8073
|
+
return { context, page };
|
|
8074
|
+
}
|
|
8075
|
+
async function createOwnedBrowserPage(browser, context, options) {
|
|
8076
|
+
const targetUrl = options.initialUrl ?? "about:blank";
|
|
8077
|
+
const existingPages = new Set(context.pages());
|
|
8078
|
+
const browserSession = await browser.newBrowserCDPSession();
|
|
8079
|
+
try {
|
|
8080
|
+
const { targetId } = await browserSession.send("Target.createTarget", {
|
|
8081
|
+
url: targetUrl,
|
|
8082
|
+
newWindow: !options.headless
|
|
8083
|
+
});
|
|
8084
|
+
await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
|
|
8085
|
+
const page = await waitForOwnedBrowserPage(context, {
|
|
8086
|
+
existingPages,
|
|
8087
|
+
targetUrl,
|
|
8088
|
+
timeoutMs: options.timeoutMs
|
|
8089
|
+
});
|
|
8090
|
+
if (targetUrl !== "about:blank") {
|
|
8091
|
+
await page.waitForLoadState("domcontentloaded", {
|
|
8092
|
+
timeout: options.timeoutMs
|
|
8093
|
+
});
|
|
8094
|
+
}
|
|
8095
|
+
await closeDisposableStartupTargets(browserSession, targetId);
|
|
8096
|
+
return page;
|
|
8097
|
+
} finally {
|
|
8098
|
+
await browserSession.detach().catch(() => void 0);
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
|
|
8102
|
+
const response = await browserSession.send("Target.getTargets").catch(() => null);
|
|
8103
|
+
if (!response) {
|
|
8104
|
+
return;
|
|
8105
|
+
}
|
|
8106
|
+
for (const targetInfo of response.targetInfos) {
|
|
8107
|
+
if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
|
|
8108
|
+
continue;
|
|
8109
|
+
}
|
|
8110
|
+
await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
8113
|
+
async function waitForOwnedBrowserPage(context, options) {
|
|
8114
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
8115
|
+
let fallbackPage = null;
|
|
8116
|
+
while (Date.now() < deadline) {
|
|
8117
|
+
for (const candidate of context.pages()) {
|
|
8118
|
+
if (options.existingPages.has(candidate)) {
|
|
8119
|
+
continue;
|
|
8120
|
+
}
|
|
8121
|
+
const url = candidate.url();
|
|
8122
|
+
if (!isInspectablePageUrl2(url)) {
|
|
8123
|
+
continue;
|
|
8124
|
+
}
|
|
8125
|
+
fallbackPage ??= candidate;
|
|
8126
|
+
if (options.targetUrl === "about:blank") {
|
|
8127
|
+
return candidate;
|
|
8128
|
+
}
|
|
8129
|
+
if (pageLooselyMatchesUrl(url, options.targetUrl)) {
|
|
8130
|
+
return candidate;
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
8133
|
+
await sleep5(100);
|
|
8134
|
+
}
|
|
8135
|
+
if (fallbackPage) {
|
|
8136
|
+
return fallbackPage;
|
|
8137
|
+
}
|
|
8138
|
+
throw new Error(
|
|
8139
|
+
`Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
|
|
8140
|
+
);
|
|
8141
|
+
}
|
|
8142
|
+
function getPrimaryBrowserContext(browser) {
|
|
8143
|
+
const contexts = browser.contexts();
|
|
8144
|
+
if (contexts.length === 0) {
|
|
8145
|
+
throw new Error(
|
|
8146
|
+
"Connection succeeded but no browser contexts were exposed."
|
|
8147
|
+
);
|
|
8148
|
+
}
|
|
8149
|
+
return contexts[0];
|
|
8150
|
+
}
|
|
8151
|
+
function isInspectablePageUrl2(url) {
|
|
8152
|
+
return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
|
|
8153
|
+
}
|
|
8154
|
+
function isDisposableStartupPageUrl(url) {
|
|
8155
|
+
return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
|
|
8156
|
+
}
|
|
8157
|
+
function pageLooselyMatchesUrl(currentUrl, initialUrl) {
|
|
8158
|
+
try {
|
|
8159
|
+
const current = new URL(currentUrl);
|
|
8160
|
+
const requested = new URL(initialUrl);
|
|
8161
|
+
if (current.href === requested.href) {
|
|
8162
|
+
return true;
|
|
8163
|
+
}
|
|
8164
|
+
return current.hostname === requested.hostname && current.pathname === requested.pathname;
|
|
8165
|
+
} catch {
|
|
8166
|
+
return currentUrl === initialUrl;
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
8170
|
+
let parsed;
|
|
8171
|
+
try {
|
|
8172
|
+
parsed = new URL(cdpUrl);
|
|
8173
|
+
} catch {
|
|
8174
|
+
throw new Error(
|
|
8175
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
8176
|
+
);
|
|
8177
|
+
}
|
|
8178
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
8179
|
+
return parsed;
|
|
8180
|
+
}
|
|
8181
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
8182
|
+
throw new Error(
|
|
8183
|
+
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
8184
|
+
);
|
|
8185
|
+
}
|
|
8186
|
+
const normalized = new URL(parsed.toString());
|
|
8187
|
+
normalized.pathname = "/json/version";
|
|
8188
|
+
normalized.search = "";
|
|
8189
|
+
normalized.hash = "";
|
|
8190
|
+
return normalized;
|
|
8191
|
+
}
|
|
8192
|
+
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
8193
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
8194
|
+
return cdpUrl;
|
|
8195
|
+
}
|
|
8196
|
+
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
8197
|
+
const deadline = Date.now() + timeoutMs;
|
|
8198
|
+
let lastError = "CDP discovery did not respond.";
|
|
8199
|
+
while (Date.now() < deadline) {
|
|
8200
|
+
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
8201
|
+
try {
|
|
8202
|
+
const response = await fetch(versionUrl, {
|
|
8203
|
+
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
8204
|
+
});
|
|
8205
|
+
if (!response.ok) {
|
|
8206
|
+
lastError = `${response.status} ${response.statusText}`;
|
|
8207
|
+
} else {
|
|
8208
|
+
const payload = await response.json();
|
|
8209
|
+
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
8210
|
+
if (wsUrl && wsUrl.trim()) {
|
|
8211
|
+
return wsUrl;
|
|
8212
|
+
}
|
|
8213
|
+
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
8214
|
+
}
|
|
8215
|
+
} catch (error) {
|
|
8216
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
8217
|
+
}
|
|
8218
|
+
await sleep5(100);
|
|
8219
|
+
}
|
|
8220
|
+
throw new Error(
|
|
8221
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
8222
|
+
);
|
|
8223
|
+
}
|
|
8224
|
+
async function reserveDebugPort() {
|
|
8225
|
+
return await new Promise((resolve, reject) => {
|
|
8226
|
+
const server = createServer();
|
|
8227
|
+
server.unref();
|
|
8228
|
+
server.on("error", reject);
|
|
8229
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8230
|
+
const address = server.address();
|
|
8231
|
+
if (!address || typeof address === "string") {
|
|
8232
|
+
server.close();
|
|
8233
|
+
reject(new Error("Failed to reserve a local debug port."));
|
|
8234
|
+
return;
|
|
8235
|
+
}
|
|
8236
|
+
server.close((error) => {
|
|
8237
|
+
if (error) {
|
|
8238
|
+
reject(error);
|
|
8239
|
+
return;
|
|
8240
|
+
}
|
|
8241
|
+
resolve(address.port);
|
|
8242
|
+
});
|
|
8243
|
+
});
|
|
8244
|
+
});
|
|
8245
|
+
}
|
|
8246
|
+
async function cloneProfileToTempDir(userDataDir, profileDirectory) {
|
|
8247
|
+
const resolvedUserDataDir = expandHome(userDataDir);
|
|
8248
|
+
const tempUserDataDir = await mkdtemp(
|
|
8249
|
+
join(tmpdir(), "opensteer-real-browser-")
|
|
8250
|
+
);
|
|
8251
|
+
const sourceProfileDir = join(resolvedUserDataDir, profileDirectory);
|
|
8252
|
+
const targetProfileDir = join(tempUserDataDir, profileDirectory);
|
|
8253
|
+
if (existsSync(sourceProfileDir)) {
|
|
8254
|
+
await cp(sourceProfileDir, targetProfileDir, {
|
|
8255
|
+
recursive: true
|
|
8256
|
+
});
|
|
8257
|
+
} else {
|
|
8258
|
+
await mkdir(targetProfileDir, {
|
|
8259
|
+
recursive: true
|
|
8260
|
+
});
|
|
8261
|
+
}
|
|
8262
|
+
const localStatePath = join(resolvedUserDataDir, "Local State");
|
|
8263
|
+
if (existsSync(localStatePath)) {
|
|
8264
|
+
await copyFile(localStatePath, join(tempUserDataDir, "Local State"));
|
|
8265
|
+
}
|
|
8266
|
+
return tempUserDataDir;
|
|
8267
|
+
}
|
|
8268
|
+
function buildRealBrowserLaunchArgs(options) {
|
|
8269
|
+
const args = [
|
|
8270
|
+
`--user-data-dir=${options.userDataDir}`,
|
|
8271
|
+
`--profile-directory=${options.profileDirectory}`,
|
|
8272
|
+
`--remote-debugging-port=${options.debugPort}`,
|
|
8273
|
+
"--no-first-run",
|
|
8274
|
+
"--no-default-browser-check",
|
|
8275
|
+
"--disable-background-networking",
|
|
8276
|
+
"--disable-sync",
|
|
8277
|
+
"--disable-popup-blocking"
|
|
8278
|
+
];
|
|
8279
|
+
if (options.headless) {
|
|
8280
|
+
args.push("--headless=new");
|
|
8281
|
+
}
|
|
8282
|
+
return args;
|
|
8283
|
+
}
|
|
8284
|
+
async function killProcessTree(processHandle) {
|
|
8285
|
+
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
8286
|
+
return;
|
|
8287
|
+
}
|
|
8288
|
+
if (process.platform === "win32") {
|
|
8289
|
+
await new Promise((resolve) => {
|
|
8290
|
+
const killer = spawn(
|
|
8291
|
+
"taskkill",
|
|
8292
|
+
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
8293
|
+
{
|
|
8294
|
+
stdio: "ignore"
|
|
8295
|
+
}
|
|
8296
|
+
);
|
|
8297
|
+
killer.on("error", () => resolve());
|
|
8298
|
+
killer.on("exit", () => resolve());
|
|
8299
|
+
});
|
|
8300
|
+
return;
|
|
8301
|
+
}
|
|
8302
|
+
try {
|
|
8303
|
+
process.kill(-processHandle.pid, "SIGKILL");
|
|
8304
|
+
} catch {
|
|
8305
|
+
try {
|
|
8306
|
+
processHandle.kill("SIGKILL");
|
|
8307
|
+
} catch {
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
8311
|
+
async function sleep5(ms) {
|
|
8312
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
8313
|
+
}
|
|
8489
8314
|
|
|
8490
8315
|
// src/action-wait.ts
|
|
8491
8316
|
var ROBUST_PROFILE = {
|
|
@@ -8680,7 +8505,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8680
8505
|
this.idleSince = 0;
|
|
8681
8506
|
}
|
|
8682
8507
|
const remaining = Math.max(1, options.deadline - now);
|
|
8683
|
-
await
|
|
8508
|
+
await sleep6(Math.min(NETWORK_POLL_MS, remaining));
|
|
8684
8509
|
}
|
|
8685
8510
|
}
|
|
8686
8511
|
handleRequestStarted = (request) => {
|
|
@@ -8725,7 +8550,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8725
8550
|
return false;
|
|
8726
8551
|
}
|
|
8727
8552
|
};
|
|
8728
|
-
async function
|
|
8553
|
+
async function sleep6(ms) {
|
|
8729
8554
|
await new Promise((resolve) => {
|
|
8730
8555
|
setTimeout(resolve, ms);
|
|
8731
8556
|
});
|
|
@@ -10416,9 +10241,11 @@ var Opensteer = class _Opensteer {
|
|
|
10416
10241
|
}
|
|
10417
10242
|
const session = await this.pool.launch({
|
|
10418
10243
|
...options,
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10244
|
+
mode: options.mode ?? this.config.browser?.mode,
|
|
10245
|
+
cdpUrl: options.cdpUrl ?? this.config.browser?.cdpUrl,
|
|
10246
|
+
userDataDir: options.userDataDir ?? this.config.browser?.userDataDir,
|
|
10247
|
+
profileDirectory: options.profileDirectory ?? this.config.browser?.profileDirectory,
|
|
10248
|
+
executablePath: options.executablePath ?? this.config.browser?.executablePath
|
|
10422
10249
|
});
|
|
10423
10250
|
this.browser = session.browser;
|
|
10424
10251
|
this.contextRef = session.context;
|
|
@@ -10449,6 +10276,32 @@ var Opensteer = class _Opensteer {
|
|
|
10449
10276
|
instance.snapshotCache = null;
|
|
10450
10277
|
return instance;
|
|
10451
10278
|
}
|
|
10279
|
+
static listLocalProfiles(userDataDir) {
|
|
10280
|
+
return listLocalChromeProfiles(userDataDir);
|
|
10281
|
+
}
|
|
10282
|
+
static fromSystemChrome(browser = {}, config = {}) {
|
|
10283
|
+
const chromePaths = detectChromePaths();
|
|
10284
|
+
const executablePath = browser.executablePath ?? config.browser?.executablePath ?? chromePaths.executable ?? void 0;
|
|
10285
|
+
if (!executablePath) {
|
|
10286
|
+
throw new Error(
|
|
10287
|
+
"Chrome was not found. Pass executablePath explicitly or install Chrome in a supported location."
|
|
10288
|
+
);
|
|
10289
|
+
}
|
|
10290
|
+
const userDataDir = browser.userDataDir ?? config.browser?.userDataDir ?? chromePaths.defaultUserDataDir;
|
|
10291
|
+
const autoDetectedProfiles = listLocalChromeProfiles(userDataDir);
|
|
10292
|
+
const profileDirectory = browser.profileDirectory ?? config.browser?.profileDirectory ?? autoDetectedProfiles[0]?.directory ?? "Default";
|
|
10293
|
+
return new _Opensteer({
|
|
10294
|
+
...config,
|
|
10295
|
+
browser: {
|
|
10296
|
+
...config.browser || {},
|
|
10297
|
+
mode: "real",
|
|
10298
|
+
headless: browser.headless ?? config.browser?.headless ?? true,
|
|
10299
|
+
executablePath,
|
|
10300
|
+
userDataDir,
|
|
10301
|
+
profileDirectory
|
|
10302
|
+
}
|
|
10303
|
+
});
|
|
10304
|
+
}
|
|
10452
10305
|
async close() {
|
|
10453
10306
|
this.snapshotCache = null;
|
|
10454
10307
|
if (this.cloud) {
|
|
@@ -12481,7 +12334,7 @@ var Opensteer = class _Opensteer {
|
|
|
12481
12334
|
}
|
|
12482
12335
|
resolveStorageKey(description) {
|
|
12483
12336
|
if (!description) return null;
|
|
12484
|
-
return
|
|
12337
|
+
return createHash("sha256").update(description).digest("hex").slice(0, 16);
|
|
12485
12338
|
}
|
|
12486
12339
|
normalizePath(path3) {
|
|
12487
12340
|
return sanitizeElementPath(path3);
|
|
@@ -12553,7 +12406,7 @@ function normalizeExtractSource(source) {
|
|
|
12553
12406
|
}
|
|
12554
12407
|
function computeSchemaHash(schema) {
|
|
12555
12408
|
const stable = stableStringify(schema);
|
|
12556
|
-
return
|
|
12409
|
+
return createHash("sha256").update(stable).digest("hex");
|
|
12557
12410
|
}
|
|
12558
12411
|
function buildPathMap(fields) {
|
|
12559
12412
|
const out = {};
|
|
@@ -12813,8 +12666,6 @@ function buildLocalRunId(namespace) {
|
|
|
12813
12666
|
}
|
|
12814
12667
|
|
|
12815
12668
|
export {
|
|
12816
|
-
expandHome,
|
|
12817
|
-
loadCookiesFromLocalProfileDir,
|
|
12818
12669
|
waitForVisualStability,
|
|
12819
12670
|
createEmptyRegistry,
|
|
12820
12671
|
LocalSelectorStorage,
|