browserclaw 0.5.1 → 0.5.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.cjs CHANGED
@@ -841,6 +841,7 @@ async function connectBrowser(cdpUrl, authToken) {
841
841
  return connected;
842
842
  } catch (err) {
843
843
  lastErr = err;
844
+ if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) break;
844
845
  await new Promise((r) => setTimeout(r, 250 + attempt * 250));
845
846
  }
846
847
  }
@@ -866,6 +867,55 @@ async function disconnectBrowser() {
866
867
  if (cur) await cur.browser.close().catch(() => {
867
868
  });
868
869
  }
870
+ async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
871
+ const httpBase = normalizeCdpHttpBaseForJsonEndpoints(cdpUrl);
872
+ const ctrl = new AbortController();
873
+ const t = setTimeout(() => ctrl.abort(), 2e3);
874
+ let targets;
875
+ try {
876
+ const res = await fetch(`${httpBase}/json/list`, { signal: ctrl.signal });
877
+ if (!res.ok) return;
878
+ targets = await res.json();
879
+ } catch {
880
+ return;
881
+ } finally {
882
+ clearTimeout(t);
883
+ }
884
+ if (!Array.isArray(targets)) return;
885
+ const target = targets.find((entry) => String(entry?.id ?? "").trim() === targetId);
886
+ const wsUrl = String(target?.webSocketDebuggerUrl ?? "").trim();
887
+ if (!wsUrl) return;
888
+ await new Promise((resolve2) => {
889
+ let done = false;
890
+ const finish = () => {
891
+ if (done) return;
892
+ done = true;
893
+ clearTimeout(timer);
894
+ try {
895
+ ws.close();
896
+ } catch {
897
+ }
898
+ resolve2();
899
+ };
900
+ const timer = setTimeout(finish, 2e3);
901
+ let ws;
902
+ try {
903
+ ws = new WebSocket(wsUrl);
904
+ } catch {
905
+ finish();
906
+ return;
907
+ }
908
+ ws.onopen = () => {
909
+ try {
910
+ ws.send(JSON.stringify({ id: 1, method: "Runtime.terminateExecution" }));
911
+ } catch {
912
+ }
913
+ setTimeout(finish, 300);
914
+ };
915
+ ws.onerror = () => finish();
916
+ ws.onclose = () => finish();
917
+ });
918
+ }
869
919
  async function forceDisconnectPlaywrightForTarget(opts) {
870
920
  const normalized = normalizeCdpUrl(opts.cdpUrl);
871
921
  const cur = cached;
@@ -874,23 +924,8 @@ async function forceDisconnectPlaywrightForTarget(opts) {
874
924
  connectingByUrl.delete(normalized);
875
925
  const targetId = opts.targetId?.trim() || "";
876
926
  if (targetId) {
877
- try {
878
- const pages = await getAllPages(cur.browser);
879
- for (const page of pages) {
880
- const tid = await pageTargetId(page).catch(() => null);
881
- if (tid === targetId) {
882
- const session = await page.context().newCDPSession(page);
883
- try {
884
- await session.send("Runtime.terminateExecution");
885
- } finally {
886
- await session.detach().catch(() => {
887
- });
888
- }
889
- break;
890
- }
891
- }
892
- } catch {
893
- }
927
+ await tryTerminateExecutionViaCdp(normalized, targetId).catch(() => {
928
+ });
894
929
  }
895
930
  cur.browser.close().catch(() => {
896
931
  });
@@ -1312,6 +1347,35 @@ async function snapshotRole(opts) {
1312
1347
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1313
1348
  ensurePageState(page);
1314
1349
  const sourceUrl = page.url();
1350
+ if (opts.refsMode === "aria") {
1351
+ if (opts.selector?.trim() || opts.frameSelector?.trim()) {
1352
+ throw new Error("refs=aria does not support selector/frame snapshots yet.");
1353
+ }
1354
+ const maybe = page;
1355
+ if (!maybe._snapshotForAI) {
1356
+ throw new Error("refs=aria requires Playwright _snapshotForAI support.");
1357
+ }
1358
+ const result = await maybe._snapshotForAI({ timeout: 5e3, track: "response" });
1359
+ const built2 = buildRoleSnapshotFromAiSnapshot(String(result?.full ?? ""), opts.options);
1360
+ storeRoleRefsForTarget({
1361
+ page,
1362
+ cdpUrl: opts.cdpUrl,
1363
+ targetId: opts.targetId,
1364
+ refs: built2.refs,
1365
+ mode: "aria"
1366
+ });
1367
+ return {
1368
+ snapshot: built2.snapshot,
1369
+ refs: built2.refs,
1370
+ stats: getRoleSnapshotStats(built2.snapshot, built2.refs),
1371
+ untrusted: true,
1372
+ contentMeta: {
1373
+ sourceUrl,
1374
+ contentType: "browser-snapshot",
1375
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
1376
+ }
1377
+ };
1378
+ }
1315
1379
  const frameSelector = opts.frameSelector?.trim() || "";
1316
1380
  const selector = opts.selector?.trim() || "";
1317
1381
  const locator = frameSelector ? selector ? page.frameLocator(frameSelector).locator(selector) : page.frameLocator(frameSelector).locator(":root") : selector ? page.locator(selector) : page.locator(":root");
@@ -1323,7 +1387,7 @@ async function snapshotRole(opts) {
1323
1387
  targetId: opts.targetId,
1324
1388
  refs: built.refs,
1325
1389
  frameSelector: frameSelector || void 0,
1326
- mode: opts.refsMode ?? "role"
1390
+ mode: "role"
1327
1391
  });
1328
1392
  return {
1329
1393
  snapshot: built.snapshot,
@@ -1411,7 +1475,7 @@ var InvalidBrowserNavigationUrlError = class extends Error {
1411
1475
  }
1412
1476
  };
1413
1477
  function withBrowserNavigationPolicy(ssrfPolicy) {
1414
- return { ssrfPolicy };
1478
+ return ssrfPolicy ? { ssrfPolicy } : {};
1415
1479
  }
1416
1480
  var NETWORK_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
1417
1481
  var SAFE_NON_NETWORK_URLS = /* @__PURE__ */ new Set(["about:blank"]);
@@ -1524,6 +1588,14 @@ function extractEmbeddedIpv4FromIpv6(v6, opts) {
1524
1588
  return true;
1525
1589
  }
1526
1590
  }
1591
+ if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 65535 && parts[5] === 0) {
1592
+ const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1593
+ try {
1594
+ return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1595
+ } catch {
1596
+ return true;
1597
+ }
1598
+ }
1527
1599
  if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0) {
1528
1600
  const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1529
1601
  try {
@@ -1840,6 +1912,13 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
1840
1912
  }
1841
1913
 
1842
1914
  // src/actions/interaction.ts
1915
+ var MAX_CLICK_DELAY_MS = 5e3;
1916
+ function resolveBoundedDelayMs(value, label, maxMs) {
1917
+ const normalized = Math.floor(value ?? 0);
1918
+ if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
1919
+ if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${maxMs}ms`);
1920
+ return normalized;
1921
+ }
1843
1922
  async function clickViaPlaywright(opts) {
1844
1923
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1845
1924
  ensurePageState(page);
@@ -1847,6 +1926,11 @@ async function clickViaPlaywright(opts) {
1847
1926
  const locator = refLocator(page, opts.ref);
1848
1927
  const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
1849
1928
  try {
1929
+ const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
1930
+ if (delayMs > 0) {
1931
+ await locator.hover({ timeout });
1932
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
1933
+ }
1850
1934
  if (opts.doubleClick) {
1851
1935
  await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
1852
1936
  } else {
@@ -2157,12 +2241,14 @@ async function resizeViewportViaPlaywright(opts) {
2157
2241
  }
2158
2242
 
2159
2243
  // src/actions/wait.ts
2244
+ var MAX_WAIT_TIME_MS = 3e4;
2160
2245
  async function waitForViaPlaywright(opts) {
2161
2246
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2162
2247
  ensurePageState(page);
2163
2248
  const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
2164
2249
  if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
2165
- await page.waitForTimeout(Math.max(0, opts.timeMs));
2250
+ const bounded = Math.max(0, Math.min(MAX_WAIT_TIME_MS, Math.floor(opts.timeMs)));
2251
+ await page.waitForTimeout(bounded);
2166
2252
  }
2167
2253
  if (opts.text) {
2168
2254
  await page.getByText(opts.text).first().waitFor({ state: "visible", timeout });
@@ -2851,6 +2937,7 @@ var CrawlPage = class {
2851
2937
  doubleClick: opts?.doubleClick,
2852
2938
  button: opts?.button,
2853
2939
  modifiers: opts?.modifiers,
2940
+ delayMs: opts?.delayMs,
2854
2941
  timeoutMs: opts?.timeoutMs
2855
2942
  });
2856
2943
  }