browserclaw 0.5.1 → 0.5.3
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 +126 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +126 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
878
|
-
|
|
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
|
});
|
|
@@ -922,33 +957,37 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
|
922
957
|
}
|
|
923
958
|
if (tid && tid === targetId) return page;
|
|
924
959
|
}
|
|
925
|
-
if (!resolvedViaCdp && pages.length === 1) {
|
|
926
|
-
return pages[0];
|
|
927
|
-
}
|
|
928
960
|
if (cdpUrl) {
|
|
929
961
|
try {
|
|
930
962
|
const listUrl = `${normalizeCdpHttpBaseForJsonEndpoints(cdpUrl)}/json/list`;
|
|
931
963
|
const headers = {};
|
|
932
964
|
if (cached?.authToken) headers["Authorization"] = `Bearer ${cached.authToken}`;
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const
|
|
937
|
-
if (
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
if (
|
|
941
|
-
const
|
|
942
|
-
if (
|
|
943
|
-
|
|
944
|
-
|
|
965
|
+
const ctrl = new AbortController();
|
|
966
|
+
const t = setTimeout(() => ctrl.abort(), 2e3);
|
|
967
|
+
try {
|
|
968
|
+
const response = await fetch(listUrl, { headers, signal: ctrl.signal });
|
|
969
|
+
if (response.ok) {
|
|
970
|
+
const targets = await response.json();
|
|
971
|
+
const target = targets.find((entry) => entry.id === targetId);
|
|
972
|
+
if (target) {
|
|
973
|
+
const urlMatch = pages.filter((p) => p.url() === target.url);
|
|
974
|
+
if (urlMatch.length === 1) return urlMatch[0];
|
|
975
|
+
if (urlMatch.length > 1) {
|
|
976
|
+
const sameUrlTargets = targets.filter((entry) => entry.url === target.url);
|
|
977
|
+
if (sameUrlTargets.length === urlMatch.length) {
|
|
978
|
+
const idx = sameUrlTargets.findIndex((entry) => entry.id === targetId);
|
|
979
|
+
if (idx >= 0 && idx < urlMatch.length) return urlMatch[idx];
|
|
980
|
+
}
|
|
945
981
|
}
|
|
946
982
|
}
|
|
947
983
|
}
|
|
984
|
+
} finally {
|
|
985
|
+
clearTimeout(t);
|
|
948
986
|
}
|
|
949
987
|
} catch {
|
|
950
988
|
}
|
|
951
989
|
}
|
|
990
|
+
if (!resolvedViaCdp && pages.length === 1) return pages[0];
|
|
952
991
|
return null;
|
|
953
992
|
}
|
|
954
993
|
async function getPageForTargetId(opts) {
|
|
@@ -1312,6 +1351,35 @@ async function snapshotRole(opts) {
|
|
|
1312
1351
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1313
1352
|
ensurePageState(page);
|
|
1314
1353
|
const sourceUrl = page.url();
|
|
1354
|
+
if (opts.refsMode === "aria") {
|
|
1355
|
+
if (opts.selector?.trim() || opts.frameSelector?.trim()) {
|
|
1356
|
+
throw new Error("refs=aria does not support selector/frame snapshots yet.");
|
|
1357
|
+
}
|
|
1358
|
+
const maybe = page;
|
|
1359
|
+
if (!maybe._snapshotForAI) {
|
|
1360
|
+
throw new Error("refs=aria requires Playwright _snapshotForAI support.");
|
|
1361
|
+
}
|
|
1362
|
+
const result = await maybe._snapshotForAI({ timeout: 5e3, track: "response" });
|
|
1363
|
+
const built2 = buildRoleSnapshotFromAiSnapshot(String(result?.full ?? ""), opts.options);
|
|
1364
|
+
storeRoleRefsForTarget({
|
|
1365
|
+
page,
|
|
1366
|
+
cdpUrl: opts.cdpUrl,
|
|
1367
|
+
targetId: opts.targetId,
|
|
1368
|
+
refs: built2.refs,
|
|
1369
|
+
mode: "aria"
|
|
1370
|
+
});
|
|
1371
|
+
return {
|
|
1372
|
+
snapshot: built2.snapshot,
|
|
1373
|
+
refs: built2.refs,
|
|
1374
|
+
stats: getRoleSnapshotStats(built2.snapshot, built2.refs),
|
|
1375
|
+
untrusted: true,
|
|
1376
|
+
contentMeta: {
|
|
1377
|
+
sourceUrl,
|
|
1378
|
+
contentType: "browser-snapshot",
|
|
1379
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1315
1383
|
const frameSelector = opts.frameSelector?.trim() || "";
|
|
1316
1384
|
const selector = opts.selector?.trim() || "";
|
|
1317
1385
|
const locator = frameSelector ? selector ? page.frameLocator(frameSelector).locator(selector) : page.frameLocator(frameSelector).locator(":root") : selector ? page.locator(selector) : page.locator(":root");
|
|
@@ -1323,7 +1391,7 @@ async function snapshotRole(opts) {
|
|
|
1323
1391
|
targetId: opts.targetId,
|
|
1324
1392
|
refs: built.refs,
|
|
1325
1393
|
frameSelector: frameSelector || void 0,
|
|
1326
|
-
mode:
|
|
1394
|
+
mode: "role"
|
|
1327
1395
|
});
|
|
1328
1396
|
return {
|
|
1329
1397
|
snapshot: built.snapshot,
|
|
@@ -1411,7 +1479,7 @@ var InvalidBrowserNavigationUrlError = class extends Error {
|
|
|
1411
1479
|
}
|
|
1412
1480
|
};
|
|
1413
1481
|
function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
1414
|
-
return { ssrfPolicy };
|
|
1482
|
+
return ssrfPolicy ? { ssrfPolicy } : {};
|
|
1415
1483
|
}
|
|
1416
1484
|
var NETWORK_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
1417
1485
|
var SAFE_NON_NETWORK_URLS = /* @__PURE__ */ new Set(["about:blank"]);
|
|
@@ -1524,6 +1592,14 @@ function extractEmbeddedIpv4FromIpv6(v6, opts) {
|
|
|
1524
1592
|
return true;
|
|
1525
1593
|
}
|
|
1526
1594
|
}
|
|
1595
|
+
if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 65535 && parts[5] === 0) {
|
|
1596
|
+
const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
|
|
1597
|
+
try {
|
|
1598
|
+
return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
|
|
1599
|
+
} catch {
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1527
1603
|
if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0) {
|
|
1528
1604
|
const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
|
|
1529
1605
|
try {
|
|
@@ -1840,6 +1916,13 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
|
|
|
1840
1916
|
}
|
|
1841
1917
|
|
|
1842
1918
|
// src/actions/interaction.ts
|
|
1919
|
+
var MAX_CLICK_DELAY_MS = 5e3;
|
|
1920
|
+
function resolveBoundedDelayMs(value, label, maxMs) {
|
|
1921
|
+
const normalized = Math.floor(value ?? 0);
|
|
1922
|
+
if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
|
|
1923
|
+
if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${maxMs}ms`);
|
|
1924
|
+
return normalized;
|
|
1925
|
+
}
|
|
1843
1926
|
async function clickViaPlaywright(opts) {
|
|
1844
1927
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1845
1928
|
ensurePageState(page);
|
|
@@ -1847,6 +1930,11 @@ async function clickViaPlaywright(opts) {
|
|
|
1847
1930
|
const locator = refLocator(page, opts.ref);
|
|
1848
1931
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
|
|
1849
1932
|
try {
|
|
1933
|
+
const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
|
|
1934
|
+
if (delayMs > 0) {
|
|
1935
|
+
await locator.hover({ timeout });
|
|
1936
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
1937
|
+
}
|
|
1850
1938
|
if (opts.doubleClick) {
|
|
1851
1939
|
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
1852
1940
|
} else {
|
|
@@ -2157,12 +2245,14 @@ async function resizeViewportViaPlaywright(opts) {
|
|
|
2157
2245
|
}
|
|
2158
2246
|
|
|
2159
2247
|
// src/actions/wait.ts
|
|
2248
|
+
var MAX_WAIT_TIME_MS = 3e4;
|
|
2160
2249
|
async function waitForViaPlaywright(opts) {
|
|
2161
2250
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2162
2251
|
ensurePageState(page);
|
|
2163
2252
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
2164
2253
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
2165
|
-
|
|
2254
|
+
const bounded = Math.max(0, Math.min(MAX_WAIT_TIME_MS, Math.floor(opts.timeMs)));
|
|
2255
|
+
await page.waitForTimeout(bounded);
|
|
2166
2256
|
}
|
|
2167
2257
|
if (opts.text) {
|
|
2168
2258
|
await page.getByText(opts.text).first().waitFor({ state: "visible", timeout });
|
|
@@ -2851,6 +2941,7 @@ var CrawlPage = class {
|
|
|
2851
2941
|
doubleClick: opts?.doubleClick,
|
|
2852
2942
|
button: opts?.button,
|
|
2853
2943
|
modifiers: opts?.modifiers,
|
|
2944
|
+
delayMs: opts?.delayMs,
|
|
2854
2945
|
timeoutMs: opts?.timeoutMs
|
|
2855
2946
|
});
|
|
2856
2947
|
}
|