browser-pilot 0.0.15 → 0.0.16

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/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(src_exports, {
35
35
  BatchExecutor: () => BatchExecutor,
36
36
  Browser: () => Browser,
37
37
  BrowserBaseProvider: () => BrowserBaseProvider,
38
+ BrowserEndpointResolutionError: () => BrowserEndpointResolutionError,
38
39
  BrowserlessProvider: () => BrowserlessProvider,
39
40
  CDPError: () => CDPError,
40
41
  ElementNotFoundError: () => ElementNotFoundError,
@@ -46,12 +47,14 @@ __export(src_exports, {
46
47
  Tracer: () => Tracer,
47
48
  addBatchToPage: () => addBatchToPage,
48
49
  bufferToBase64: () => bufferToBase64,
50
+ buildLocalBrowserScanTargets: () => buildLocalBrowserScanTargets,
49
51
  calculateRMS: () => calculateRMS,
50
52
  connect: () => connect,
51
53
  createCDPClient: () => createCDPClient,
52
54
  createProvider: () => createProvider,
53
55
  devices: () => devices,
54
56
  disableTracing: () => disableTracing,
57
+ discoverLocalBrowsers: () => discoverLocalBrowsers,
55
58
  discoverTargets: () => discoverTargets,
56
59
  enableTracing: () => enableTracing,
57
60
  generateSilence: () => generateSilence,
@@ -61,8 +64,11 @@ __export(src_exports, {
61
64
  getTracer: () => getTracer,
62
65
  grantAudioPermissions: () => grantAudioPermissions,
63
66
  isTranscriptionAvailable: () => isTranscriptionAvailable,
67
+ parseDevToolsActivePortFile: () => parseDevToolsActivePortFile,
64
68
  parseWavHeader: () => parseWavHeader,
65
69
  pcmToWav: () => pcmToWav,
70
+ resolveBrowserEndpoint: () => resolveBrowserEndpoint,
71
+ resolveChromeUserDataDirs: () => resolveChromeUserDataDirs,
66
72
  transcribe: () => transcribe,
67
73
  validateSteps: () => validateSteps,
68
74
  waitForAnyElement: () => waitForAnyElement,
@@ -942,7 +948,9 @@ function buildTraceSummaries(events) {
942
948
  };
943
949
  }
944
950
  function summarizeWs(events) {
945
- const relevant = events.filter((event) => event.channel === "ws" || event.event.startsWith("ws."));
951
+ const relevant = events.filter(
952
+ (event) => event.channel === "ws" || event.event.startsWith("ws.")
953
+ );
946
954
  const connections = /* @__PURE__ */ new Map();
947
955
  for (const event of relevant) {
948
956
  const id = event.connectionId ?? event.requestId ?? event.traceId;
@@ -972,7 +980,7 @@ function summarizeWs(events) {
972
980
  }
973
981
  const values = [...connections.values()];
974
982
  const reconnects = values.reduce((count, connection) => {
975
- return connection.closedAt && !connection.createdAt ? count : count;
983
+ return connection.closedAt && !connection.createdAt ? count + 1 : count;
976
984
  }, 0);
977
985
  return {
978
986
  view: "ws",
@@ -1225,6 +1233,31 @@ function frameToStep(frame) {
1225
1233
  }
1226
1234
  }
1227
1235
 
1236
+ // src/trace/model.ts
1237
+ function createTraceId(prefix = "evt") {
1238
+ const random = Math.random().toString(36).slice(2, 10);
1239
+ return `${prefix}-${Date.now().toString(36)}-${random}`;
1240
+ }
1241
+ function normalizeTraceEvent(event) {
1242
+ return {
1243
+ traceId: event.traceId ?? createTraceId(event.channel),
1244
+ ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
1245
+ elapsedMs: event.elapsedMs ?? 0,
1246
+ severity: event.severity ?? inferSeverity(event.event),
1247
+ data: event.data ?? {},
1248
+ ...event
1249
+ };
1250
+ }
1251
+ function inferSeverity(eventName) {
1252
+ if (eventName.includes(".failed") || eventName.includes(".error") || eventName.includes("exception") || eventName.includes("notReady")) {
1253
+ return "error";
1254
+ }
1255
+ if (eventName.includes(".closed") || eventName.includes(".warn") || eventName.includes(".changed")) {
1256
+ return "warn";
1257
+ }
1258
+ return "info";
1259
+ }
1260
+
1228
1261
  // src/trace/script.ts
1229
1262
  var TRACE_BINDING_NAME = "__bpTraceBinding";
1230
1263
  var TRACE_SCRIPT = `
@@ -1504,31 +1537,6 @@ var TRACE_SCRIPT = `
1504
1537
  })();
1505
1538
  `;
1506
1539
 
1507
- // src/trace/model.ts
1508
- function createTraceId(prefix = "evt") {
1509
- const random = Math.random().toString(36).slice(2, 10);
1510
- return `${prefix}-${Date.now().toString(36)}-${random}`;
1511
- }
1512
- function normalizeTraceEvent(event) {
1513
- return {
1514
- traceId: event.traceId ?? createTraceId(event.channel),
1515
- ts: event.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
1516
- elapsedMs: event.elapsedMs ?? 0,
1517
- severity: event.severity ?? inferSeverity(event.event),
1518
- data: event.data ?? {},
1519
- ...event
1520
- };
1521
- }
1522
- function inferSeverity(eventName) {
1523
- if (eventName.includes(".failed") || eventName.includes(".error") || eventName.includes("exception") || eventName.includes("notReady")) {
1524
- return "error";
1525
- }
1526
- if (eventName.includes(".closed") || eventName.includes(".warn") || eventName.includes(".changed")) {
1527
- return "warn";
1528
- }
1529
- return "info";
1530
- }
1531
-
1532
1540
  // src/trace/live.ts
1533
1541
  function globToRegex(pattern) {
1534
1542
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
@@ -1545,6 +1553,15 @@ var DEFAULT_RECORDING_SKIP_ACTIONS = [
1545
1553
  "text",
1546
1554
  "screenshot"
1547
1555
  ];
1556
+ function readString(value) {
1557
+ return typeof value === "string" ? value : void 0;
1558
+ }
1559
+ function readStringOr(value, fallback = "") {
1560
+ return readString(value) ?? fallback;
1561
+ }
1562
+ function formatConsoleArg(entry) {
1563
+ return readString(entry["value"]) ?? readString(entry["description"]) ?? "";
1564
+ }
1548
1565
  function loadExistingRecording(manifestPath) {
1549
1566
  try {
1550
1567
  const raw = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
@@ -2345,8 +2362,13 @@ Valid actions: ${valid}`);
2345
2362
  await this.page.cdpClient.send("Runtime.addBinding", { name: TRACE_BINDING_NAME });
2346
2363
  } catch {
2347
2364
  }
2348
- await this.page.cdpClient.send("Page.addScriptToEvaluateOnNewDocument", { source: TRACE_SCRIPT });
2349
- await this.page.cdpClient.send("Runtime.evaluate", { expression: TRACE_SCRIPT, awaitPromise: false });
2365
+ await this.page.cdpClient.send("Page.addScriptToEvaluateOnNewDocument", {
2366
+ source: TRACE_SCRIPT
2367
+ });
2368
+ await this.page.cdpClient.send("Runtime.evaluate", {
2369
+ expression: TRACE_SCRIPT,
2370
+ awaitPromise: false
2371
+ });
2350
2372
  }
2351
2373
  async waitForWsMessage(match, where, timeout) {
2352
2374
  await this.ensureTraceHooks();
@@ -2364,12 +2386,12 @@ Valid actions: ${valid}`);
2364
2386
  clearTimeout(timer);
2365
2387
  };
2366
2388
  const onCreated = (params) => {
2367
- wsUrls.set(String(params["requestId"] ?? ""), String(params["url"] ?? ""));
2389
+ wsUrls.set(readStringOr(params["requestId"]), readStringOr(params["url"]));
2368
2390
  };
2369
2391
  const onFrame = (params) => {
2370
- const requestId = String(params["requestId"] ?? "");
2392
+ const requestId = readStringOr(params["requestId"]);
2371
2393
  const response = params["response"] ?? {};
2372
- const payload = String(response.payloadData ?? "");
2394
+ const payload = response.payloadData ?? "";
2373
2395
  const url = wsUrls.get(requestId) ?? "";
2374
2396
  if (!regex.test(url) && !regex.test(payload)) {
2375
2397
  return;
@@ -2385,13 +2407,13 @@ Valid actions: ${valid}`);
2385
2407
  return;
2386
2408
  }
2387
2409
  try {
2388
- const parsed = JSON.parse(String(params["payload"] ?? ""));
2410
+ const parsed = JSON.parse(readStringOr(params["payload"]));
2389
2411
  if (parsed.event !== "ws.frame.received") {
2390
2412
  return;
2391
2413
  }
2392
2414
  const data = parsed.data ?? {};
2393
- const payload = String(data["payload"] ?? "");
2394
- const url = String(data["url"] ?? "");
2415
+ const payload = readStringOr(data["payload"]);
2416
+ const url = readStringOr(data["url"]);
2395
2417
  if (!regex.test(url) && !regex.test(payload)) {
2396
2418
  return;
2397
2419
  }
@@ -2400,7 +2422,7 @@ Valid actions: ${valid}`);
2400
2422
  }
2401
2423
  cleanup();
2402
2424
  resolve({
2403
- requestId: String(data["connectionId"] ?? ""),
2425
+ requestId: readStringOr(data["connectionId"]),
2404
2426
  url,
2405
2427
  payload
2406
2428
  });
@@ -2444,13 +2466,14 @@ Valid actions: ${valid}`);
2444
2466
  if (!entry || typeof entry !== "object") {
2445
2467
  continue;
2446
2468
  }
2447
- const event = String(entry["event"] ?? "");
2469
+ const record = entry;
2470
+ const event = readStringOr(record["event"]);
2448
2471
  if (event !== "ws.frame.received") {
2449
2472
  continue;
2450
2473
  }
2451
- const data = entry["data"] ?? {};
2452
- const payload = String(data["payload"] ?? "");
2453
- const url = String(data["url"] ?? "");
2474
+ const data = record["data"] ?? {};
2475
+ const payload = readStringOr(data["payload"]);
2476
+ const url = readStringOr(data["url"]);
2454
2477
  if (!regex.test(url) && !regex.test(payload)) {
2455
2478
  continue;
2456
2479
  }
@@ -2458,7 +2481,7 @@ Valid actions: ${valid}`);
2458
2481
  continue;
2459
2482
  }
2460
2483
  return {
2461
- requestId: String(data["connectionId"] ?? ""),
2484
+ requestId: readStringOr(data["connectionId"]),
2462
2485
  url,
2463
2486
  payload
2464
2487
  };
@@ -2479,13 +2502,11 @@ Valid actions: ${valid}`);
2479
2502
  return;
2480
2503
  }
2481
2504
  const args = Array.isArray(params["args"]) ? params["args"] : [];
2482
- errors.push(
2483
- args.map((entry) => String(entry["value"] ?? entry["description"] ?? "")).filter(Boolean).join(" ")
2484
- );
2505
+ errors.push(args.map(formatConsoleArg).filter(Boolean).join(" "));
2485
2506
  };
2486
2507
  const onException = (params) => {
2487
2508
  const details = params["exceptionDetails"] ?? {};
2488
- errors.push(String(details["text"] ?? "Runtime exception"));
2509
+ errors.push(readString(details["text"]) ?? "Runtime exception");
2489
2510
  };
2490
2511
  const timer = setTimeout(() => {
2491
2512
  cleanup();
@@ -3258,12 +3279,12 @@ function parseWavHeader(data) {
3258
3279
  if (data.byteLength < 44) {
3259
3280
  throw new Error("Invalid WAV: file too small");
3260
3281
  }
3261
- const riff = readString(view, 0, 4);
3262
- const wave = readString(view, 8, 4);
3282
+ const riff = readString2(view, 0, 4);
3283
+ const wave = readString2(view, 8, 4);
3263
3284
  if (riff !== "RIFF" || wave !== "WAVE") {
3264
3285
  throw new Error("Invalid WAV: missing RIFF/WAVE header");
3265
3286
  }
3266
- const fmt = readString(view, 12, 4);
3287
+ const fmt = readString2(view, 12, 4);
3267
3288
  if (fmt !== "fmt ") {
3268
3289
  throw new Error("Invalid WAV: missing fmt chunk");
3269
3290
  }
@@ -3272,7 +3293,7 @@ function parseWavHeader(data) {
3272
3293
  const bitsPerSample = view.getUint16(34, true);
3273
3294
  let dataOffset = 36;
3274
3295
  while (dataOffset < data.byteLength - 8) {
3275
- const chunkId = readString(view, dataOffset, 4);
3296
+ const chunkId = readString2(view, dataOffset, 4);
3276
3297
  const chunkSize = view.getUint32(dataOffset + 4, true);
3277
3298
  if (chunkId === "data") {
3278
3299
  return {
@@ -3303,7 +3324,7 @@ function writeString(view, offset, str) {
3303
3324
  view.setUint8(offset + i, str.charCodeAt(i));
3304
3325
  }
3305
3326
  }
3306
- function readString(view, offset, length) {
3327
+ function readString2(view, offset, length) {
3307
3328
  let str = "";
3308
3329
  for (let i = 0; i < length; i++) {
3309
3330
  str += String.fromCharCode(view.getUint8(offset + i));
@@ -5092,6 +5113,330 @@ async function getBrowserWebSocketUrl(host = "localhost:9222") {
5092
5113
  return info.webSocketDebuggerUrl;
5093
5114
  }
5094
5115
 
5116
+ // src/providers/local-discovery.ts
5117
+ var CHANNEL_ORDER = ["stable", "beta", "dev", "canary"];
5118
+ var DEFAULT_PROBE_TIMEOUT_MS = 1e3;
5119
+ var DevToolsActivePortParseError = class extends Error {
5120
+ constructor(message, reason) {
5121
+ super(message);
5122
+ this.reason = reason;
5123
+ this.name = "DevToolsActivePortParseError";
5124
+ }
5125
+ };
5126
+ function getRuntimeEnv() {
5127
+ if (typeof process === "undefined") {
5128
+ return {};
5129
+ }
5130
+ return process.env;
5131
+ }
5132
+ function getRuntimePlatform() {
5133
+ if (typeof process === "undefined") {
5134
+ return void 0;
5135
+ }
5136
+ return process.platform;
5137
+ }
5138
+ function normalizePlatform(platform) {
5139
+ if (platform === "darwin" || platform === "linux" || platform === "win32") {
5140
+ return platform;
5141
+ }
5142
+ throw new Error(`Unsupported platform: ${platform ?? "unknown"}`);
5143
+ }
5144
+ function trimTrailingSeparator(path) {
5145
+ return path.replace(/[\\/]+$/, "");
5146
+ }
5147
+ function joinPath(platform, ...parts) {
5148
+ const separator = platform === "win32" ? "\\" : "/";
5149
+ const cleaned = parts.map((part, index) => {
5150
+ if (index === 0) return trimTrailingSeparator(part);
5151
+ return part.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
5152
+ }).filter((part) => part.length > 0);
5153
+ return cleaned.join(separator);
5154
+ }
5155
+ function resolveHomeDir(platform, env, explicitHomeDir) {
5156
+ if (explicitHomeDir) {
5157
+ return explicitHomeDir;
5158
+ }
5159
+ if (platform === "win32") {
5160
+ return env["USERPROFILE"] ?? env["HOME"] ?? "";
5161
+ }
5162
+ return env["HOME"] ?? env["USERPROFILE"] ?? "";
5163
+ }
5164
+ function toFileFailure(target, error) {
5165
+ const errno = error?.code;
5166
+ if (errno === "ENOENT") {
5167
+ return {
5168
+ ...target,
5169
+ reason: "missing-file",
5170
+ message: `DevToolsActivePort not found at ${target.portFile}`
5171
+ };
5172
+ }
5173
+ return {
5174
+ ...target,
5175
+ reason: "unreadable-file",
5176
+ message: error instanceof Error ? error.message : `Could not read DevToolsActivePort at ${target.portFile}`
5177
+ };
5178
+ }
5179
+ function toProbeFailure(target, wsUrl, error) {
5180
+ const message = error instanceof Error ? error.message : String(error);
5181
+ const lowerMessage = message.toLowerCase();
5182
+ let reason = "connection-error";
5183
+ if (lowerMessage.includes("refused") || lowerMessage.includes("econnrefused")) {
5184
+ reason = "connection-refused";
5185
+ } else if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out")) {
5186
+ reason = "connection-timeout";
5187
+ } else if (lowerMessage.includes("closed")) {
5188
+ reason = "unexpected-close";
5189
+ } else if (lowerMessage.includes("browser.getversion") || lowerMessage.includes("cdp") || lowerMessage.includes("protocol")) {
5190
+ reason = "cdp-error";
5191
+ }
5192
+ return {
5193
+ ...target,
5194
+ wsUrl,
5195
+ reason,
5196
+ message
5197
+ };
5198
+ }
5199
+ async function readTextFile(path) {
5200
+ const fs2 = await import("fs/promises");
5201
+ return fs2.readFile(path, "utf-8");
5202
+ }
5203
+ async function probeBrowserWebSocket(wsUrl, timeoutMs) {
5204
+ let client;
5205
+ try {
5206
+ client = await createCDPClient(wsUrl, { timeout: timeoutMs });
5207
+ const version = await client.send("Browser.getVersion", void 0, null);
5208
+ return { browserVersion: version.product };
5209
+ } finally {
5210
+ await client?.close().catch(() => {
5211
+ });
5212
+ }
5213
+ }
5214
+ var defaultDependencies = {
5215
+ readTextFile,
5216
+ probeBrowserWebSocket,
5217
+ getLegacyBrowserWebSocketUrl: getBrowserWebSocketUrl
5218
+ };
5219
+ function resolveChromeUserDataDirs(options = {}) {
5220
+ const env = options.env ?? getRuntimeEnv();
5221
+ const platform = normalizePlatform(options.platform ?? getRuntimePlatform());
5222
+ const homeDir = resolveHomeDir(platform, env, options.homeDir);
5223
+ if (!homeDir) {
5224
+ throw new Error("Could not determine home directory for local Chrome discovery");
5225
+ }
5226
+ switch (platform) {
5227
+ case "darwin": {
5228
+ const base = joinPath(platform, homeDir, "Library", "Application Support", "Google");
5229
+ return {
5230
+ stable: joinPath(platform, base, "Chrome"),
5231
+ beta: joinPath(platform, base, "Chrome Beta"),
5232
+ dev: joinPath(platform, base, "Chrome Dev"),
5233
+ canary: joinPath(platform, base, "Chrome Canary")
5234
+ };
5235
+ }
5236
+ case "linux": {
5237
+ const configHome = env["CHROME_CONFIG_HOME"] ?? env["XDG_CONFIG_HOME"] ?? joinPath(platform, homeDir, ".config");
5238
+ return {
5239
+ stable: joinPath(platform, configHome, "google-chrome"),
5240
+ beta: joinPath(platform, configHome, "google-chrome-beta"),
5241
+ dev: joinPath(platform, configHome, "google-chrome-dev"),
5242
+ canary: joinPath(platform, configHome, "google-chrome-canary")
5243
+ };
5244
+ }
5245
+ case "win32": {
5246
+ const localAppData = env["LOCALAPPDATA"] ?? joinPath(platform, homeDir, "AppData", "Local");
5247
+ const base = joinPath(platform, localAppData, "Google");
5248
+ return {
5249
+ stable: joinPath(platform, base, "Chrome", "User Data"),
5250
+ beta: joinPath(platform, base, "Chrome Beta", "User Data"),
5251
+ dev: joinPath(platform, base, "Chrome Dev", "User Data"),
5252
+ canary: joinPath(platform, base, "Chrome SxS", "User Data")
5253
+ };
5254
+ }
5255
+ }
5256
+ throw new Error(`Unsupported platform for local Chrome discovery: ${platform}`);
5257
+ }
5258
+ function buildLocalBrowserScanTargets(options = {}) {
5259
+ const env = options.env ?? getRuntimeEnv();
5260
+ const platform = normalizePlatform(options.platform ?? getRuntimePlatform());
5261
+ if (options.userDataDir) {
5262
+ return [
5263
+ {
5264
+ channel: options.channel ?? "custom",
5265
+ userDataDir: options.userDataDir,
5266
+ portFile: joinPath(platform, options.userDataDir, "DevToolsActivePort")
5267
+ }
5268
+ ];
5269
+ }
5270
+ const dirs = resolveChromeUserDataDirs({
5271
+ platform,
5272
+ env,
5273
+ homeDir: options.homeDir
5274
+ });
5275
+ const channels = options.channel ? [options.channel] : CHANNEL_ORDER;
5276
+ return channels.map((channel) => ({
5277
+ channel,
5278
+ userDataDir: dirs[channel],
5279
+ portFile: joinPath(platform, dirs[channel], "DevToolsActivePort")
5280
+ }));
5281
+ }
5282
+ function parseDevToolsActivePortFile(content) {
5283
+ const lines = content.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
5284
+ if (lines.length !== 2) {
5285
+ throw new DevToolsActivePortParseError(
5286
+ `Expected exactly 2 non-empty lines in DevToolsActivePort, got ${lines.length}`,
5287
+ "malformed-file"
5288
+ );
5289
+ }
5290
+ const portText = lines[0];
5291
+ const browserPath = lines[1];
5292
+ const port = Number.parseInt(portText, 10);
5293
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
5294
+ throw new DevToolsActivePortParseError(
5295
+ `Invalid DevToolsActivePort port: ${portText}`,
5296
+ "invalid-port"
5297
+ );
5298
+ }
5299
+ if (!browserPath.startsWith("/devtools/browser/") || browserPath.includes("..") || /[?#\s\\]/u.test(browserPath)) {
5300
+ throw new DevToolsActivePortParseError(
5301
+ `Invalid DevToolsActivePort browser path: ${browserPath}`,
5302
+ "invalid-path"
5303
+ );
5304
+ }
5305
+ return {
5306
+ port,
5307
+ browserPath,
5308
+ wsUrl: `ws://127.0.0.1:${port}${browserPath}`
5309
+ };
5310
+ }
5311
+ async function inspectScanTarget(target, options, deps) {
5312
+ let content;
5313
+ try {
5314
+ content = await deps.readTextFile(target.portFile);
5315
+ } catch (error) {
5316
+ return { kind: "failure", failure: toFileFailure(target, error) };
5317
+ }
5318
+ let parsed;
5319
+ try {
5320
+ parsed = parseDevToolsActivePortFile(content);
5321
+ } catch (error) {
5322
+ if (error instanceof DevToolsActivePortParseError) {
5323
+ return {
5324
+ kind: "failure",
5325
+ failure: {
5326
+ ...target,
5327
+ reason: error.reason,
5328
+ message: error.message
5329
+ }
5330
+ };
5331
+ }
5332
+ throw error;
5333
+ }
5334
+ try {
5335
+ const probe = await deps.probeBrowserWebSocket(
5336
+ parsed.wsUrl,
5337
+ options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS
5338
+ );
5339
+ return {
5340
+ kind: "candidate",
5341
+ candidate: {
5342
+ ...target,
5343
+ port: parsed.port,
5344
+ browserPath: parsed.browserPath,
5345
+ wsUrl: parsed.wsUrl,
5346
+ browserVersion: probe.browserVersion
5347
+ }
5348
+ };
5349
+ } catch (error) {
5350
+ return {
5351
+ kind: "failure",
5352
+ failure: toProbeFailure(target, parsed.wsUrl, error)
5353
+ };
5354
+ }
5355
+ }
5356
+ async function discoverLocalBrowsers(options = {}, deps = defaultDependencies) {
5357
+ const scanTargets = buildLocalBrowserScanTargets(options);
5358
+ const outcomes = await Promise.all(
5359
+ scanTargets.map((target) => inspectScanTarget(target, options, deps))
5360
+ );
5361
+ const candidates = [];
5362
+ const failures = [];
5363
+ for (const outcome of outcomes) {
5364
+ if (outcome.kind === "candidate") {
5365
+ candidates.push(outcome.candidate);
5366
+ } else {
5367
+ failures.push(outcome.failure);
5368
+ }
5369
+ }
5370
+ return { candidates, failures };
5371
+ }
5372
+ var BrowserEndpointResolutionError = class extends Error {
5373
+ constructor(code, message, details = {}) {
5374
+ super(message);
5375
+ this.code = code;
5376
+ this.details = details;
5377
+ }
5378
+ name = "BrowserEndpointResolutionError";
5379
+ };
5380
+ async function resolveBrowserEndpoint(options = {}, deps = defaultDependencies) {
5381
+ if (options.explicitWsUrl) {
5382
+ return {
5383
+ wsUrl: options.explicitWsUrl,
5384
+ source: "explicit-ws"
5385
+ };
5386
+ }
5387
+ let localDiscovery;
5388
+ if (options.allowLocalDiscovery ?? true) {
5389
+ localDiscovery = await discoverLocalBrowsers(options, deps);
5390
+ if (localDiscovery.candidates.length === 1) {
5391
+ const candidate = localDiscovery.candidates[0];
5392
+ return {
5393
+ wsUrl: candidate.wsUrl,
5394
+ source: "devtools-active-port",
5395
+ channel: candidate.channel,
5396
+ userDataDir: candidate.userDataDir
5397
+ };
5398
+ }
5399
+ if (localDiscovery.candidates.length > 1) {
5400
+ throw new BrowserEndpointResolutionError(
5401
+ "multiple-local-browsers",
5402
+ "Multiple local Chrome profiles are available for auto-discovery",
5403
+ {
5404
+ candidates: localDiscovery.candidates,
5405
+ failures: localDiscovery.failures
5406
+ }
5407
+ );
5408
+ }
5409
+ }
5410
+ if (options.allowLegacyHostFallback ?? true) {
5411
+ const legacyHost = options.legacyHost ?? "localhost:9222";
5412
+ try {
5413
+ return {
5414
+ wsUrl: await deps.getLegacyBrowserWebSocketUrl(legacyHost),
5415
+ source: "json-version"
5416
+ };
5417
+ } catch (error) {
5418
+ throw new BrowserEndpointResolutionError(
5419
+ "browser-not-found",
5420
+ "Could not resolve a browser endpoint",
5421
+ {
5422
+ candidates: localDiscovery?.candidates,
5423
+ failures: localDiscovery?.failures,
5424
+ legacyError: error instanceof Error ? error : new Error(String(error)),
5425
+ legacyHost
5426
+ }
5427
+ );
5428
+ }
5429
+ }
5430
+ throw new BrowserEndpointResolutionError(
5431
+ "browser-not-found",
5432
+ "Could not resolve a browser endpoint",
5433
+ {
5434
+ candidates: localDiscovery?.candidates,
5435
+ failures: localDiscovery?.failures
5436
+ }
5437
+ );
5438
+ }
5439
+
5095
5440
  // src/providers/index.ts
5096
5441
  function createProvider(options) {
5097
5442
  switch (options.provider) {
@@ -9186,13 +9531,26 @@ var Browser = class _Browser {
9186
9531
  * Connect to a browser instance
9187
9532
  */
9188
9533
  static async connect(options) {
9189
- const provider = createProvider(options);
9190
- const session = await provider.createSession(options.session);
9534
+ let connectOptions = options;
9535
+ if (options.provider === "generic" && !options.wsUrl) {
9536
+ const endpoint = await resolveBrowserEndpoint({
9537
+ channel: options.channel,
9538
+ userDataDir: options.userDataDir,
9539
+ allowLocalDiscovery: true,
9540
+ allowLegacyHostFallback: true
9541
+ });
9542
+ connectOptions = {
9543
+ ...options,
9544
+ wsUrl: endpoint.wsUrl
9545
+ };
9546
+ }
9547
+ const provider = createProvider(connectOptions);
9548
+ const session = await provider.createSession(connectOptions.session);
9191
9549
  const cdp = await createCDPClient(session.wsUrl, {
9192
- debug: options.debug,
9193
- timeout: options.timeout
9550
+ debug: connectOptions.debug,
9551
+ timeout: connectOptions.timeout
9194
9552
  });
9195
- return new _Browser(cdp, provider, session, options);
9553
+ return new _Browser(cdp, provider, session, connectOptions);
9196
9554
  }
9197
9555
  /**
9198
9556
  * Get or create a page by name.
@@ -9598,6 +9956,7 @@ function disableTracing() {
9598
9956
  BatchExecutor,
9599
9957
  Browser,
9600
9958
  BrowserBaseProvider,
9959
+ BrowserEndpointResolutionError,
9601
9960
  BrowserlessProvider,
9602
9961
  CDPError,
9603
9962
  ElementNotFoundError,
@@ -9609,12 +9968,14 @@ function disableTracing() {
9609
9968
  Tracer,
9610
9969
  addBatchToPage,
9611
9970
  bufferToBase64,
9971
+ buildLocalBrowserScanTargets,
9612
9972
  calculateRMS,
9613
9973
  connect,
9614
9974
  createCDPClient,
9615
9975
  createProvider,
9616
9976
  devices,
9617
9977
  disableTracing,
9978
+ discoverLocalBrowsers,
9618
9979
  discoverTargets,
9619
9980
  enableTracing,
9620
9981
  generateSilence,
@@ -9624,8 +9985,11 @@ function disableTracing() {
9624
9985
  getTracer,
9625
9986
  grantAudioPermissions,
9626
9987
  isTranscriptionAvailable,
9988
+ parseDevToolsActivePortFile,
9627
9989
  parseWavHeader,
9628
9990
  pcmToWav,
9991
+ resolveBrowserEndpoint,
9992
+ resolveChromeUserDataDirs,
9629
9993
  transcribe,
9630
9994
  validateSteps,
9631
9995
  waitForAnyElement,
package/dist/index.d.cts CHANGED
@@ -1,12 +1,12 @@
1
1
  export { BatchExecutor, ValidationError, ValidationResult, addBatchToPage, validateSteps } from './actions.cjs';
2
- import { R as RequestPattern, a as RequestHandler, C as CaptureResult } from './types-C9ySEdOX.cjs';
3
- export { l as ActionOptions, m as ActionResult, A as ActionType, e as AudioChunk, f as AudioInput, g as AudioInputState, h as AudioOutput, B as BatchOptions, b as BatchResult, i as CaptureOptions, a7 as ClearCookiesOptions, n as ConsoleHandler, o as ConsoleMessage, p as ConsoleMessageType, a0 as ContinueRequestOptions, a8 as Cookie, q as CustomSelectConfig, a9 as DeleteCookieOptions, Z as DeviceDescriptor, _ as DeviceName, D as Dialog, r as DialogHandler, s as DialogType, t as Download, E as ElementInfo, u as ElementNotFoundError, v as EmulationState, w as ErrorHandler, a1 as FailRequestOptions, F as FileInput, x as FillOptions, y as FormField, z as FormOption, a2 as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, a3 as InterceptedRequest, N as NavigationError, H as NetworkIdleOptions, J as Page, K as PageError, L as PageSnapshot, P as PlayOptions, c as RecordOptions, a4 as RequestActions, a5 as ResourceType, j as RoundTripOptions, k as RoundTripResult, a6 as RouteOptions, aa as SetCookieOptions, M as SnapshotNode, O as SnapshotOptions, S as Step, d as StepResult, Q as SubmitOptions, T as TimeoutError, U as TypeOptions, V as UserAgentMetadata, W as UserAgentOptions, X as ViewportOptions, Y as WaitForOptions, ab as WaitOptions, ac as WaitResult, ad as WaitState, $ as devices, ae as waitForAnyElement, af as waitForElement, ag as waitForNavigation, ah as waitForNetworkIdle } from './types-C9ySEdOX.cjs';
2
+ import { R as RequestPattern, a as RequestHandler, C as CaptureResult } from './types-BflRmiDz.cjs';
3
+ export { l as ActionOptions, m as ActionResult, A as ActionType, e as AudioChunk, f as AudioInput, g as AudioInputState, h as AudioOutput, B as BatchOptions, b as BatchResult, i as CaptureOptions, a7 as ClearCookiesOptions, n as ConsoleHandler, o as ConsoleMessage, p as ConsoleMessageType, a0 as ContinueRequestOptions, a8 as Cookie, q as CustomSelectConfig, a9 as DeleteCookieOptions, Z as DeviceDescriptor, _ as DeviceName, D as Dialog, r as DialogHandler, s as DialogType, t as Download, E as ElementInfo, u as ElementNotFoundError, v as EmulationState, w as ErrorHandler, a1 as FailRequestOptions, F as FileInput, x as FillOptions, y as FormField, z as FormOption, a2 as FulfillRequestOptions, G as GeolocationOptions, I as InteractiveElement, a3 as InterceptedRequest, N as NavigationError, H as NetworkIdleOptions, J as Page, K as PageError, L as PageSnapshot, P as PlayOptions, c as RecordOptions, a4 as RequestActions, a5 as ResourceType, j as RoundTripOptions, k as RoundTripResult, a6 as RouteOptions, aa as SetCookieOptions, M as SnapshotNode, O as SnapshotOptions, S as Step, d as StepResult, Q as SubmitOptions, T as TimeoutError, U as TypeOptions, V as UserAgentMetadata, W as UserAgentOptions, X as ViewportOptions, Y as WaitForOptions, ab as WaitOptions, ac as WaitResult, ad as WaitState, $ as devices, ae as waitForAnyElement, af as waitForElement, ag as waitForNavigation, ah as waitForNetworkIdle } from './types-BflRmiDz.cjs';
4
4
  import { C as CDPClient } from './client-B5QBRgIy.cjs';
5
5
  export { a as CDPClientOptions, c as createCDPClient } from './client-B5QBRgIy.cjs';
6
6
  export { Browser, BrowserOptions, PageOptions, connect } from './browser.cjs';
7
7
  export { CDPError } from './cdp.cjs';
8
8
  export { BrowserBaseProvider, BrowserlessProvider, GenericProvider, createProvider, discoverTargets, getBrowserWebSocketUrl } from './providers.cjs';
9
- export { b as ConnectOptions, C as CreateSessionOptions, P as Provider, a as ProviderSession } from './types--wXNHUwt.cjs';
9
+ export { B as BrowserEndpointResolutionError, d as ChromeChannel, b as ConnectOptions, C as CreateSessionOptions, P as Provider, a as ProviderSession, R as ResolvedBrowserEndpoint, g as ResolvedBrowserSource, c as buildLocalBrowserScanTargets, f as discoverLocalBrowsers, p as parseDevToolsActivePortFile, r as resolveBrowserEndpoint, h as resolveChromeUserDataDirs } from './types-DeVSWhXj.cjs';
10
10
 
11
11
  /**
12
12
  * Request interception implementation