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 +107 -20
- 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 +107 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -227,6 +227,8 @@ interface ClickOptions {
|
|
|
227
227
|
button?: 'left' | 'right' | 'middle';
|
|
228
228
|
/** Modifier keys to hold during click */
|
|
229
229
|
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
|
|
230
|
+
/** Delay in ms between hover and click (hovers first, waits, then clicks). Max: `5000` */
|
|
231
|
+
delayMs?: number;
|
|
230
232
|
/** Timeout in milliseconds. Default: `8000` */
|
|
231
233
|
timeoutMs?: number;
|
|
232
234
|
}
|
|
@@ -1238,8 +1240,8 @@ declare function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: Ssrf
|
|
|
1238
1240
|
declare function ensureContextState(context: BrowserContext): ContextState;
|
|
1239
1241
|
/**
|
|
1240
1242
|
* Force-disconnect a Playwright browser connection for a given CDP target.
|
|
1241
|
-
* Clears the connection cache, sends Runtime.terminateExecution via CDP
|
|
1242
|
-
*
|
|
1243
|
+
* Clears the connection cache, sends Runtime.terminateExecution via raw CDP
|
|
1244
|
+
* websocket to kill stuck evals (bypassing Playwright), and closes the browser.
|
|
1243
1245
|
*/
|
|
1244
1246
|
declare function forceDisconnectPlaywrightForTarget(opts: {
|
|
1245
1247
|
cdpUrl: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -227,6 +227,8 @@ interface ClickOptions {
|
|
|
227
227
|
button?: 'left' | 'right' | 'middle';
|
|
228
228
|
/** Modifier keys to hold during click */
|
|
229
229
|
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
|
|
230
|
+
/** Delay in ms between hover and click (hovers first, waits, then clicks). Max: `5000` */
|
|
231
|
+
delayMs?: number;
|
|
230
232
|
/** Timeout in milliseconds. Default: `8000` */
|
|
231
233
|
timeoutMs?: number;
|
|
232
234
|
}
|
|
@@ -1238,8 +1240,8 @@ declare function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: Ssrf
|
|
|
1238
1240
|
declare function ensureContextState(context: BrowserContext): ContextState;
|
|
1239
1241
|
/**
|
|
1240
1242
|
* Force-disconnect a Playwright browser connection for a given CDP target.
|
|
1241
|
-
* Clears the connection cache, sends Runtime.terminateExecution via CDP
|
|
1242
|
-
*
|
|
1243
|
+
* Clears the connection cache, sends Runtime.terminateExecution via raw CDP
|
|
1244
|
+
* websocket to kill stuck evals (bypassing Playwright), and closes the browser.
|
|
1243
1245
|
*/
|
|
1244
1246
|
declare function forceDisconnectPlaywrightForTarget(opts: {
|
|
1245
1247
|
cdpUrl: string;
|
package/dist/index.js
CHANGED
|
@@ -813,6 +813,7 @@ async function connectBrowser(cdpUrl, authToken) {
|
|
|
813
813
|
return connected;
|
|
814
814
|
} catch (err) {
|
|
815
815
|
lastErr = err;
|
|
816
|
+
if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) break;
|
|
816
817
|
await new Promise((r) => setTimeout(r, 250 + attempt * 250));
|
|
817
818
|
}
|
|
818
819
|
}
|
|
@@ -838,6 +839,55 @@ async function disconnectBrowser() {
|
|
|
838
839
|
if (cur) await cur.browser.close().catch(() => {
|
|
839
840
|
});
|
|
840
841
|
}
|
|
842
|
+
async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
|
|
843
|
+
const httpBase = normalizeCdpHttpBaseForJsonEndpoints(cdpUrl);
|
|
844
|
+
const ctrl = new AbortController();
|
|
845
|
+
const t = setTimeout(() => ctrl.abort(), 2e3);
|
|
846
|
+
let targets;
|
|
847
|
+
try {
|
|
848
|
+
const res = await fetch(`${httpBase}/json/list`, { signal: ctrl.signal });
|
|
849
|
+
if (!res.ok) return;
|
|
850
|
+
targets = await res.json();
|
|
851
|
+
} catch {
|
|
852
|
+
return;
|
|
853
|
+
} finally {
|
|
854
|
+
clearTimeout(t);
|
|
855
|
+
}
|
|
856
|
+
if (!Array.isArray(targets)) return;
|
|
857
|
+
const target = targets.find((entry) => String(entry?.id ?? "").trim() === targetId);
|
|
858
|
+
const wsUrl = String(target?.webSocketDebuggerUrl ?? "").trim();
|
|
859
|
+
if (!wsUrl) return;
|
|
860
|
+
await new Promise((resolve2) => {
|
|
861
|
+
let done = false;
|
|
862
|
+
const finish = () => {
|
|
863
|
+
if (done) return;
|
|
864
|
+
done = true;
|
|
865
|
+
clearTimeout(timer);
|
|
866
|
+
try {
|
|
867
|
+
ws.close();
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
resolve2();
|
|
871
|
+
};
|
|
872
|
+
const timer = setTimeout(finish, 2e3);
|
|
873
|
+
let ws;
|
|
874
|
+
try {
|
|
875
|
+
ws = new WebSocket(wsUrl);
|
|
876
|
+
} catch {
|
|
877
|
+
finish();
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
ws.onopen = () => {
|
|
881
|
+
try {
|
|
882
|
+
ws.send(JSON.stringify({ id: 1, method: "Runtime.terminateExecution" }));
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
885
|
+
setTimeout(finish, 300);
|
|
886
|
+
};
|
|
887
|
+
ws.onerror = () => finish();
|
|
888
|
+
ws.onclose = () => finish();
|
|
889
|
+
});
|
|
890
|
+
}
|
|
841
891
|
async function forceDisconnectPlaywrightForTarget(opts) {
|
|
842
892
|
const normalized = normalizeCdpUrl(opts.cdpUrl);
|
|
843
893
|
const cur = cached;
|
|
@@ -846,23 +896,8 @@ async function forceDisconnectPlaywrightForTarget(opts) {
|
|
|
846
896
|
connectingByUrl.delete(normalized);
|
|
847
897
|
const targetId = opts.targetId?.trim() || "";
|
|
848
898
|
if (targetId) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
for (const page of pages) {
|
|
852
|
-
const tid = await pageTargetId(page).catch(() => null);
|
|
853
|
-
if (tid === targetId) {
|
|
854
|
-
const session = await page.context().newCDPSession(page);
|
|
855
|
-
try {
|
|
856
|
-
await session.send("Runtime.terminateExecution");
|
|
857
|
-
} finally {
|
|
858
|
-
await session.detach().catch(() => {
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
} catch {
|
|
865
|
-
}
|
|
899
|
+
await tryTerminateExecutionViaCdp(normalized, targetId).catch(() => {
|
|
900
|
+
});
|
|
866
901
|
}
|
|
867
902
|
cur.browser.close().catch(() => {
|
|
868
903
|
});
|
|
@@ -1284,6 +1319,35 @@ async function snapshotRole(opts) {
|
|
|
1284
1319
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1285
1320
|
ensurePageState(page);
|
|
1286
1321
|
const sourceUrl = page.url();
|
|
1322
|
+
if (opts.refsMode === "aria") {
|
|
1323
|
+
if (opts.selector?.trim() || opts.frameSelector?.trim()) {
|
|
1324
|
+
throw new Error("refs=aria does not support selector/frame snapshots yet.");
|
|
1325
|
+
}
|
|
1326
|
+
const maybe = page;
|
|
1327
|
+
if (!maybe._snapshotForAI) {
|
|
1328
|
+
throw new Error("refs=aria requires Playwright _snapshotForAI support.");
|
|
1329
|
+
}
|
|
1330
|
+
const result = await maybe._snapshotForAI({ timeout: 5e3, track: "response" });
|
|
1331
|
+
const built2 = buildRoleSnapshotFromAiSnapshot(String(result?.full ?? ""), opts.options);
|
|
1332
|
+
storeRoleRefsForTarget({
|
|
1333
|
+
page,
|
|
1334
|
+
cdpUrl: opts.cdpUrl,
|
|
1335
|
+
targetId: opts.targetId,
|
|
1336
|
+
refs: built2.refs,
|
|
1337
|
+
mode: "aria"
|
|
1338
|
+
});
|
|
1339
|
+
return {
|
|
1340
|
+
snapshot: built2.snapshot,
|
|
1341
|
+
refs: built2.refs,
|
|
1342
|
+
stats: getRoleSnapshotStats(built2.snapshot, built2.refs),
|
|
1343
|
+
untrusted: true,
|
|
1344
|
+
contentMeta: {
|
|
1345
|
+
sourceUrl,
|
|
1346
|
+
contentType: "browser-snapshot",
|
|
1347
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1287
1351
|
const frameSelector = opts.frameSelector?.trim() || "";
|
|
1288
1352
|
const selector = opts.selector?.trim() || "";
|
|
1289
1353
|
const locator = frameSelector ? selector ? page.frameLocator(frameSelector).locator(selector) : page.frameLocator(frameSelector).locator(":root") : selector ? page.locator(selector) : page.locator(":root");
|
|
@@ -1295,7 +1359,7 @@ async function snapshotRole(opts) {
|
|
|
1295
1359
|
targetId: opts.targetId,
|
|
1296
1360
|
refs: built.refs,
|
|
1297
1361
|
frameSelector: frameSelector || void 0,
|
|
1298
|
-
mode:
|
|
1362
|
+
mode: "role"
|
|
1299
1363
|
});
|
|
1300
1364
|
return {
|
|
1301
1365
|
snapshot: built.snapshot,
|
|
@@ -1383,7 +1447,7 @@ var InvalidBrowserNavigationUrlError = class extends Error {
|
|
|
1383
1447
|
}
|
|
1384
1448
|
};
|
|
1385
1449
|
function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
1386
|
-
return { ssrfPolicy };
|
|
1450
|
+
return ssrfPolicy ? { ssrfPolicy } : {};
|
|
1387
1451
|
}
|
|
1388
1452
|
var NETWORK_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
1389
1453
|
var SAFE_NON_NETWORK_URLS = /* @__PURE__ */ new Set(["about:blank"]);
|
|
@@ -1496,6 +1560,14 @@ function extractEmbeddedIpv4FromIpv6(v6, opts) {
|
|
|
1496
1560
|
return true;
|
|
1497
1561
|
}
|
|
1498
1562
|
}
|
|
1563
|
+
if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 65535 && parts[5] === 0) {
|
|
1564
|
+
const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
|
|
1565
|
+
try {
|
|
1566
|
+
return isBlockedSpecialUseIpv4Address(ipaddr.IPv4.parse(ip4str), opts);
|
|
1567
|
+
} catch {
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1499
1571
|
if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0) {
|
|
1500
1572
|
const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
|
|
1501
1573
|
try {
|
|
@@ -1812,6 +1884,13 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
|
|
|
1812
1884
|
}
|
|
1813
1885
|
|
|
1814
1886
|
// src/actions/interaction.ts
|
|
1887
|
+
var MAX_CLICK_DELAY_MS = 5e3;
|
|
1888
|
+
function resolveBoundedDelayMs(value, label, maxMs) {
|
|
1889
|
+
const normalized = Math.floor(value ?? 0);
|
|
1890
|
+
if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
|
|
1891
|
+
if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${maxMs}ms`);
|
|
1892
|
+
return normalized;
|
|
1893
|
+
}
|
|
1815
1894
|
async function clickViaPlaywright(opts) {
|
|
1816
1895
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1817
1896
|
ensurePageState(page);
|
|
@@ -1819,6 +1898,11 @@ async function clickViaPlaywright(opts) {
|
|
|
1819
1898
|
const locator = refLocator(page, opts.ref);
|
|
1820
1899
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
|
|
1821
1900
|
try {
|
|
1901
|
+
const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
|
|
1902
|
+
if (delayMs > 0) {
|
|
1903
|
+
await locator.hover({ timeout });
|
|
1904
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
1905
|
+
}
|
|
1822
1906
|
if (opts.doubleClick) {
|
|
1823
1907
|
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
1824
1908
|
} else {
|
|
@@ -2129,12 +2213,14 @@ async function resizeViewportViaPlaywright(opts) {
|
|
|
2129
2213
|
}
|
|
2130
2214
|
|
|
2131
2215
|
// src/actions/wait.ts
|
|
2216
|
+
var MAX_WAIT_TIME_MS = 3e4;
|
|
2132
2217
|
async function waitForViaPlaywright(opts) {
|
|
2133
2218
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2134
2219
|
ensurePageState(page);
|
|
2135
2220
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
2136
2221
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
2137
|
-
|
|
2222
|
+
const bounded = Math.max(0, Math.min(MAX_WAIT_TIME_MS, Math.floor(opts.timeMs)));
|
|
2223
|
+
await page.waitForTimeout(bounded);
|
|
2138
2224
|
}
|
|
2139
2225
|
if (opts.text) {
|
|
2140
2226
|
await page.getByText(opts.text).first().waitFor({ state: "visible", timeout });
|
|
@@ -2823,6 +2909,7 @@ var CrawlPage = class {
|
|
|
2823
2909
|
doubleClick: opts?.doubleClick,
|
|
2824
2910
|
button: opts?.button,
|
|
2825
2911
|
modifiers: opts?.modifiers,
|
|
2912
|
+
delayMs: opts?.delayMs,
|
|
2826
2913
|
timeoutMs: opts?.timeoutMs
|
|
2827
2914
|
});
|
|
2828
2915
|
}
|