browserclaw 0.4.0 → 0.4.2

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.d.cts CHANGED
@@ -1136,6 +1136,12 @@ declare class BrowserClaw {
1136
1136
  stop(): Promise<void>;
1137
1137
  }
1138
1138
 
1139
+ /**
1140
+ * Convert a WebSocket CDP URL to an HTTP base URL for `/json/*` endpoints.
1141
+ */
1142
+ declare function normalizeCdpHttpBaseForJsonEndpoints(cdpUrl: string): string;
1143
+ declare function isChromeReachable(cdpUrl: string, timeoutMs?: number, authToken?: string): Promise<boolean>;
1144
+ declare function getChromeWebSocketUrl(cdpUrl: string, timeoutMs?: number, authToken?: string): Promise<string | null>;
1139
1145
  declare function isChromeCdpReady(cdpUrl: string, timeoutMs?: number, handshakeTimeoutMs?: number): Promise<boolean>;
1140
1146
 
1141
1147
  type LookupFn = typeof lookup;
@@ -1151,6 +1157,11 @@ declare class InvalidBrowserNavigationUrlError extends Error {
1151
1157
  type BrowserNavigationPolicyOptions = {
1152
1158
  ssrfPolicy?: SsrfPolicy;
1153
1159
  };
1160
+ /** Playwright-compatible request interface for redirect chain inspection. */
1161
+ type BrowserNavigationRequestLike = {
1162
+ url(): string;
1163
+ redirectedFrom(): BrowserNavigationRequestLike | null;
1164
+ };
1154
1165
  /** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
1155
1166
  declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
1156
1167
  /**
@@ -1161,5 +1172,31 @@ declare function assertBrowserNavigationAllowed(opts: {
1161
1172
  url: string;
1162
1173
  lookupFn?: LookupFn;
1163
1174
  } & BrowserNavigationPolicyOptions): Promise<void>;
1175
+ /**
1176
+ * Best-effort post-navigation guard for the final page URL.
1177
+ * Only validates http/https URLs and about:blank — swallows errors on
1178
+ * unparseable URLs and non-network protocols (e.g. chrome-error://) to avoid
1179
+ * false positives on browser-internal error pages.
1180
+ *
1181
+ * Call this after `page.goto()` to catch redirect-based SSRF bypasses.
1182
+ */
1183
+ declare function assertBrowserNavigationResultAllowed(opts: {
1184
+ url: string;
1185
+ lookupFn?: LookupFn;
1186
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1187
+ /**
1188
+ * Walk the full redirect chain and validate each hop against the SSRF policy.
1189
+ * Call this after `page.goto()` with `response?.request()` to catch intermediate
1190
+ * redirects that resolve to private/internal addresses.
1191
+ */
1192
+ declare function assertBrowserNavigationRedirectChainAllowed(opts: {
1193
+ request?: BrowserNavigationRequestLike | null;
1194
+ lookupFn?: LookupFn;
1195
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1196
+ /**
1197
+ * Returns true if the SSRF policy requires redirect chain inspection
1198
+ * (i.e. strict mode where private network is blocked).
1199
+ */
1200
+ declare function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: SsrfPolicy): boolean;
1164
1201
 
1165
- export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, isChromeCdpReady, withBrowserNavigationPolicy };
1202
+ export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, getChromeWebSocketUrl, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, requiresInspectableBrowserNavigationRedirects, withBrowserNavigationPolicy };
package/dist/index.d.ts CHANGED
@@ -1136,6 +1136,12 @@ declare class BrowserClaw {
1136
1136
  stop(): Promise<void>;
1137
1137
  }
1138
1138
 
1139
+ /**
1140
+ * Convert a WebSocket CDP URL to an HTTP base URL for `/json/*` endpoints.
1141
+ */
1142
+ declare function normalizeCdpHttpBaseForJsonEndpoints(cdpUrl: string): string;
1143
+ declare function isChromeReachable(cdpUrl: string, timeoutMs?: number, authToken?: string): Promise<boolean>;
1144
+ declare function getChromeWebSocketUrl(cdpUrl: string, timeoutMs?: number, authToken?: string): Promise<string | null>;
1139
1145
  declare function isChromeCdpReady(cdpUrl: string, timeoutMs?: number, handshakeTimeoutMs?: number): Promise<boolean>;
1140
1146
 
1141
1147
  type LookupFn = typeof lookup;
@@ -1151,6 +1157,11 @@ declare class InvalidBrowserNavigationUrlError extends Error {
1151
1157
  type BrowserNavigationPolicyOptions = {
1152
1158
  ssrfPolicy?: SsrfPolicy;
1153
1159
  };
1160
+ /** Playwright-compatible request interface for redirect chain inspection. */
1161
+ type BrowserNavigationRequestLike = {
1162
+ url(): string;
1163
+ redirectedFrom(): BrowserNavigationRequestLike | null;
1164
+ };
1154
1165
  /** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
1155
1166
  declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
1156
1167
  /**
@@ -1161,5 +1172,31 @@ declare function assertBrowserNavigationAllowed(opts: {
1161
1172
  url: string;
1162
1173
  lookupFn?: LookupFn;
1163
1174
  } & BrowserNavigationPolicyOptions): Promise<void>;
1175
+ /**
1176
+ * Best-effort post-navigation guard for the final page URL.
1177
+ * Only validates http/https URLs and about:blank — swallows errors on
1178
+ * unparseable URLs and non-network protocols (e.g. chrome-error://) to avoid
1179
+ * false positives on browser-internal error pages.
1180
+ *
1181
+ * Call this after `page.goto()` to catch redirect-based SSRF bypasses.
1182
+ */
1183
+ declare function assertBrowserNavigationResultAllowed(opts: {
1184
+ url: string;
1185
+ lookupFn?: LookupFn;
1186
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1187
+ /**
1188
+ * Walk the full redirect chain and validate each hop against the SSRF policy.
1189
+ * Call this after `page.goto()` with `response?.request()` to catch intermediate
1190
+ * redirects that resolve to private/internal addresses.
1191
+ */
1192
+ declare function assertBrowserNavigationRedirectChainAllowed(opts: {
1193
+ request?: BrowserNavigationRequestLike | null;
1194
+ lookupFn?: LookupFn;
1195
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1196
+ /**
1197
+ * Returns true if the SSRF policy requires redirect chain inspection
1198
+ * (i.e. strict mode where private network is blocked).
1199
+ */
1200
+ declare function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: SsrfPolicy): boolean;
1164
1201
 
1165
- export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, isChromeCdpReady, withBrowserNavigationPolicy };
1202
+ export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, getChromeWebSocketUrl, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, requiresInspectableBrowserNavigationRedirects, withBrowserNavigationPolicy };
package/dist/index.js CHANGED
@@ -340,39 +340,109 @@ function resolveUserDataDir(profileName) {
340
340
  const configDir = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
341
341
  return path.join(configDir, "browserclaw", "profiles", profileName, "user-data");
342
342
  }
343
- async function isChromeReachable(cdpUrl, timeoutMs = 500, authToken) {
344
- const ctrl = new AbortController();
345
- const t = setTimeout(() => ctrl.abort(), timeoutMs);
343
+ function isWebSocketUrl(url) {
346
344
  try {
347
- const headers = {};
348
- if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
349
- const res = await fetch(`${cdpUrl.replace(/\/+$/, "")}/json/version`, { signal: ctrl.signal, headers });
350
- if (!res.ok) return false;
351
- const data = await res.json();
352
- return data != null && typeof data === "object";
345
+ const parsed = new URL(url);
346
+ return parsed.protocol === "ws:" || parsed.protocol === "wss:";
353
347
  } catch {
354
348
  return false;
355
- } finally {
356
- clearTimeout(t);
357
349
  }
358
350
  }
359
- async function getChromeWebSocketUrl(cdpUrl, timeoutMs = 500, authToken) {
351
+ function isLoopbackHost(hostname) {
352
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
353
+ }
354
+ function normalizeCdpWsUrl(wsUrl, cdpUrl) {
355
+ const ws = new URL(wsUrl);
356
+ const cdp = new URL(cdpUrl);
357
+ const isWildcardBind = ws.hostname === "0.0.0.0" || ws.hostname === "[::]";
358
+ if ((isLoopbackHost(ws.hostname) || isWildcardBind) && !isLoopbackHost(cdp.hostname)) {
359
+ ws.hostname = cdp.hostname;
360
+ const cdpPort = cdp.port || (cdp.protocol === "https:" ? "443" : "80");
361
+ ws.port = cdpPort;
362
+ ws.protocol = cdp.protocol === "https:" ? "wss:" : "ws:";
363
+ }
364
+ if (cdp.protocol === "https:" && ws.protocol === "ws:") ws.protocol = "wss:";
365
+ if (!ws.username && !ws.password && (cdp.username || cdp.password)) {
366
+ ws.username = cdp.username;
367
+ ws.password = cdp.password;
368
+ }
369
+ for (const [key, value] of cdp.searchParams.entries()) {
370
+ if (!ws.searchParams.has(key)) ws.searchParams.append(key, value);
371
+ }
372
+ return ws.toString();
373
+ }
374
+ function normalizeCdpHttpBaseForJsonEndpoints(cdpUrl) {
375
+ try {
376
+ const url = new URL(cdpUrl);
377
+ if (url.protocol === "ws:") url.protocol = "http:";
378
+ else if (url.protocol === "wss:") url.protocol = "https:";
379
+ url.pathname = url.pathname.replace(/\/devtools\/browser\/.*$/, "");
380
+ url.pathname = url.pathname.replace(/\/cdp$/, "");
381
+ return url.toString().replace(/\/$/, "");
382
+ } catch {
383
+ return cdpUrl.replace(/^ws:/, "http:").replace(/^wss:/, "https:").replace(/\/devtools\/browser\/.*$/, "").replace(/\/cdp$/, "").replace(/\/$/, "");
384
+ }
385
+ }
386
+ function appendCdpPath(cdpUrl, cdpPath) {
387
+ const url = new URL(cdpUrl);
388
+ url.pathname = `${url.pathname.replace(/\/$/, "")}${cdpPath.startsWith("/") ? cdpPath : `/${cdpPath}`}`;
389
+ return url.toString();
390
+ }
391
+ async function canOpenWebSocket(url, timeoutMs) {
392
+ return new Promise((resolve2) => {
393
+ let settled = false;
394
+ const finish = (value) => {
395
+ if (settled) return;
396
+ settled = true;
397
+ clearTimeout(timer);
398
+ try {
399
+ ws.close();
400
+ } catch {
401
+ }
402
+ resolve2(value);
403
+ };
404
+ const timer = setTimeout(() => finish(false), Math.max(50, timeoutMs + 25));
405
+ let ws;
406
+ try {
407
+ ws = new WebSocket(url);
408
+ } catch {
409
+ finish(false);
410
+ return;
411
+ }
412
+ ws.onopen = () => finish(true);
413
+ ws.onerror = () => finish(false);
414
+ });
415
+ }
416
+ async function fetchChromeVersion(cdpUrl, timeoutMs = 500, authToken) {
360
417
  const ctrl = new AbortController();
361
418
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
362
419
  try {
420
+ const httpBase = isWebSocketUrl(cdpUrl) ? normalizeCdpHttpBaseForJsonEndpoints(cdpUrl) : cdpUrl;
363
421
  const headers = {};
364
422
  if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
365
- const res = await fetch(`${cdpUrl.replace(/\/+$/, "")}/json/version`, { signal: ctrl.signal, headers });
423
+ const res = await fetch(appendCdpPath(httpBase, "/json/version"), { signal: ctrl.signal, headers });
366
424
  if (!res.ok) return null;
367
425
  const data = await res.json();
368
426
  if (!data || typeof data !== "object") return null;
369
- return String(data?.webSocketDebuggerUrl ?? "").trim() || null;
427
+ return data;
370
428
  } catch {
371
429
  return null;
372
430
  } finally {
373
431
  clearTimeout(t);
374
432
  }
375
433
  }
434
+ async function isChromeReachable(cdpUrl, timeoutMs = 500, authToken) {
435
+ if (isWebSocketUrl(cdpUrl)) return await canOpenWebSocket(cdpUrl, timeoutMs);
436
+ const version = await fetchChromeVersion(cdpUrl, timeoutMs, authToken);
437
+ return Boolean(version);
438
+ }
439
+ async function getChromeWebSocketUrl(cdpUrl, timeoutMs = 500, authToken) {
440
+ if (isWebSocketUrl(cdpUrl)) return cdpUrl;
441
+ const version = await fetchChromeVersion(cdpUrl, timeoutMs, authToken);
442
+ const wsUrl = String(version?.webSocketDebuggerUrl ?? "").trim();
443
+ if (!wsUrl) return null;
444
+ return normalizeCdpWsUrl(wsUrl, cdpUrl);
445
+ }
376
446
  async function isChromeCdpReady(cdpUrl, timeoutMs = 500, handshakeTimeoutMs = 800) {
377
447
  const wsUrl = await getChromeWebSocketUrl(cdpUrl, timeoutMs);
378
448
  if (!wsUrl) return false;
@@ -770,7 +840,7 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
770
840
  }
771
841
  if (cdpUrl) {
772
842
  try {
773
- const listUrl = `${cdpUrl.replace(/\/+$/, "").replace(/^ws:/, "http:").replace(/\/cdp$/, "")}/json/list`;
843
+ const listUrl = `${normalizeCdpHttpBaseForJsonEndpoints(cdpUrl)}/json/list`;
774
844
  const headers = {};
775
845
  if (cached?.authToken) headers["Authorization"] = `Bearer ${cached.authToken}`;
776
846
  const response = await fetch(listUrl, { headers });
@@ -1258,6 +1328,20 @@ function withBrowserNavigationPolicy(ssrfPolicy) {
1258
1328
  }
1259
1329
  var NETWORK_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
1260
1330
  var SAFE_NON_NETWORK_URLS = /* @__PURE__ */ new Set(["about:blank"]);
1331
+ var PROXY_ENV_KEYS = ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy"];
1332
+ function isAllowedNonNetworkNavigationUrl(parsed) {
1333
+ return SAFE_NON_NETWORK_URLS.has(parsed.href);
1334
+ }
1335
+ function isPrivateNetworkAllowedByPolicy(policy) {
1336
+ return policy?.dangerouslyAllowPrivateNetwork === true || policy?.allowPrivateNetwork === true;
1337
+ }
1338
+ function hasProxyEnvConfigured(env = process.env) {
1339
+ for (const key of PROXY_ENV_KEYS) {
1340
+ const value = env[key];
1341
+ if (typeof value === "string" && value.trim().length > 0) return true;
1342
+ }
1343
+ return false;
1344
+ }
1261
1345
  async function assertBrowserNavigationAllowed(opts) {
1262
1346
  const rawUrl = String(opts.url ?? "").trim();
1263
1347
  let parsed;
@@ -1267,9 +1351,14 @@ async function assertBrowserNavigationAllowed(opts) {
1267
1351
  throw new InvalidBrowserNavigationUrlError(`Invalid URL: "${rawUrl}"`);
1268
1352
  }
1269
1353
  if (!NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol)) {
1270
- if (SAFE_NON_NETWORK_URLS.has(parsed.href)) return;
1354
+ if (isAllowedNonNetworkNavigationUrl(parsed)) return;
1271
1355
  throw new InvalidBrowserNavigationUrlError(`Navigation blocked: unsupported protocol "${parsed.protocol}"`);
1272
1356
  }
1357
+ if (hasProxyEnvConfigured() && !isPrivateNetworkAllowedByPolicy(opts.ssrfPolicy)) {
1358
+ throw new InvalidBrowserNavigationUrlError(
1359
+ "Navigation blocked: strict browser SSRF policy cannot be enforced while env proxy variables are set"
1360
+ );
1361
+ }
1273
1362
  const policy = opts.ssrfPolicy;
1274
1363
  if (policy?.dangerouslyAllowPrivateNetwork ?? policy?.allowPrivateNetwork ?? true) return;
1275
1364
  const allowedHostnames = [
@@ -1474,10 +1563,24 @@ async function assertBrowserNavigationResultAllowed(opts) {
1474
1563
  } catch {
1475
1564
  return;
1476
1565
  }
1477
- if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || SAFE_NON_NETWORK_URLS.has(parsed.href)) {
1566
+ if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || isAllowedNonNetworkNavigationUrl(parsed)) {
1478
1567
  await assertBrowserNavigationAllowed(opts);
1479
1568
  }
1480
1569
  }
1570
+ async function assertBrowserNavigationRedirectChainAllowed(opts) {
1571
+ const chain = [];
1572
+ let current = opts.request ?? null;
1573
+ while (current) {
1574
+ chain.push(current.url());
1575
+ current = current.redirectedFrom();
1576
+ }
1577
+ for (const url of [...chain].reverse()) {
1578
+ await assertBrowserNavigationAllowed({ url, lookupFn: opts.lookupFn, ssrfPolicy: opts.ssrfPolicy });
1579
+ }
1580
+ }
1581
+ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
1582
+ return !isPrivateNetworkAllowedByPolicy(ssrfPolicy);
1583
+ }
1481
1584
 
1482
1585
  // src/actions/interaction.ts
1483
1586
  async function clickViaPlaywright(opts) {
@@ -1682,7 +1785,8 @@ async function navigateViaPlaywright(opts) {
1682
1785
  await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
1683
1786
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1684
1787
  ensurePageState(page);
1685
- await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
1788
+ const response = await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
1789
+ await assertBrowserNavigationRedirectChainAllowed({ request: response?.request(), ...withBrowserNavigationPolicy(policy) });
1686
1790
  const finalUrl = page.url();
1687
1791
  await assertBrowserNavigationResultAllowed({ url: finalUrl, ssrfPolicy: policy });
1688
1792
  return { url: finalUrl };
@@ -1713,7 +1817,9 @@ async function createPageViaPlaywright(opts) {
1713
1817
  const page = await context.newPage();
1714
1818
  ensurePageState(page);
1715
1819
  if (targetUrl !== "about:blank") {
1716
- await page.goto(targetUrl, { timeout: normalizeTimeoutMs(void 0, 2e4) });
1820
+ const navigationPolicy = withBrowserNavigationPolicy(policy);
1821
+ const response = await page.goto(targetUrl, { timeout: normalizeTimeoutMs(void 0, 2e4) });
1822
+ await assertBrowserNavigationRedirectChainAllowed({ request: response?.request(), ...navigationPolicy });
1717
1823
  await assertBrowserNavigationResultAllowed({ url: page.url(), ssrfPolicy: policy });
1718
1824
  }
1719
1825
  const tid = await pageTargetId(page).catch(() => null);
@@ -3239,6 +3345,6 @@ var BrowserClaw = class _BrowserClaw {
3239
3345
  }
3240
3346
  };
3241
3347
 
3242
- export { BrowserClaw, CrawlPage, InvalidBrowserNavigationUrlError, assertBrowserNavigationAllowed, isChromeCdpReady, withBrowserNavigationPolicy };
3348
+ export { BrowserClaw, CrawlPage, InvalidBrowserNavigationUrlError, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, getChromeWebSocketUrl, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, requiresInspectableBrowserNavigationRedirects, withBrowserNavigationPolicy };
3243
3349
  //# sourceMappingURL=index.js.map
3244
3350
  //# sourceMappingURL=index.js.map