browserclaw 0.12.2 → 0.12.4
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 +499 -267
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +499 -267
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -823,6 +823,19 @@ var require_ipaddr = __commonJS({
|
|
|
823
823
|
})(exports$1);
|
|
824
824
|
}
|
|
825
825
|
});
|
|
826
|
+
function killProcessTree(proc, signal) {
|
|
827
|
+
if (process.platform !== "win32" && proc.pid !== void 0) {
|
|
828
|
+
try {
|
|
829
|
+
process.kill(-proc.pid, signal);
|
|
830
|
+
return;
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
proc.kill(signal);
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
}
|
|
826
839
|
var CHROMIUM_BUNDLE_IDS = /* @__PURE__ */ new Set([
|
|
827
840
|
"com.google.Chrome",
|
|
828
841
|
"com.google.Chrome.beta",
|
|
@@ -1178,17 +1191,30 @@ function resolveBrowserExecutable(opts) {
|
|
|
1178
1191
|
if (platform === "win32") return detectDefaultChromiumWindows() ?? findChromeWindows();
|
|
1179
1192
|
return null;
|
|
1180
1193
|
}
|
|
1181
|
-
async function ensurePortAvailable(port) {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1194
|
+
async function ensurePortAvailable(port, retries = 2) {
|
|
1195
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
1196
|
+
try {
|
|
1197
|
+
await new Promise((resolve2, reject) => {
|
|
1198
|
+
const tester = net.createServer().once("error", (err) => {
|
|
1199
|
+
tester.close(() => {
|
|
1200
|
+
if (err.code === "EADDRINUSE") reject(new Error(`Port ${String(port)} is already in use`));
|
|
1201
|
+
else reject(err);
|
|
1202
|
+
});
|
|
1203
|
+
}).once("listening", () => {
|
|
1204
|
+
tester.close(() => {
|
|
1205
|
+
resolve2();
|
|
1206
|
+
});
|
|
1207
|
+
}).listen(port);
|
|
1189
1208
|
});
|
|
1190
|
-
|
|
1191
|
-
|
|
1209
|
+
return;
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
if (attempt < retries) {
|
|
1212
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
throw err;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1192
1218
|
}
|
|
1193
1219
|
function safeReadJson(filePath) {
|
|
1194
1220
|
try {
|
|
@@ -1461,7 +1487,7 @@ async function launchChrome(opts = {}) {
|
|
|
1461
1487
|
const profileName = opts.profileName ?? DEFAULT_PROFILE_NAME;
|
|
1462
1488
|
const userDataDir = opts.userDataDir ?? resolveUserDataDir(profileName);
|
|
1463
1489
|
fs.mkdirSync(userDataDir, { recursive: true });
|
|
1464
|
-
const spawnChrome = () => {
|
|
1490
|
+
const spawnChrome = (spawnOpts) => {
|
|
1465
1491
|
const args = [
|
|
1466
1492
|
`--remote-debugging-port=${String(cdpPort)}`,
|
|
1467
1493
|
"--remote-debugging-address=127.0.0.1",
|
|
@@ -1492,33 +1518,29 @@ async function launchChrome(opts = {}) {
|
|
|
1492
1518
|
args.push("about:blank");
|
|
1493
1519
|
return spawn(exe.path, args, {
|
|
1494
1520
|
stdio: "pipe",
|
|
1495
|
-
env: { ...process.env, HOME: os.homedir() }
|
|
1521
|
+
env: { ...process.env, HOME: os.homedir() },
|
|
1522
|
+
...spawnOpts
|
|
1496
1523
|
});
|
|
1497
1524
|
};
|
|
1498
1525
|
const startedAt = Date.now();
|
|
1499
1526
|
const localStatePath = path.join(userDataDir, "Local State");
|
|
1500
1527
|
const preferencesPath = path.join(userDataDir, "Default", "Preferences");
|
|
1501
1528
|
if (!fileExists(localStatePath) || !fileExists(preferencesPath)) {
|
|
1502
|
-
const
|
|
1529
|
+
const useDetached = process.platform !== "win32";
|
|
1530
|
+
const bootstrap = spawnChrome(useDetached ? { detached: true } : void 0);
|
|
1503
1531
|
const deadline = Date.now() + 1e4;
|
|
1504
1532
|
while (Date.now() < deadline) {
|
|
1505
1533
|
if (fileExists(localStatePath) && fileExists(preferencesPath)) break;
|
|
1506
1534
|
await new Promise((r) => setTimeout(r, 100));
|
|
1507
1535
|
}
|
|
1508
|
-
|
|
1509
|
-
bootstrap.kill("SIGTERM");
|
|
1510
|
-
} catch {
|
|
1511
|
-
}
|
|
1536
|
+
killProcessTree(bootstrap, "SIGTERM");
|
|
1512
1537
|
const exitDeadline = Date.now() + 5e3;
|
|
1513
1538
|
while (Date.now() < exitDeadline) {
|
|
1514
1539
|
if (bootstrap.exitCode != null) break;
|
|
1515
1540
|
await new Promise((r) => setTimeout(r, 50));
|
|
1516
1541
|
}
|
|
1517
1542
|
if (bootstrap.exitCode == null) {
|
|
1518
|
-
|
|
1519
|
-
bootstrap.kill("SIGKILL");
|
|
1520
|
-
} catch {
|
|
1521
|
-
}
|
|
1543
|
+
killProcessTree(bootstrap, "SIGKILL");
|
|
1522
1544
|
}
|
|
1523
1545
|
}
|
|
1524
1546
|
try {
|
|
@@ -1537,9 +1559,11 @@ async function launchChrome(opts = {}) {
|
|
|
1537
1559
|
};
|
|
1538
1560
|
proc.stderr.on("data", onStderr);
|
|
1539
1561
|
const readyDeadline = Date.now() + 15e3;
|
|
1562
|
+
let pollDelay = 200;
|
|
1540
1563
|
while (Date.now() < readyDeadline) {
|
|
1541
1564
|
if (await isChromeCdpReady(cdpUrl, 500)) break;
|
|
1542
|
-
await new Promise((r) => setTimeout(r,
|
|
1565
|
+
await new Promise((r) => setTimeout(r, pollDelay));
|
|
1566
|
+
pollDelay = Math.min(pollDelay + 100, 1e3);
|
|
1543
1567
|
}
|
|
1544
1568
|
if (!await isChromeCdpReady(cdpUrl, 500)) {
|
|
1545
1569
|
const stderrOutput = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
@@ -1551,6 +1575,11 @@ ${stderrOutput.slice(0, 2e3)}` : "";
|
|
|
1551
1575
|
proc.kill("SIGKILL");
|
|
1552
1576
|
} catch {
|
|
1553
1577
|
}
|
|
1578
|
+
try {
|
|
1579
|
+
const lockFile = path.join(userDataDir, "SingletonLock");
|
|
1580
|
+
if (fs.existsSync(lockFile)) fs.unlinkSync(lockFile);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1554
1583
|
throw new Error(`Failed to start Chrome CDP on port ${String(cdpPort)}.${sandboxHint}${stderrHint}`);
|
|
1555
1584
|
}
|
|
1556
1585
|
proc.stderr.off("data", onStderr);
|
|
@@ -1569,19 +1598,13 @@ ${stderrOutput.slice(0, 2e3)}` : "";
|
|
|
1569
1598
|
async function stopChrome(running, timeoutMs = 2500) {
|
|
1570
1599
|
const proc = running.proc;
|
|
1571
1600
|
if (proc.exitCode !== null) return;
|
|
1572
|
-
|
|
1573
|
-
proc.kill("SIGTERM");
|
|
1574
|
-
} catch {
|
|
1575
|
-
}
|
|
1601
|
+
killProcessTree(proc, "SIGTERM");
|
|
1576
1602
|
const start = Date.now();
|
|
1577
1603
|
while (Date.now() - start < timeoutMs) {
|
|
1578
1604
|
if (proc.exitCode !== null) return;
|
|
1579
1605
|
await new Promise((r) => setTimeout(r, 100));
|
|
1580
1606
|
}
|
|
1581
|
-
|
|
1582
|
-
proc.kill("SIGKILL");
|
|
1583
|
-
} catch {
|
|
1584
|
-
}
|
|
1607
|
+
killProcessTree(proc, "SIGKILL");
|
|
1585
1608
|
}
|
|
1586
1609
|
|
|
1587
1610
|
// src/stealth.ts
|
|
@@ -1654,6 +1677,9 @@ var STEALTH_SCRIPT = `(function() {
|
|
|
1654
1677
|
});
|
|
1655
1678
|
|
|
1656
1679
|
// \u2500\u2500 4. window.chrome \u2500\u2500
|
|
1680
|
+
// Stub the chrome.runtime API surface that detection scripts probe for.
|
|
1681
|
+
// Only applied when the real chrome.runtime.connect is absent (headless/CDP mode).
|
|
1682
|
+
// The stubs are intentionally non-functional \u2014 they exist solely to pass presence checks.
|
|
1657
1683
|
p(function() {
|
|
1658
1684
|
if (window.chrome && window.chrome.runtime && window.chrome.runtime.connect) return;
|
|
1659
1685
|
|
|
@@ -1704,6 +1730,9 @@ var STEALTH_SCRIPT = `(function() {
|
|
|
1704
1730
|
});
|
|
1705
1731
|
|
|
1706
1732
|
// \u2500\u2500 6. WebGL vendor / renderer \u2500\u2500
|
|
1733
|
+
// Hardcoded to Intel Iris \u2014 the most common discrete GPU on macOS. These strings
|
|
1734
|
+
// are fingerprinting targets; a more sophisticated approach would randomize per-session,
|
|
1735
|
+
// but static values are sufficient to avoid the default "Google SwiftShader" headless signal.
|
|
1707
1736
|
p(function() {
|
|
1708
1737
|
var h = {
|
|
1709
1738
|
apply: function(target, self, args) {
|
|
@@ -1839,7 +1868,7 @@ function ensurePageState(page) {
|
|
|
1839
1868
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1840
1869
|
location: msg.location()
|
|
1841
1870
|
});
|
|
1842
|
-
if (state.console.length > MAX_CONSOLE_MESSAGES) state.console.
|
|
1871
|
+
if (state.console.length > MAX_CONSOLE_MESSAGES + 50) state.console.splice(0, 50);
|
|
1843
1872
|
});
|
|
1844
1873
|
page.on("pageerror", (err) => {
|
|
1845
1874
|
state.errors.push({
|
|
@@ -1848,7 +1877,7 @@ function ensurePageState(page) {
|
|
|
1848
1877
|
stack: err.stack !== void 0 && err.stack !== "" ? err.stack : void 0,
|
|
1849
1878
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1850
1879
|
});
|
|
1851
|
-
if (state.errors.length > MAX_PAGE_ERRORS) state.errors.
|
|
1880
|
+
if (state.errors.length > MAX_PAGE_ERRORS + 20) state.errors.splice(0, 20);
|
|
1852
1881
|
});
|
|
1853
1882
|
page.on("request", (req) => {
|
|
1854
1883
|
state.nextRequestId += 1;
|
|
@@ -1861,7 +1890,7 @@ function ensurePageState(page) {
|
|
|
1861
1890
|
url: req.url(),
|
|
1862
1891
|
resourceType: req.resourceType()
|
|
1863
1892
|
});
|
|
1864
|
-
if (state.requests.length > MAX_NETWORK_REQUESTS) state.requests.
|
|
1893
|
+
if (state.requests.length > MAX_NETWORK_REQUESTS + 50) state.requests.splice(0, 50);
|
|
1865
1894
|
});
|
|
1866
1895
|
page.on("response", (resp) => {
|
|
1867
1896
|
const req = resp.request();
|
|
@@ -1935,31 +1964,40 @@ function setDialogHandlerOnPage(page, handler) {
|
|
|
1935
1964
|
const state = ensurePageState(page);
|
|
1936
1965
|
state.dialogHandler = handler;
|
|
1937
1966
|
}
|
|
1938
|
-
function applyStealthToPage(page) {
|
|
1939
|
-
|
|
1967
|
+
async function applyStealthToPage(page) {
|
|
1968
|
+
try {
|
|
1969
|
+
await page.evaluate(STEALTH_SCRIPT);
|
|
1970
|
+
} catch (e) {
|
|
1940
1971
|
if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
|
|
1941
1972
|
console.warn("[browserclaw] stealth evaluate failed:", e instanceof Error ? e.message : String(e));
|
|
1942
|
-
}
|
|
1973
|
+
}
|
|
1943
1974
|
}
|
|
1944
|
-
function observeContext(context) {
|
|
1975
|
+
async function observeContext(context) {
|
|
1945
1976
|
if (observedContexts.has(context)) return;
|
|
1946
1977
|
observedContexts.add(context);
|
|
1947
1978
|
ensureContextState(context);
|
|
1948
|
-
|
|
1979
|
+
try {
|
|
1980
|
+
await context.addInitScript(STEALTH_SCRIPT);
|
|
1981
|
+
} catch (e) {
|
|
1949
1982
|
if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
|
|
1950
1983
|
console.warn("[browserclaw] stealth initScript failed:", e instanceof Error ? e.message : String(e));
|
|
1951
|
-
}
|
|
1984
|
+
}
|
|
1952
1985
|
for (const page of context.pages()) {
|
|
1953
1986
|
ensurePageState(page);
|
|
1954
|
-
applyStealthToPage(page);
|
|
1987
|
+
await applyStealthToPage(page);
|
|
1955
1988
|
}
|
|
1956
|
-
|
|
1989
|
+
const onPage = (page) => {
|
|
1957
1990
|
ensurePageState(page);
|
|
1958
|
-
applyStealthToPage(page)
|
|
1991
|
+
applyStealthToPage(page).catch(() => {
|
|
1992
|
+
});
|
|
1993
|
+
};
|
|
1994
|
+
context.on("page", onPage);
|
|
1995
|
+
context.once("close", () => {
|
|
1996
|
+
context.off("page", onPage);
|
|
1959
1997
|
});
|
|
1960
1998
|
}
|
|
1961
|
-
function observeBrowser(browser) {
|
|
1962
|
-
for (const context of browser.contexts()) observeContext(context);
|
|
1999
|
+
async function observeBrowser(browser) {
|
|
2000
|
+
for (const context of browser.contexts()) await observeContext(context);
|
|
1963
2001
|
}
|
|
1964
2002
|
function toAIFriendlyError(error, selector) {
|
|
1965
2003
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1994,6 +2032,7 @@ function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
|
1994
2032
|
}
|
|
1995
2033
|
|
|
1996
2034
|
// src/ref-resolver.ts
|
|
2035
|
+
var REFS_STALENESS_THRESHOLD_MS = 3e4;
|
|
1997
2036
|
var roleRefsByTarget = /* @__PURE__ */ new Map();
|
|
1998
2037
|
var MAX_ROLE_REFS_CACHE = 50;
|
|
1999
2038
|
function normalizeCdpUrl(raw) {
|
|
@@ -2008,7 +2047,8 @@ function rememberRoleRefsForTarget(opts) {
|
|
|
2008
2047
|
roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), {
|
|
2009
2048
|
refs: opts.refs,
|
|
2010
2049
|
...opts.frameSelector !== void 0 && opts.frameSelector !== "" ? { frameSelector: opts.frameSelector } : {},
|
|
2011
|
-
...opts.mode !== void 0 ? { mode: opts.mode } : {}
|
|
2050
|
+
...opts.mode !== void 0 ? { mode: opts.mode } : {},
|
|
2051
|
+
storedAt: Date.now()
|
|
2012
2052
|
});
|
|
2013
2053
|
while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) {
|
|
2014
2054
|
const first = roleRefsByTarget.keys().next();
|
|
@@ -2021,6 +2061,7 @@ function storeRoleRefsForTarget(opts) {
|
|
|
2021
2061
|
state.roleRefs = opts.refs;
|
|
2022
2062
|
state.roleRefsFrameSelector = opts.frameSelector;
|
|
2023
2063
|
state.roleRefsMode = opts.mode;
|
|
2064
|
+
state.roleRefsStoredAt = Date.now();
|
|
2024
2065
|
if (opts.targetId === void 0 || opts.targetId.trim() === "") return;
|
|
2025
2066
|
rememberRoleRefsForTarget({
|
|
2026
2067
|
cdpUrl: opts.cdpUrl,
|
|
@@ -2030,17 +2071,6 @@ function storeRoleRefsForTarget(opts) {
|
|
|
2030
2071
|
mode: opts.mode
|
|
2031
2072
|
});
|
|
2032
2073
|
}
|
|
2033
|
-
function restoreRoleRefsForTarget(opts) {
|
|
2034
|
-
const targetId = opts.targetId?.trim() ?? "";
|
|
2035
|
-
if (targetId === "") return;
|
|
2036
|
-
const entry = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId));
|
|
2037
|
-
if (!entry) return;
|
|
2038
|
-
const state = ensurePageState(opts.page);
|
|
2039
|
-
if (state.roleRefs) return;
|
|
2040
|
-
state.roleRefs = entry.refs;
|
|
2041
|
-
state.roleRefsFrameSelector = entry.frameSelector;
|
|
2042
|
-
state.roleRefsMode = entry.mode;
|
|
2043
|
-
}
|
|
2044
2074
|
function clearRoleRefsForCdpUrl(cdpUrl) {
|
|
2045
2075
|
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2046
2076
|
for (const key of roleRefsByTarget.keys()) {
|
|
@@ -2079,6 +2109,14 @@ function refLocator(page, ref) {
|
|
|
2079
2109
|
if (normalized.trim() === "") throw new Error("ref is required");
|
|
2080
2110
|
if (/^e\d+$/.test(normalized)) {
|
|
2081
2111
|
const state = getPageState(page);
|
|
2112
|
+
if (state?.roleRefsStoredAt !== void 0) {
|
|
2113
|
+
const ageMs = Date.now() - state.roleRefsStoredAt;
|
|
2114
|
+
if (ageMs > REFS_STALENESS_THRESHOLD_MS) {
|
|
2115
|
+
console.warn(
|
|
2116
|
+
`[browserclaw] refs are ${String(Math.round(ageMs / 1e3))}s old \u2014 consider re-snapshotting for fresh refs`
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2082
2120
|
if (state?.roleRefsMode === "aria") {
|
|
2083
2121
|
return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
|
|
2084
2122
|
}
|
|
@@ -2127,14 +2165,16 @@ function appendCdpPath2(cdpUrl, cdpPath) {
|
|
|
2127
2165
|
}
|
|
2128
2166
|
async function withPlaywrightPageCdpSession(page, fn) {
|
|
2129
2167
|
const CDP_SESSION_TIMEOUT_MS = 1e4;
|
|
2168
|
+
let timer;
|
|
2130
2169
|
const session = await Promise.race([
|
|
2131
2170
|
page.context().newCDPSession(page),
|
|
2132
2171
|
new Promise((_, reject) => {
|
|
2133
|
-
setTimeout(() => {
|
|
2172
|
+
timer = setTimeout(() => {
|
|
2134
2173
|
reject(new Error("newCDPSession timed out after 10s"));
|
|
2135
2174
|
}, CDP_SESSION_TIMEOUT_MS);
|
|
2136
2175
|
})
|
|
2137
2176
|
]);
|
|
2177
|
+
clearTimeout(timer);
|
|
2138
2178
|
try {
|
|
2139
2179
|
return await fn(session);
|
|
2140
2180
|
} finally {
|
|
@@ -2160,8 +2200,10 @@ function isLoopbackCdpUrl(url) {
|
|
|
2160
2200
|
}
|
|
2161
2201
|
}
|
|
2162
2202
|
var envMutexPromise = Promise.resolve();
|
|
2203
|
+
var envMutexDepth = 0;
|
|
2163
2204
|
async function withNoProxyForCdpUrl(url, fn) {
|
|
2164
2205
|
if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return fn();
|
|
2206
|
+
if (envMutexDepth > 0) return fn();
|
|
2165
2207
|
const prev = envMutexPromise;
|
|
2166
2208
|
let release = () => {
|
|
2167
2209
|
};
|
|
@@ -2182,9 +2224,11 @@ async function withNoProxyForCdpUrl(url, fn) {
|
|
|
2182
2224
|
const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
|
|
2183
2225
|
process.env.NO_PROXY = applied;
|
|
2184
2226
|
process.env.no_proxy = applied;
|
|
2227
|
+
envMutexDepth += 1;
|
|
2185
2228
|
try {
|
|
2186
2229
|
return await fn();
|
|
2187
2230
|
} finally {
|
|
2231
|
+
envMutexDepth -= 1;
|
|
2188
2232
|
if (process.env.NO_PROXY === applied) {
|
|
2189
2233
|
if (savedNoProxy !== void 0) process.env.NO_PROXY = savedNoProxy;
|
|
2190
2234
|
else delete process.env.NO_PROXY;
|
|
@@ -2215,12 +2259,28 @@ function getHeadersWithAuth(endpoint, baseHeaders = {}) {
|
|
|
2215
2259
|
}
|
|
2216
2260
|
var cachedByCdpUrl = /* @__PURE__ */ new Map();
|
|
2217
2261
|
var connectingByCdpUrl = /* @__PURE__ */ new Map();
|
|
2262
|
+
var connectionMutex = Promise.resolve();
|
|
2263
|
+
async function withConnectionLock(fn) {
|
|
2264
|
+
const prev = connectionMutex;
|
|
2265
|
+
let release = () => {
|
|
2266
|
+
};
|
|
2267
|
+
connectionMutex = new Promise((r) => {
|
|
2268
|
+
release = r;
|
|
2269
|
+
});
|
|
2270
|
+
await prev;
|
|
2271
|
+
try {
|
|
2272
|
+
return await fn();
|
|
2273
|
+
} finally {
|
|
2274
|
+
release();
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2218
2277
|
var BlockedBrowserTargetError = class extends Error {
|
|
2219
2278
|
constructor() {
|
|
2220
2279
|
super("Browser target is unavailable after SSRF policy blocked its navigation.");
|
|
2221
2280
|
this.name = "BlockedBrowserTargetError";
|
|
2222
2281
|
}
|
|
2223
2282
|
};
|
|
2283
|
+
var MAX_BLOCKED_TARGETS = 200;
|
|
2224
2284
|
var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
|
|
2225
2285
|
var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
|
|
2226
2286
|
function blockedTargetKey(cdpUrl, targetId) {
|
|
@@ -2235,6 +2295,10 @@ function markTargetBlocked(cdpUrl, targetId) {
|
|
|
2235
2295
|
const normalized = targetId?.trim() ?? "";
|
|
2236
2296
|
if (normalized === "") return;
|
|
2237
2297
|
blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
|
|
2298
|
+
if (blockedTargetsByCdpUrl.size > MAX_BLOCKED_TARGETS) {
|
|
2299
|
+
const first = blockedTargetsByCdpUrl.values().next();
|
|
2300
|
+
if (first.done !== true) blockedTargetsByCdpUrl.delete(first.value);
|
|
2301
|
+
}
|
|
2238
2302
|
}
|
|
2239
2303
|
function clearBlockedTarget(cdpUrl, targetId) {
|
|
2240
2304
|
const normalized = targetId?.trim() ?? "";
|
|
@@ -2248,6 +2312,16 @@ function hasBlockedTargetsForCdpUrl(cdpUrl) {
|
|
|
2248
2312
|
}
|
|
2249
2313
|
return false;
|
|
2250
2314
|
}
|
|
2315
|
+
function clearBlockedTargetsForCdpUrl(cdpUrl) {
|
|
2316
|
+
if (cdpUrl === void 0) {
|
|
2317
|
+
blockedTargetsByCdpUrl.clear();
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
|
|
2321
|
+
for (const key of blockedTargetsByCdpUrl) {
|
|
2322
|
+
if (key.startsWith(prefix)) blockedTargetsByCdpUrl.delete(key);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2251
2325
|
function blockedPageRefsForCdpUrl(cdpUrl) {
|
|
2252
2326
|
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2253
2327
|
const existing = blockedPageRefsByCdpUrl.get(normalized);
|
|
@@ -2262,6 +2336,13 @@ function isBlockedPageRef(cdpUrl, page) {
|
|
|
2262
2336
|
function markPageRefBlocked(cdpUrl, page) {
|
|
2263
2337
|
blockedPageRefsForCdpUrl(cdpUrl).add(page);
|
|
2264
2338
|
}
|
|
2339
|
+
function clearBlockedPageRefsForCdpUrl(cdpUrl) {
|
|
2340
|
+
if (cdpUrl === void 0) {
|
|
2341
|
+
blockedPageRefsByCdpUrl.clear();
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
blockedPageRefsByCdpUrl.delete(normalizeCdpUrl(cdpUrl));
|
|
2345
|
+
}
|
|
2265
2346
|
function clearBlockedPageRef(cdpUrl, page) {
|
|
2266
2347
|
blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
|
|
2267
2348
|
}
|
|
@@ -2275,64 +2356,98 @@ async function connectBrowser(cdpUrl, authToken) {
|
|
|
2275
2356
|
if (existing_cached) return existing_cached;
|
|
2276
2357
|
const existing = connectingByCdpUrl.get(normalized);
|
|
2277
2358
|
if (existing) return await existing;
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
endpoint,
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2359
|
+
return withConnectionLock(async () => {
|
|
2360
|
+
const rechecked = cachedByCdpUrl.get(normalized);
|
|
2361
|
+
if (rechecked) return rechecked;
|
|
2362
|
+
const recheckPending = connectingByCdpUrl.get(normalized);
|
|
2363
|
+
if (recheckPending) return await recheckPending;
|
|
2364
|
+
const connectWithRetry = async () => {
|
|
2365
|
+
let lastErr;
|
|
2366
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2367
|
+
try {
|
|
2368
|
+
const timeout = 5e3 + attempt * 2e3;
|
|
2369
|
+
const endpoint = await getChromeWebSocketUrl(normalized, timeout, authToken).catch(() => null) ?? normalized;
|
|
2370
|
+
const headers = getHeadersWithAuth(endpoint);
|
|
2371
|
+
if (authToken !== void 0 && authToken !== "" && !headers.Authorization)
|
|
2372
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
2373
|
+
const browser = await withNoProxyForCdpUrl(
|
|
2374
|
+
endpoint,
|
|
2375
|
+
() => chromium.connectOverCDP(endpoint, { timeout, headers })
|
|
2376
|
+
);
|
|
2377
|
+
const onDisconnected = () => {
|
|
2378
|
+
if (cachedByCdpUrl.get(normalized)?.browser === browser) {
|
|
2379
|
+
cachedByCdpUrl.delete(normalized);
|
|
2380
|
+
clearRoleRefsForCdpUrl(normalized);
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
const connected = { browser, cdpUrl: normalized, onDisconnected };
|
|
2384
|
+
cachedByCdpUrl.set(normalized, connected);
|
|
2385
|
+
await observeBrowser(browser);
|
|
2386
|
+
browser.on("disconnected", onDisconnected);
|
|
2387
|
+
return connected;
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
lastErr = err;
|
|
2390
|
+
if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) {
|
|
2391
|
+
await new Promise((r) => setTimeout(r, 1e3 + attempt * 1e3));
|
|
2392
|
+
continue;
|
|
2295
2393
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
cachedByCdpUrl.set(normalized, connected);
|
|
2299
|
-
observeBrowser(browser);
|
|
2300
|
-
browser.on("disconnected", onDisconnected);
|
|
2301
|
-
return connected;
|
|
2302
|
-
} catch (err) {
|
|
2303
|
-
lastErr = err;
|
|
2304
|
-
if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) break;
|
|
2305
|
-
await new Promise((r) => setTimeout(r, 250 + attempt * 250));
|
|
2394
|
+
await new Promise((r) => setTimeout(r, 250 + attempt * 250));
|
|
2395
|
+
}
|
|
2306
2396
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2397
|
+
throw lastErr instanceof Error ? lastErr : new Error("CDP connect failed");
|
|
2398
|
+
};
|
|
2399
|
+
const promise = connectWithRetry().finally(() => {
|
|
2400
|
+
connectingByCdpUrl.delete(normalized);
|
|
2401
|
+
});
|
|
2402
|
+
connectingByCdpUrl.set(normalized, promise);
|
|
2403
|
+
return await promise;
|
|
2312
2404
|
});
|
|
2313
|
-
connectingByCdpUrl.set(normalized, promise);
|
|
2314
|
-
return await promise;
|
|
2315
2405
|
}
|
|
2316
2406
|
async function disconnectBrowser() {
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2407
|
+
return withConnectionLock(async () => {
|
|
2408
|
+
if (connectingByCdpUrl.size) {
|
|
2409
|
+
for (const p of connectingByCdpUrl.values()) {
|
|
2410
|
+
try {
|
|
2411
|
+
await p;
|
|
2412
|
+
} catch (err) {
|
|
2413
|
+
console.warn(
|
|
2414
|
+
`[browserclaw] disconnectBrowser: pending connect failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2325
2417
|
}
|
|
2326
2418
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
cur.browser.
|
|
2332
|
-
|
|
2419
|
+
for (const cur of cachedByCdpUrl.values()) {
|
|
2420
|
+
clearRoleRefsForCdpUrl(cur.cdpUrl);
|
|
2421
|
+
if (cur.onDisconnected && typeof cur.browser.off === "function")
|
|
2422
|
+
cur.browser.off("disconnected", cur.onDisconnected);
|
|
2423
|
+
await cur.browser.close().catch(() => {
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
cachedByCdpUrl.clear();
|
|
2427
|
+
clearBlockedTargetsForCdpUrl();
|
|
2428
|
+
clearBlockedPageRefsForCdpUrl();
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
async function closePlaywrightBrowserConnection(opts) {
|
|
2432
|
+
if (opts?.cdpUrl !== void 0 && opts.cdpUrl !== "") {
|
|
2433
|
+
return withConnectionLock(async () => {
|
|
2434
|
+
const cdpUrl = opts.cdpUrl;
|
|
2435
|
+
if (cdpUrl === void 0 || cdpUrl === "") return;
|
|
2436
|
+
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2437
|
+
clearBlockedTargetsForCdpUrl(normalized);
|
|
2438
|
+
clearBlockedPageRefsForCdpUrl(normalized);
|
|
2439
|
+
const cur = cachedByCdpUrl.get(normalized);
|
|
2440
|
+
cachedByCdpUrl.delete(normalized);
|
|
2441
|
+
connectingByCdpUrl.delete(normalized);
|
|
2442
|
+
if (!cur) return;
|
|
2443
|
+
if (cur.onDisconnected && typeof cur.browser.off === "function")
|
|
2444
|
+
cur.browser.off("disconnected", cur.onDisconnected);
|
|
2445
|
+
await cur.browser.close().catch(() => {
|
|
2446
|
+
});
|
|
2333
2447
|
});
|
|
2448
|
+
} else {
|
|
2449
|
+
await disconnectBrowser();
|
|
2334
2450
|
}
|
|
2335
|
-
cachedByCdpUrl.clear();
|
|
2336
2451
|
}
|
|
2337
2452
|
function cdpSocketNeedsAttach(wsUrl) {
|
|
2338
2453
|
try {
|
|
@@ -2441,7 +2556,7 @@ async function forceDisconnectPlaywrightConnection(opts) {
|
|
|
2441
2556
|
await tryTerminateExecutionViaCdp(normalized, targetId).catch(() => {
|
|
2442
2557
|
});
|
|
2443
2558
|
}
|
|
2444
|
-
cur.browser.close().catch(() => {
|
|
2559
|
+
await cur.browser.close().catch(() => {
|
|
2445
2560
|
});
|
|
2446
2561
|
}
|
|
2447
2562
|
var forceDisconnectPlaywrightForTarget = forceDisconnectPlaywrightConnection;
|
|
@@ -2452,17 +2567,13 @@ var pageTargetIdCache = /* @__PURE__ */ new WeakMap();
|
|
|
2452
2567
|
async function pageTargetId(page) {
|
|
2453
2568
|
const cached = pageTargetIdCache.get(page);
|
|
2454
2569
|
if (cached !== void 0) return cached;
|
|
2455
|
-
|
|
2456
|
-
try {
|
|
2570
|
+
return withPlaywrightPageCdpSession(page, async (session) => {
|
|
2457
2571
|
const info = await session.send("Target.getTargetInfo");
|
|
2458
2572
|
const targetInfo = info.targetInfo;
|
|
2459
2573
|
const id = (targetInfo?.targetId ?? "").trim() || null;
|
|
2460
2574
|
if (id !== null) pageTargetIdCache.set(page, id);
|
|
2461
2575
|
return id;
|
|
2462
|
-
}
|
|
2463
|
-
await session.detach().catch(() => {
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2576
|
+
});
|
|
2466
2577
|
}
|
|
2467
2578
|
function matchPageByTargetList(pages, targets, targetId) {
|
|
2468
2579
|
const target = targets.find((entry) => entry.id === targetId);
|
|
@@ -2498,7 +2609,6 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
|
2498
2609
|
}
|
|
2499
2610
|
})
|
|
2500
2611
|
);
|
|
2501
|
-
const resolvedViaCdp = results.some(({ tid }) => tid !== null);
|
|
2502
2612
|
const matched = results.find(({ tid }) => tid !== null && tid !== "" && tid === targetId);
|
|
2503
2613
|
if (matched) return matched.page;
|
|
2504
2614
|
if (cdpUrl !== void 0 && cdpUrl !== "") {
|
|
@@ -2507,7 +2617,6 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
|
2507
2617
|
} catch {
|
|
2508
2618
|
}
|
|
2509
2619
|
}
|
|
2510
|
-
if (!resolvedViaCdp && pages.length === 1) return pages[0] ?? null;
|
|
2511
2620
|
return null;
|
|
2512
2621
|
}
|
|
2513
2622
|
async function partitionAccessiblePages(opts) {
|
|
@@ -2550,12 +2659,14 @@ async function getPageForTargetId(opts) {
|
|
|
2550
2659
|
if (opts.targetId === void 0 || opts.targetId === "") return first;
|
|
2551
2660
|
const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
|
2552
2661
|
if (!found) {
|
|
2553
|
-
if (pages.length === 1) return first;
|
|
2554
2662
|
throw new BrowserTabNotFoundError(
|
|
2555
2663
|
`Tab not found (targetId: ${opts.targetId}). Call browser.tabs() to list open tabs.`
|
|
2556
2664
|
);
|
|
2557
2665
|
}
|
|
2558
2666
|
if (isBlockedPageRef(opts.cdpUrl, found)) throw new BlockedBrowserTargetError();
|
|
2667
|
+
const foundTargetId = await pageTargetId(found).catch(() => null);
|
|
2668
|
+
if (foundTargetId !== null && foundTargetId !== "" && isBlockedTarget(opts.cdpUrl, foundTargetId))
|
|
2669
|
+
throw new BlockedBrowserTargetError();
|
|
2559
2670
|
return found;
|
|
2560
2671
|
}
|
|
2561
2672
|
async function resolvePageByTargetIdOrThrow(opts) {
|
|
@@ -2567,7 +2678,6 @@ async function resolvePageByTargetIdOrThrow(opts) {
|
|
|
2567
2678
|
async function getRestoredPageForTarget(opts) {
|
|
2568
2679
|
const page = await getPageForTargetId(opts);
|
|
2569
2680
|
ensurePageState(page);
|
|
2570
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2571
2681
|
return page;
|
|
2572
2682
|
}
|
|
2573
2683
|
|
|
@@ -2621,10 +2731,11 @@ var BROWSER_EVALUATOR = new Function(
|
|
|
2621
2731
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2622
2732
|
' var result = typeof candidate === "function" ? candidate() : candidate;',
|
|
2623
2733
|
' if (result && typeof result.then === "function") {',
|
|
2734
|
+
" var tid;",
|
|
2624
2735
|
" return Promise.race([",
|
|
2625
|
-
" result,",
|
|
2736
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2626
2737
|
" new Promise(function(_, reject) {",
|
|
2627
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2738
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2628
2739
|
" })",
|
|
2629
2740
|
" ]);",
|
|
2630
2741
|
" }",
|
|
@@ -2646,10 +2757,11 @@ var ELEMENT_EVALUATOR = new Function(
|
|
|
2646
2757
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2647
2758
|
' var result = typeof candidate === "function" ? candidate(el) : candidate;',
|
|
2648
2759
|
' if (result && typeof result.then === "function") {',
|
|
2760
|
+
" var tid;",
|
|
2649
2761
|
" return Promise.race([",
|
|
2650
|
-
" result,",
|
|
2762
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2651
2763
|
" new Promise(function(_, reject) {",
|
|
2652
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2764
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2653
2765
|
" })",
|
|
2654
2766
|
" ]);",
|
|
2655
2767
|
" }",
|
|
@@ -2664,10 +2776,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2664
2776
|
if (!fnText) throw new Error("function is required");
|
|
2665
2777
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2666
2778
|
ensurePageState(page);
|
|
2667
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2668
2779
|
const outerTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
2669
|
-
|
|
2670
|
-
evaluateTimeout = Math.min(evaluateTimeout, outerTimeout);
|
|
2780
|
+
const evaluateTimeout = Math.max(1e3, Math.min(12e4, outerTimeout - 1e3));
|
|
2671
2781
|
const signal = opts.signal;
|
|
2672
2782
|
let abortListener;
|
|
2673
2783
|
let abortReject;
|
|
@@ -2681,10 +2791,16 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2681
2791
|
}
|
|
2682
2792
|
if (signal !== void 0) {
|
|
2683
2793
|
const disconnect = () => {
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2794
|
+
const targetId = opts.targetId?.trim() ?? "";
|
|
2795
|
+
if (targetId !== "") {
|
|
2796
|
+
tryTerminateExecutionViaCdp(opts.cdpUrl, targetId).catch(() => {
|
|
2797
|
+
});
|
|
2798
|
+
} else {
|
|
2799
|
+
console.warn("[browserclaw] evaluate abort: no targetId, forcing full disconnect");
|
|
2800
|
+
forceDisconnectPlaywrightConnection({
|
|
2801
|
+
cdpUrl: opts.cdpUrl}).catch(() => {
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2688
2804
|
};
|
|
2689
2805
|
if (signal.aborted) {
|
|
2690
2806
|
disconnect();
|
|
@@ -2720,6 +2836,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2720
2836
|
);
|
|
2721
2837
|
} finally {
|
|
2722
2838
|
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
2839
|
+
abortReject = void 0;
|
|
2840
|
+
abortListener = void 0;
|
|
2723
2841
|
}
|
|
2724
2842
|
}
|
|
2725
2843
|
|
|
@@ -2912,7 +3030,7 @@ function isBlockedHostnameOrIp(hostname, policy) {
|
|
|
2912
3030
|
function isPrivateIpAddress(address, policy) {
|
|
2913
3031
|
let normalized = address.trim().toLowerCase();
|
|
2914
3032
|
if (normalized.startsWith("[") && normalized.endsWith("]")) normalized = normalized.slice(1, -1);
|
|
2915
|
-
if (!normalized) return
|
|
3033
|
+
if (!normalized) return true;
|
|
2916
3034
|
const blockOptions = resolveIpv4SpecialUseBlockOptions(policy);
|
|
2917
3035
|
const strictIp = parseCanonicalIpAddress(normalized);
|
|
2918
3036
|
if (strictIp) {
|
|
@@ -2999,6 +3117,25 @@ function createPinnedLookup(params) {
|
|
|
2999
3117
|
cb(null, chosen.address, chosen.family);
|
|
3000
3118
|
});
|
|
3001
3119
|
}
|
|
3120
|
+
var DNS_CACHE_TTL_MS = 3e4;
|
|
3121
|
+
var MAX_DNS_CACHE_SIZE = 100;
|
|
3122
|
+
var dnsCache = /* @__PURE__ */ new Map();
|
|
3123
|
+
function getCachedDnsResult(hostname) {
|
|
3124
|
+
const entry = dnsCache.get(hostname);
|
|
3125
|
+
if (!entry) return void 0;
|
|
3126
|
+
if (Date.now() > entry.expiresAt) {
|
|
3127
|
+
dnsCache.delete(hostname);
|
|
3128
|
+
return void 0;
|
|
3129
|
+
}
|
|
3130
|
+
return entry.result;
|
|
3131
|
+
}
|
|
3132
|
+
function cacheDnsResult(hostname, result) {
|
|
3133
|
+
dnsCache.set(hostname, { result, expiresAt: Date.now() + DNS_CACHE_TTL_MS });
|
|
3134
|
+
if (dnsCache.size > MAX_DNS_CACHE_SIZE) {
|
|
3135
|
+
const first = dnsCache.keys().next();
|
|
3136
|
+
if (first.done !== true) dnsCache.delete(first.value);
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3002
3139
|
async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
3003
3140
|
const normalized = normalizeHostname(hostname);
|
|
3004
3141
|
if (!normalized) throw new InvalidBrowserNavigationUrlError(`Invalid hostname: "${hostname}"`);
|
|
@@ -3017,6 +3154,8 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3017
3154
|
);
|
|
3018
3155
|
}
|
|
3019
3156
|
}
|
|
3157
|
+
const cached = getCachedDnsResult(normalized);
|
|
3158
|
+
if (cached) return cached;
|
|
3020
3159
|
const lookupFn = params.lookupFn ?? lookup$1;
|
|
3021
3160
|
let results;
|
|
3022
3161
|
try {
|
|
@@ -3046,11 +3185,13 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3046
3185
|
`Navigation to internal/loopback address blocked: unable to resolve "${hostname}".`
|
|
3047
3186
|
);
|
|
3048
3187
|
}
|
|
3049
|
-
|
|
3188
|
+
const pinned = {
|
|
3050
3189
|
hostname: normalized,
|
|
3051
3190
|
addresses,
|
|
3052
3191
|
lookup: createPinnedLookup({ hostname: normalized, addresses })
|
|
3053
3192
|
};
|
|
3193
|
+
cacheDnsResult(normalized, pinned);
|
|
3194
|
+
return pinned;
|
|
3054
3195
|
}
|
|
3055
3196
|
async function assertBrowserNavigationAllowed(opts) {
|
|
3056
3197
|
const rawUrl = opts.url.trim();
|
|
@@ -3204,13 +3345,27 @@ function buildSiblingTempPath(targetPath) {
|
|
|
3204
3345
|
return join(dirname(targetPath), `.browserclaw-output-${id}-${safeTail}.part`);
|
|
3205
3346
|
}
|
|
3206
3347
|
async function writeViaSiblingTempPath(params) {
|
|
3207
|
-
|
|
3348
|
+
let rootDir;
|
|
3349
|
+
try {
|
|
3350
|
+
rootDir = await realpath(resolve(params.rootDir));
|
|
3351
|
+
} catch {
|
|
3352
|
+
console.warn(`[browserclaw] writeViaSiblingTempPath: rootDir realpath failed, using lexical resolve`);
|
|
3353
|
+
rootDir = resolve(params.rootDir);
|
|
3354
|
+
}
|
|
3208
3355
|
const requestedTargetPath = resolve(params.targetPath);
|
|
3209
3356
|
const targetPath = await realpath(dirname(requestedTargetPath)).then((realDir) => join(realDir, basename(requestedTargetPath))).catch(() => requestedTargetPath);
|
|
3210
3357
|
const relativeTargetPath = relative(rootDir, targetPath);
|
|
3211
3358
|
if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${sep}`) || isAbsolute(relativeTargetPath)) {
|
|
3212
3359
|
throw new Error("Target path is outside the allowed root");
|
|
3213
3360
|
}
|
|
3361
|
+
try {
|
|
3362
|
+
const stat = await lstat(targetPath);
|
|
3363
|
+
if (stat.isSymbolicLink()) {
|
|
3364
|
+
throw new Error(`Unsafe output path: "${params.targetPath}" is a symbolic link.`);
|
|
3365
|
+
}
|
|
3366
|
+
} catch (e) {
|
|
3367
|
+
if (e.code !== "ENOENT") throw e;
|
|
3368
|
+
}
|
|
3214
3369
|
const tempPath = buildSiblingTempPath(targetPath);
|
|
3215
3370
|
let renameSucceeded = false;
|
|
3216
3371
|
try {
|
|
@@ -3232,6 +3387,9 @@ async function assertBrowserNavigationResultAllowed(opts) {
|
|
|
3232
3387
|
} catch {
|
|
3233
3388
|
return;
|
|
3234
3389
|
}
|
|
3390
|
+
if (parsed.protocol === "data:" || parsed.protocol === "blob:") {
|
|
3391
|
+
throw new InvalidBrowserNavigationUrlError(`Navigation result blocked: "${parsed.protocol}" URLs are not allowed.`);
|
|
3392
|
+
}
|
|
3235
3393
|
if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || isAllowedNonNetworkNavigationUrl(parsed)) {
|
|
3236
3394
|
await assertBrowserNavigationAllowed(opts);
|
|
3237
3395
|
}
|
|
@@ -3349,9 +3507,10 @@ async function clickViaPlaywright(opts) {
|
|
|
3349
3507
|
if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
|
|
3350
3508
|
const POLL_INTERVAL_MS = 50;
|
|
3351
3509
|
const POLL_TIMEOUT_MS = 500;
|
|
3510
|
+
const ATTR_TIMEOUT_MS = Math.min(timeout, POLL_TIMEOUT_MS);
|
|
3352
3511
|
let changed = false;
|
|
3353
3512
|
for (let elapsed = 0; elapsed < POLL_TIMEOUT_MS; elapsed += POLL_INTERVAL_MS) {
|
|
3354
|
-
const current = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
|
|
3513
|
+
const current = await locator.getAttribute("aria-checked", { timeout: ATTR_TIMEOUT_MS }).catch(() => void 0);
|
|
3355
3514
|
if (current === void 0 || current !== ariaCheckedBefore) {
|
|
3356
3515
|
changed = true;
|
|
3357
3516
|
break;
|
|
@@ -3428,6 +3587,7 @@ async function dragViaPlaywright(opts) {
|
|
|
3428
3587
|
async function fillFormViaPlaywright(opts) {
|
|
3429
3588
|
const page = await getRestoredPageForTarget(opts);
|
|
3430
3589
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3590
|
+
let filledCount = 0;
|
|
3431
3591
|
for (const field of opts.fields) {
|
|
3432
3592
|
const ref = field.ref.trim();
|
|
3433
3593
|
const type = (typeof field.type === "string" ? field.type.trim() : "") || "text";
|
|
@@ -3446,16 +3606,24 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3446
3606
|
try {
|
|
3447
3607
|
await setCheckedViaEvaluate(locator, checked);
|
|
3448
3608
|
} catch (err) {
|
|
3449
|
-
|
|
3609
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3610
|
+
throw new Error(
|
|
3611
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3612
|
+
);
|
|
3450
3613
|
}
|
|
3451
3614
|
}
|
|
3615
|
+
filledCount += 1;
|
|
3452
3616
|
continue;
|
|
3453
3617
|
}
|
|
3454
3618
|
try {
|
|
3455
3619
|
await locator.fill(value, { timeout });
|
|
3456
3620
|
} catch (err) {
|
|
3457
|
-
|
|
3621
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3622
|
+
throw new Error(
|
|
3623
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3624
|
+
);
|
|
3458
3625
|
}
|
|
3626
|
+
filledCount += 1;
|
|
3459
3627
|
}
|
|
3460
3628
|
}
|
|
3461
3629
|
async function scrollIntoViewViaPlaywright(opts) {
|
|
@@ -3521,16 +3689,22 @@ async function armDialogViaPlaywright(opts) {
|
|
|
3521
3689
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3522
3690
|
state.armIdDialog = bumpDialogArmId(state);
|
|
3523
3691
|
const armId = state.armIdDialog;
|
|
3692
|
+
const resetArm = () => {
|
|
3693
|
+
if (state.armIdDialog === armId) state.armIdDialog = 0;
|
|
3694
|
+
};
|
|
3695
|
+
page.once("close", resetArm);
|
|
3524
3696
|
page.waitForEvent("dialog", { timeout }).then(async (dialog) => {
|
|
3525
3697
|
if (state.armIdDialog !== armId) return;
|
|
3526
3698
|
try {
|
|
3527
3699
|
if (opts.accept) await dialog.accept(opts.promptText);
|
|
3528
3700
|
else await dialog.dismiss();
|
|
3529
3701
|
} finally {
|
|
3530
|
-
|
|
3702
|
+
resetArm();
|
|
3703
|
+
page.off("close", resetArm);
|
|
3531
3704
|
}
|
|
3532
3705
|
}).catch(() => {
|
|
3533
|
-
|
|
3706
|
+
resetArm();
|
|
3707
|
+
page.off("close", resetArm);
|
|
3534
3708
|
});
|
|
3535
3709
|
}
|
|
3536
3710
|
async function armFileUploadViaPlaywright(opts) {
|
|
@@ -3539,6 +3713,10 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3539
3713
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3540
3714
|
state.armIdUpload = bumpUploadArmId(state);
|
|
3541
3715
|
const armId = state.armIdUpload;
|
|
3716
|
+
const resetArm = () => {
|
|
3717
|
+
if (state.armIdUpload === armId) state.armIdUpload = 0;
|
|
3718
|
+
};
|
|
3719
|
+
page.once("close", resetArm);
|
|
3542
3720
|
page.waitForEvent("filechooser", { timeout }).then(async (fileChooser) => {
|
|
3543
3721
|
if (state.armIdUpload !== armId) return;
|
|
3544
3722
|
if (opts.paths === void 0 || opts.paths.length === 0) {
|
|
@@ -3570,9 +3748,18 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3570
3748
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3571
3749
|
});
|
|
3572
3750
|
}
|
|
3573
|
-
} catch {
|
|
3751
|
+
} catch (e) {
|
|
3752
|
+
console.warn(
|
|
3753
|
+
`[browserclaw] armFileUpload: dispatch events failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3754
|
+
);
|
|
3574
3755
|
}
|
|
3575
|
-
}).catch(() => {
|
|
3756
|
+
}).catch((e) => {
|
|
3757
|
+
console.warn(
|
|
3758
|
+
`[browserclaw] armFileUpload: filechooser wait failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3759
|
+
);
|
|
3760
|
+
}).finally(() => {
|
|
3761
|
+
resetArm();
|
|
3762
|
+
page.off("close", resetArm);
|
|
3576
3763
|
});
|
|
3577
3764
|
}
|
|
3578
3765
|
|
|
@@ -3592,7 +3779,7 @@ function clearRecordingContext(cdpUrl) {
|
|
|
3592
3779
|
}
|
|
3593
3780
|
async function createRecordingContext(browser, cdpUrl, recordVideo) {
|
|
3594
3781
|
const context = await browser.newContext({ recordVideo });
|
|
3595
|
-
observeContext(context);
|
|
3782
|
+
await observeContext(context);
|
|
3596
3783
|
recordingContexts.set(cdpUrl, context);
|
|
3597
3784
|
context.on("close", () => recordingContexts.delete(cdpUrl));
|
|
3598
3785
|
return context;
|
|
@@ -3647,6 +3834,11 @@ async function gotoPageWithNavigationGuard(opts) {
|
|
|
3647
3834
|
await route.continue();
|
|
3648
3835
|
return;
|
|
3649
3836
|
}
|
|
3837
|
+
const isRedirect = request.redirectedFrom() !== null;
|
|
3838
|
+
if (!isRedirect && request.url() !== opts.url) {
|
|
3839
|
+
await route.continue();
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3650
3842
|
try {
|
|
3651
3843
|
await assertBrowserNavigationAllowed({ url: request.url(), ...navigationPolicy });
|
|
3652
3844
|
} catch (err) {
|
|
@@ -3698,6 +3890,7 @@ async function navigateViaPlaywright(opts) {
|
|
|
3698
3890
|
response = await navigate();
|
|
3699
3891
|
} catch (err) {
|
|
3700
3892
|
if (!isRetryableNavigateError(err)) throw err;
|
|
3893
|
+
recordingContexts.delete(opts.cdpUrl);
|
|
3701
3894
|
await forceDisconnectPlaywrightConnection({
|
|
3702
3895
|
cdpUrl: opts.cdpUrl,
|
|
3703
3896
|
targetId: opts.targetId}).catch(() => {
|
|
@@ -3757,6 +3950,7 @@ async function createPageViaPlaywright(opts) {
|
|
|
3757
3950
|
});
|
|
3758
3951
|
} catch (err) {
|
|
3759
3952
|
if (isPolicyDenyNavigationError(err) || err instanceof BlockedBrowserTargetError) throw err;
|
|
3953
|
+
console.warn(`[browserclaw] createPage navigation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3760
3954
|
}
|
|
3761
3955
|
await assertPageNavigationCompletedSafely({
|
|
3762
3956
|
cdpUrl: opts.cdpUrl,
|
|
@@ -3843,39 +4037,46 @@ var MAX_WAIT_TIME_MS = 3e4;
|
|
|
3843
4037
|
async function waitForViaPlaywright(opts) {
|
|
3844
4038
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3845
4039
|
ensurePageState(page);
|
|
3846
|
-
const
|
|
4040
|
+
const totalTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
4041
|
+
const deadline = Date.now() + totalTimeout;
|
|
4042
|
+
const remaining = () => Math.max(500, deadline - Date.now());
|
|
3847
4043
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
3848
4044
|
await page.waitForTimeout(resolveBoundedDelayMs(opts.timeMs, "wait timeMs", MAX_WAIT_TIME_MS));
|
|
3849
4045
|
}
|
|
3850
4046
|
if (opts.text !== void 0 && opts.text !== "") {
|
|
3851
|
-
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4047
|
+
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4048
|
+
timeout: remaining()
|
|
4049
|
+
});
|
|
3852
4050
|
}
|
|
3853
4051
|
if (opts.textGone !== void 0 && opts.textGone !== "") {
|
|
3854
|
-
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4052
|
+
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4053
|
+
timeout: remaining()
|
|
4054
|
+
});
|
|
3855
4055
|
}
|
|
3856
4056
|
if (opts.selector !== void 0 && opts.selector !== "") {
|
|
3857
4057
|
const selector = opts.selector.trim();
|
|
3858
|
-
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout });
|
|
4058
|
+
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout: remaining() });
|
|
3859
4059
|
}
|
|
3860
4060
|
if (opts.url !== void 0 && opts.url !== "") {
|
|
3861
4061
|
const url = opts.url.trim();
|
|
3862
|
-
if (url !== "") await page.waitForURL(url, { timeout });
|
|
4062
|
+
if (url !== "") await page.waitForURL(url, { timeout: remaining() });
|
|
3863
4063
|
}
|
|
3864
4064
|
if (opts.loadState !== void 0) {
|
|
3865
|
-
await page.waitForLoadState(opts.loadState, { timeout });
|
|
4065
|
+
await page.waitForLoadState(opts.loadState, { timeout: remaining() });
|
|
3866
4066
|
}
|
|
3867
4067
|
if (opts.fn !== void 0) {
|
|
3868
4068
|
if (typeof opts.fn === "function") {
|
|
3869
|
-
await page.waitForFunction(opts.fn, opts.arg, { timeout });
|
|
4069
|
+
await page.waitForFunction(opts.fn, opts.arg, { timeout: remaining() });
|
|
3870
4070
|
} else {
|
|
3871
4071
|
const fn = opts.fn.trim();
|
|
3872
|
-
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout });
|
|
4072
|
+
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout: remaining() });
|
|
3873
4073
|
}
|
|
3874
4074
|
}
|
|
3875
4075
|
}
|
|
3876
4076
|
|
|
3877
4077
|
// src/actions/batch.ts
|
|
3878
4078
|
var MAX_BATCH_DEPTH = 5;
|
|
4079
|
+
var MAX_BATCH_TIMEOUT_MS = 3e5;
|
|
3879
4080
|
var MAX_BATCH_ACTIONS = 100;
|
|
3880
4081
|
async function executeSingleAction(action, cdpUrl, targetId, evaluateEnabled, depth = 0) {
|
|
3881
4082
|
if (depth > MAX_BATCH_DEPTH) throw new Error(`Batch nesting depth exceeds maximum of ${String(MAX_BATCH_DEPTH)}`);
|
|
@@ -4023,13 +4224,19 @@ async function batchViaPlaywright(opts) {
|
|
|
4023
4224
|
throw new Error(`Batch exceeds maximum of ${String(MAX_BATCH_ACTIONS)} actions`);
|
|
4024
4225
|
const results = [];
|
|
4025
4226
|
const evaluateEnabled = opts.evaluateEnabled !== false;
|
|
4227
|
+
const deadline = Date.now() + MAX_BATCH_TIMEOUT_MS;
|
|
4026
4228
|
for (const action of opts.actions) {
|
|
4229
|
+
if (Date.now() > deadline) {
|
|
4230
|
+
results.push({ ok: false, error: "Batch timeout exceeded" });
|
|
4231
|
+
break;
|
|
4232
|
+
}
|
|
4027
4233
|
try {
|
|
4028
4234
|
await executeSingleAction(action, opts.cdpUrl, opts.targetId, evaluateEnabled, depth);
|
|
4029
4235
|
results.push({ ok: true });
|
|
4030
4236
|
} catch (err) {
|
|
4031
4237
|
const message = err instanceof Error ? err.message : String(err);
|
|
4032
4238
|
results.push({ ok: false, error: message });
|
|
4239
|
+
if (err instanceof BrowserTabNotFoundError || err instanceof BlockedBrowserTargetError) break;
|
|
4033
4240
|
if (opts.stopOnError !== false) break;
|
|
4034
4241
|
}
|
|
4035
4242
|
}
|
|
@@ -4047,8 +4254,10 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4047
4254
|
handler = void 0;
|
|
4048
4255
|
}
|
|
4049
4256
|
};
|
|
4257
|
+
let rejectPromise;
|
|
4050
4258
|
return {
|
|
4051
4259
|
promise: new Promise((resolve2, reject) => {
|
|
4260
|
+
rejectPromise = reject;
|
|
4052
4261
|
handler = (download) => {
|
|
4053
4262
|
if (done) return;
|
|
4054
4263
|
done = true;
|
|
@@ -4067,6 +4276,7 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4067
4276
|
if (done) return;
|
|
4068
4277
|
done = true;
|
|
4069
4278
|
cleanup();
|
|
4279
|
+
rejectPromise?.(new Error("Download waiter cancelled"));
|
|
4070
4280
|
}
|
|
4071
4281
|
};
|
|
4072
4282
|
}
|
|
@@ -4098,12 +4308,11 @@ async function downloadViaPlaywright(opts) {
|
|
|
4098
4308
|
await assertSafeOutputPath(opts.path, opts.allowedOutputRoots);
|
|
4099
4309
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4100
4310
|
const state = ensurePageState(page);
|
|
4101
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4102
4311
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
4103
4312
|
const outPath = opts.path.trim();
|
|
4104
4313
|
if (!outPath) throw new Error("path is required");
|
|
4105
|
-
|
|
4106
|
-
|
|
4314
|
+
const armId = bumpDownloadArmId(state);
|
|
4315
|
+
state.armIdDownload = armId;
|
|
4107
4316
|
const waiter = createPageDownloadWaiter(page, timeout);
|
|
4108
4317
|
try {
|
|
4109
4318
|
const locator = refLocator(page, opts.ref);
|
|
@@ -4150,12 +4359,6 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4150
4359
|
}
|
|
4151
4360
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4152
4361
|
ensurePageState(page);
|
|
4153
|
-
if (device.viewport !== null) {
|
|
4154
|
-
await page.setViewportSize({
|
|
4155
|
-
width: device.viewport.width,
|
|
4156
|
-
height: device.viewport.height
|
|
4157
|
-
});
|
|
4158
|
-
}
|
|
4159
4362
|
await withPageScopedCdpClient({
|
|
4160
4363
|
cdpUrl: opts.cdpUrl,
|
|
4161
4364
|
page,
|
|
@@ -4183,11 +4386,24 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4183
4386
|
}
|
|
4184
4387
|
}
|
|
4185
4388
|
});
|
|
4389
|
+
if (device.viewport !== null) {
|
|
4390
|
+
await page.setViewportSize({
|
|
4391
|
+
width: device.viewport.width,
|
|
4392
|
+
height: device.viewport.height
|
|
4393
|
+
});
|
|
4394
|
+
}
|
|
4186
4395
|
}
|
|
4187
4396
|
async function setExtraHTTPHeadersViaPlaywright(opts) {
|
|
4188
4397
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4189
4398
|
ensurePageState(page);
|
|
4190
|
-
await
|
|
4399
|
+
await withPageScopedCdpClient({
|
|
4400
|
+
cdpUrl: opts.cdpUrl,
|
|
4401
|
+
page,
|
|
4402
|
+
targetId: opts.targetId,
|
|
4403
|
+
fn: async (send) => {
|
|
4404
|
+
await send("Network.setExtraHTTPHeaders", { headers: opts.headers });
|
|
4405
|
+
}
|
|
4406
|
+
});
|
|
4191
4407
|
}
|
|
4192
4408
|
async function setGeolocationViaPlaywright(opts) {
|
|
4193
4409
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
@@ -4195,7 +4411,8 @@ async function setGeolocationViaPlaywright(opts) {
|
|
|
4195
4411
|
const context = page.context();
|
|
4196
4412
|
if (opts.clear === true) {
|
|
4197
4413
|
await context.setGeolocation(null);
|
|
4198
|
-
await context.clearPermissions().catch(() => {
|
|
4414
|
+
await context.clearPermissions().catch((err) => {
|
|
4415
|
+
console.warn(`[browserclaw] clearPermissions failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4199
4416
|
});
|
|
4200
4417
|
return;
|
|
4201
4418
|
}
|
|
@@ -4497,7 +4714,6 @@ async function waitForRequestViaPlaywright(opts) {
|
|
|
4497
4714
|
async function takeScreenshotViaPlaywright(opts) {
|
|
4498
4715
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4499
4716
|
ensurePageState(page);
|
|
4500
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4501
4717
|
const type = opts.type ?? "png";
|
|
4502
4718
|
if (opts.ref !== void 0 && opts.ref !== "") {
|
|
4503
4719
|
if (opts.fullPage === true) throw new Error("fullPage is not supported for element screenshots");
|
|
@@ -4512,7 +4728,6 @@ async function takeScreenshotViaPlaywright(opts) {
|
|
|
4512
4728
|
async function screenshotWithLabelsViaPlaywright(opts) {
|
|
4513
4729
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4514
4730
|
ensurePageState(page);
|
|
4515
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4516
4731
|
const maxLabels = typeof opts.maxLabels === "number" && Number.isFinite(opts.maxLabels) ? Math.max(1, Math.floor(opts.maxLabels)) : 150;
|
|
4517
4732
|
const type = opts.type ?? "png";
|
|
4518
4733
|
const refs = opts.refs.slice(0, maxLabels);
|
|
@@ -4576,7 +4791,8 @@ async function screenshotWithLabelsViaPlaywright(opts) {
|
|
|
4576
4791
|
document.querySelectorAll("[data-browserclaw-labels]").forEach((el) => {
|
|
4577
4792
|
el.remove();
|
|
4578
4793
|
});
|
|
4579
|
-
}).catch(() => {
|
|
4794
|
+
}).catch((err) => {
|
|
4795
|
+
console.warn(`[browserclaw] label overlay cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4580
4796
|
});
|
|
4581
4797
|
}
|
|
4582
4798
|
}
|
|
@@ -4604,17 +4820,14 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4604
4820
|
if (!ctxState.traceActive) {
|
|
4605
4821
|
throw new Error("No active trace. Start a trace before stopping it.");
|
|
4606
4822
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
} finally {
|
|
4616
|
-
ctxState.traceActive = false;
|
|
4617
|
-
}
|
|
4823
|
+
ctxState.traceActive = false;
|
|
4824
|
+
await writeViaSiblingTempPath({
|
|
4825
|
+
rootDir: dirname(opts.path),
|
|
4826
|
+
targetPath: opts.path,
|
|
4827
|
+
writeTemp: async (tempPath) => {
|
|
4828
|
+
await context.tracing.stop({ path: tempPath });
|
|
4829
|
+
}
|
|
4830
|
+
});
|
|
4618
4831
|
}
|
|
4619
4832
|
|
|
4620
4833
|
// src/snapshot/ref-map.ts
|
|
@@ -5175,17 +5388,27 @@ async function storageClearViaPlaywright(opts) {
|
|
|
5175
5388
|
// src/browser.ts
|
|
5176
5389
|
var CrawlPage = class {
|
|
5177
5390
|
cdpUrl;
|
|
5178
|
-
|
|
5391
|
+
_targetId;
|
|
5179
5392
|
ssrfPolicy;
|
|
5180
5393
|
/** @internal */
|
|
5181
5394
|
constructor(cdpUrl, targetId, ssrfPolicy) {
|
|
5182
5395
|
this.cdpUrl = cdpUrl;
|
|
5183
|
-
this.
|
|
5396
|
+
this._targetId = targetId;
|
|
5184
5397
|
this.ssrfPolicy = ssrfPolicy;
|
|
5185
5398
|
}
|
|
5186
5399
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
5187
5400
|
get id() {
|
|
5188
|
-
return this.
|
|
5401
|
+
return this._targetId;
|
|
5402
|
+
}
|
|
5403
|
+
/**
|
|
5404
|
+
* Refresh the target ID by re-resolving the page from the browser.
|
|
5405
|
+
* Useful after reconnection when the old target ID may be stale.
|
|
5406
|
+
*/
|
|
5407
|
+
async refreshTargetId() {
|
|
5408
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5409
|
+
const newId = await pageTargetId(page);
|
|
5410
|
+
if (newId !== null && newId !== "") this._targetId = newId;
|
|
5411
|
+
return this._targetId;
|
|
5189
5412
|
}
|
|
5190
5413
|
// ── Snapshot ──────────────────────────────────────────────────
|
|
5191
5414
|
/**
|
|
@@ -5213,7 +5436,7 @@ var CrawlPage = class {
|
|
|
5213
5436
|
if (opts?.mode === "role") {
|
|
5214
5437
|
return snapshotRole({
|
|
5215
5438
|
cdpUrl: this.cdpUrl,
|
|
5216
|
-
targetId: this.
|
|
5439
|
+
targetId: this._targetId,
|
|
5217
5440
|
selector: opts.selector,
|
|
5218
5441
|
frameSelector: opts.frameSelector,
|
|
5219
5442
|
refsMode: opts.refsMode,
|
|
@@ -5232,7 +5455,7 @@ var CrawlPage = class {
|
|
|
5232
5455
|
}
|
|
5233
5456
|
return snapshotAi({
|
|
5234
5457
|
cdpUrl: this.cdpUrl,
|
|
5235
|
-
targetId: this.
|
|
5458
|
+
targetId: this._targetId,
|
|
5236
5459
|
maxChars: opts?.maxChars,
|
|
5237
5460
|
options: {
|
|
5238
5461
|
interactive: opts?.interactive,
|
|
@@ -5251,7 +5474,7 @@ var CrawlPage = class {
|
|
|
5251
5474
|
* @returns Array of accessibility tree nodes
|
|
5252
5475
|
*/
|
|
5253
5476
|
async ariaSnapshot(opts) {
|
|
5254
|
-
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5477
|
+
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this._targetId, limit: opts?.limit });
|
|
5255
5478
|
}
|
|
5256
5479
|
// ── Interactions ─────────────────────────────────────────────
|
|
5257
5480
|
/**
|
|
@@ -5271,7 +5494,7 @@ var CrawlPage = class {
|
|
|
5271
5494
|
async click(ref, opts) {
|
|
5272
5495
|
return clickViaPlaywright({
|
|
5273
5496
|
cdpUrl: this.cdpUrl,
|
|
5274
|
-
targetId: this.
|
|
5497
|
+
targetId: this._targetId,
|
|
5275
5498
|
ref,
|
|
5276
5499
|
doubleClick: opts?.doubleClick,
|
|
5277
5500
|
button: opts?.button,
|
|
@@ -5298,7 +5521,7 @@ var CrawlPage = class {
|
|
|
5298
5521
|
async clickBySelector(selector, opts) {
|
|
5299
5522
|
return clickViaPlaywright({
|
|
5300
5523
|
cdpUrl: this.cdpUrl,
|
|
5301
|
-
targetId: this.
|
|
5524
|
+
targetId: this._targetId,
|
|
5302
5525
|
selector,
|
|
5303
5526
|
doubleClick: opts?.doubleClick,
|
|
5304
5527
|
button: opts?.button,
|
|
@@ -5327,7 +5550,7 @@ var CrawlPage = class {
|
|
|
5327
5550
|
async mouseClick(x, y, opts) {
|
|
5328
5551
|
return mouseClickViaPlaywright({
|
|
5329
5552
|
cdpUrl: this.cdpUrl,
|
|
5330
|
-
targetId: this.
|
|
5553
|
+
targetId: this._targetId,
|
|
5331
5554
|
x,
|
|
5332
5555
|
y,
|
|
5333
5556
|
button: opts?.button,
|
|
@@ -5354,7 +5577,7 @@ var CrawlPage = class {
|
|
|
5354
5577
|
async pressAndHold(x, y, opts) {
|
|
5355
5578
|
return pressAndHoldViaCdp({
|
|
5356
5579
|
cdpUrl: this.cdpUrl,
|
|
5357
|
-
targetId: this.
|
|
5580
|
+
targetId: this._targetId,
|
|
5358
5581
|
x,
|
|
5359
5582
|
y,
|
|
5360
5583
|
delay: opts?.delay,
|
|
@@ -5378,7 +5601,7 @@ var CrawlPage = class {
|
|
|
5378
5601
|
async clickByText(text, opts) {
|
|
5379
5602
|
return clickByTextViaPlaywright({
|
|
5380
5603
|
cdpUrl: this.cdpUrl,
|
|
5381
|
-
targetId: this.
|
|
5604
|
+
targetId: this._targetId,
|
|
5382
5605
|
text,
|
|
5383
5606
|
exact: opts?.exact,
|
|
5384
5607
|
button: opts?.button,
|
|
@@ -5405,7 +5628,7 @@ var CrawlPage = class {
|
|
|
5405
5628
|
async clickByRole(role, name, opts) {
|
|
5406
5629
|
return clickByRoleViaPlaywright({
|
|
5407
5630
|
cdpUrl: this.cdpUrl,
|
|
5408
|
-
targetId: this.
|
|
5631
|
+
targetId: this._targetId,
|
|
5409
5632
|
role,
|
|
5410
5633
|
name,
|
|
5411
5634
|
index: opts?.index,
|
|
@@ -5434,7 +5657,7 @@ var CrawlPage = class {
|
|
|
5434
5657
|
async type(ref, text, opts) {
|
|
5435
5658
|
return typeViaPlaywright({
|
|
5436
5659
|
cdpUrl: this.cdpUrl,
|
|
5437
|
-
targetId: this.
|
|
5660
|
+
targetId: this._targetId,
|
|
5438
5661
|
ref,
|
|
5439
5662
|
text,
|
|
5440
5663
|
submit: opts?.submit,
|
|
@@ -5451,7 +5674,7 @@ var CrawlPage = class {
|
|
|
5451
5674
|
async hover(ref, opts) {
|
|
5452
5675
|
return hoverViaPlaywright({
|
|
5453
5676
|
cdpUrl: this.cdpUrl,
|
|
5454
|
-
targetId: this.
|
|
5677
|
+
targetId: this._targetId,
|
|
5455
5678
|
ref,
|
|
5456
5679
|
timeoutMs: opts?.timeoutMs
|
|
5457
5680
|
});
|
|
@@ -5471,7 +5694,7 @@ var CrawlPage = class {
|
|
|
5471
5694
|
async select(ref, ...values) {
|
|
5472
5695
|
return selectOptionViaPlaywright({
|
|
5473
5696
|
cdpUrl: this.cdpUrl,
|
|
5474
|
-
targetId: this.
|
|
5697
|
+
targetId: this._targetId,
|
|
5475
5698
|
ref,
|
|
5476
5699
|
values
|
|
5477
5700
|
});
|
|
@@ -5486,7 +5709,7 @@ var CrawlPage = class {
|
|
|
5486
5709
|
async drag(startRef, endRef, opts) {
|
|
5487
5710
|
return dragViaPlaywright({
|
|
5488
5711
|
cdpUrl: this.cdpUrl,
|
|
5489
|
-
targetId: this.
|
|
5712
|
+
targetId: this._targetId,
|
|
5490
5713
|
startRef,
|
|
5491
5714
|
endRef,
|
|
5492
5715
|
timeoutMs: opts?.timeoutMs
|
|
@@ -5511,7 +5734,7 @@ var CrawlPage = class {
|
|
|
5511
5734
|
async fill(fields) {
|
|
5512
5735
|
return fillFormViaPlaywright({
|
|
5513
5736
|
cdpUrl: this.cdpUrl,
|
|
5514
|
-
targetId: this.
|
|
5737
|
+
targetId: this._targetId,
|
|
5515
5738
|
fields
|
|
5516
5739
|
});
|
|
5517
5740
|
}
|
|
@@ -5524,7 +5747,7 @@ var CrawlPage = class {
|
|
|
5524
5747
|
async scrollIntoView(ref, opts) {
|
|
5525
5748
|
return scrollIntoViewViaPlaywright({
|
|
5526
5749
|
cdpUrl: this.cdpUrl,
|
|
5527
|
-
targetId: this.
|
|
5750
|
+
targetId: this._targetId,
|
|
5528
5751
|
ref,
|
|
5529
5752
|
timeoutMs: opts?.timeoutMs
|
|
5530
5753
|
});
|
|
@@ -5537,7 +5760,7 @@ var CrawlPage = class {
|
|
|
5537
5760
|
async highlight(ref) {
|
|
5538
5761
|
return highlightViaPlaywright({
|
|
5539
5762
|
cdpUrl: this.cdpUrl,
|
|
5540
|
-
targetId: this.
|
|
5763
|
+
targetId: this._targetId,
|
|
5541
5764
|
ref
|
|
5542
5765
|
});
|
|
5543
5766
|
}
|
|
@@ -5550,7 +5773,7 @@ var CrawlPage = class {
|
|
|
5550
5773
|
async uploadFile(ref, paths) {
|
|
5551
5774
|
return setInputFilesViaPlaywright({
|
|
5552
5775
|
cdpUrl: this.cdpUrl,
|
|
5553
|
-
targetId: this.
|
|
5776
|
+
targetId: this._targetId,
|
|
5554
5777
|
ref,
|
|
5555
5778
|
paths
|
|
5556
5779
|
});
|
|
@@ -5573,7 +5796,7 @@ var CrawlPage = class {
|
|
|
5573
5796
|
async armDialog(opts) {
|
|
5574
5797
|
return armDialogViaPlaywright({
|
|
5575
5798
|
cdpUrl: this.cdpUrl,
|
|
5576
|
-
targetId: this.
|
|
5799
|
+
targetId: this._targetId,
|
|
5577
5800
|
accept: opts.accept,
|
|
5578
5801
|
promptText: opts.promptText,
|
|
5579
5802
|
timeoutMs: opts.timeoutMs
|
|
@@ -5617,7 +5840,7 @@ var CrawlPage = class {
|
|
|
5617
5840
|
async onDialog(handler) {
|
|
5618
5841
|
return setDialogHandler({
|
|
5619
5842
|
cdpUrl: this.cdpUrl,
|
|
5620
|
-
targetId: this.
|
|
5843
|
+
targetId: this._targetId,
|
|
5621
5844
|
handler: handler ?? void 0
|
|
5622
5845
|
});
|
|
5623
5846
|
}
|
|
@@ -5639,7 +5862,7 @@ var CrawlPage = class {
|
|
|
5639
5862
|
async armFileUpload(paths, opts) {
|
|
5640
5863
|
return armFileUploadViaPlaywright({
|
|
5641
5864
|
cdpUrl: this.cdpUrl,
|
|
5642
|
-
targetId: this.
|
|
5865
|
+
targetId: this._targetId,
|
|
5643
5866
|
paths,
|
|
5644
5867
|
timeoutMs: opts?.timeoutMs
|
|
5645
5868
|
});
|
|
@@ -5654,7 +5877,7 @@ var CrawlPage = class {
|
|
|
5654
5877
|
async batch(actions, opts) {
|
|
5655
5878
|
return batchViaPlaywright({
|
|
5656
5879
|
cdpUrl: this.cdpUrl,
|
|
5657
|
-
targetId: this.
|
|
5880
|
+
targetId: this._targetId,
|
|
5658
5881
|
actions,
|
|
5659
5882
|
stopOnError: opts?.stopOnError,
|
|
5660
5883
|
evaluateEnabled: opts?.evaluateEnabled
|
|
@@ -5679,7 +5902,7 @@ var CrawlPage = class {
|
|
|
5679
5902
|
async press(key, opts) {
|
|
5680
5903
|
return pressKeyViaPlaywright({
|
|
5681
5904
|
cdpUrl: this.cdpUrl,
|
|
5682
|
-
targetId: this.
|
|
5905
|
+
targetId: this._targetId,
|
|
5683
5906
|
key,
|
|
5684
5907
|
delayMs: opts?.delayMs
|
|
5685
5908
|
});
|
|
@@ -5689,14 +5912,14 @@ var CrawlPage = class {
|
|
|
5689
5912
|
* Get the current URL of the page.
|
|
5690
5913
|
*/
|
|
5691
5914
|
async url() {
|
|
5692
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5915
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5693
5916
|
return page.url();
|
|
5694
5917
|
}
|
|
5695
5918
|
/**
|
|
5696
5919
|
* Get the page title.
|
|
5697
5920
|
*/
|
|
5698
5921
|
async title() {
|
|
5699
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5922
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5700
5923
|
return page.title();
|
|
5701
5924
|
}
|
|
5702
5925
|
/**
|
|
@@ -5709,7 +5932,7 @@ var CrawlPage = class {
|
|
|
5709
5932
|
async goto(url, opts) {
|
|
5710
5933
|
return navigateViaPlaywright({
|
|
5711
5934
|
cdpUrl: this.cdpUrl,
|
|
5712
|
-
targetId: this.
|
|
5935
|
+
targetId: this._targetId,
|
|
5713
5936
|
url,
|
|
5714
5937
|
timeoutMs: opts?.timeoutMs,
|
|
5715
5938
|
ssrfPolicy: this.ssrfPolicy
|
|
@@ -5721,7 +5944,7 @@ var CrawlPage = class {
|
|
|
5721
5944
|
* @param opts - Timeout options
|
|
5722
5945
|
*/
|
|
5723
5946
|
async reload(opts) {
|
|
5724
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5947
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5725
5948
|
ensurePageState(page);
|
|
5726
5949
|
await page.reload({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5727
5950
|
}
|
|
@@ -5731,7 +5954,7 @@ var CrawlPage = class {
|
|
|
5731
5954
|
* @param opts - Timeout options
|
|
5732
5955
|
*/
|
|
5733
5956
|
async goBack(opts) {
|
|
5734
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5957
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5735
5958
|
ensurePageState(page);
|
|
5736
5959
|
await page.goBack({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5737
5960
|
}
|
|
@@ -5741,7 +5964,7 @@ var CrawlPage = class {
|
|
|
5741
5964
|
* @param opts - Timeout options
|
|
5742
5965
|
*/
|
|
5743
5966
|
async goForward(opts) {
|
|
5744
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5967
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5745
5968
|
ensurePageState(page);
|
|
5746
5969
|
await page.goForward({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5747
5970
|
}
|
|
@@ -5764,7 +5987,7 @@ var CrawlPage = class {
|
|
|
5764
5987
|
async waitFor(opts) {
|
|
5765
5988
|
return waitForViaPlaywright({
|
|
5766
5989
|
cdpUrl: this.cdpUrl,
|
|
5767
|
-
targetId: this.
|
|
5990
|
+
targetId: this._targetId,
|
|
5768
5991
|
...opts
|
|
5769
5992
|
});
|
|
5770
5993
|
}
|
|
@@ -5789,7 +6012,7 @@ var CrawlPage = class {
|
|
|
5789
6012
|
async evaluate(fn, opts) {
|
|
5790
6013
|
return evaluateViaPlaywright({
|
|
5791
6014
|
cdpUrl: this.cdpUrl,
|
|
5792
|
-
targetId: this.
|
|
6015
|
+
targetId: this._targetId,
|
|
5793
6016
|
fn,
|
|
5794
6017
|
ref: opts?.ref,
|
|
5795
6018
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5816,7 +6039,7 @@ var CrawlPage = class {
|
|
|
5816
6039
|
async evaluateInAllFrames(fn) {
|
|
5817
6040
|
return evaluateInAllFramesViaPlaywright({
|
|
5818
6041
|
cdpUrl: this.cdpUrl,
|
|
5819
|
-
targetId: this.
|
|
6042
|
+
targetId: this._targetId,
|
|
5820
6043
|
fn
|
|
5821
6044
|
});
|
|
5822
6045
|
}
|
|
@@ -5837,7 +6060,7 @@ var CrawlPage = class {
|
|
|
5837
6060
|
async screenshot(opts) {
|
|
5838
6061
|
const result = await takeScreenshotViaPlaywright({
|
|
5839
6062
|
cdpUrl: this.cdpUrl,
|
|
5840
|
-
targetId: this.
|
|
6063
|
+
targetId: this._targetId,
|
|
5841
6064
|
fullPage: opts?.fullPage,
|
|
5842
6065
|
ref: opts?.ref,
|
|
5843
6066
|
element: opts?.element,
|
|
@@ -5853,7 +6076,7 @@ var CrawlPage = class {
|
|
|
5853
6076
|
* @returns PDF document as a Buffer
|
|
5854
6077
|
*/
|
|
5855
6078
|
async pdf() {
|
|
5856
|
-
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6079
|
+
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5857
6080
|
return result.buffer;
|
|
5858
6081
|
}
|
|
5859
6082
|
/**
|
|
@@ -5874,7 +6097,7 @@ var CrawlPage = class {
|
|
|
5874
6097
|
async screenshotWithLabels(refs, opts) {
|
|
5875
6098
|
return screenshotWithLabelsViaPlaywright({
|
|
5876
6099
|
cdpUrl: this.cdpUrl,
|
|
5877
|
-
targetId: this.
|
|
6100
|
+
targetId: this._targetId,
|
|
5878
6101
|
refs,
|
|
5879
6102
|
maxLabels: opts?.maxLabels,
|
|
5880
6103
|
type: opts?.type
|
|
@@ -5891,7 +6114,7 @@ var CrawlPage = class {
|
|
|
5891
6114
|
async traceStart(opts) {
|
|
5892
6115
|
return traceStartViaPlaywright({
|
|
5893
6116
|
cdpUrl: this.cdpUrl,
|
|
5894
|
-
targetId: this.
|
|
6117
|
+
targetId: this._targetId,
|
|
5895
6118
|
screenshots: opts?.screenshots,
|
|
5896
6119
|
snapshots: opts?.snapshots,
|
|
5897
6120
|
sources: opts?.sources
|
|
@@ -5906,7 +6129,7 @@ var CrawlPage = class {
|
|
|
5906
6129
|
async traceStop(path2, opts) {
|
|
5907
6130
|
return traceStopViaPlaywright({
|
|
5908
6131
|
cdpUrl: this.cdpUrl,
|
|
5909
|
-
targetId: this.
|
|
6132
|
+
targetId: this._targetId,
|
|
5910
6133
|
path: path2,
|
|
5911
6134
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
5912
6135
|
});
|
|
@@ -5927,7 +6150,7 @@ var CrawlPage = class {
|
|
|
5927
6150
|
async responseBody(url, opts) {
|
|
5928
6151
|
return responseBodyViaPlaywright({
|
|
5929
6152
|
cdpUrl: this.cdpUrl,
|
|
5930
|
-
targetId: this.
|
|
6153
|
+
targetId: this._targetId,
|
|
5931
6154
|
url,
|
|
5932
6155
|
timeoutMs: opts?.timeoutMs,
|
|
5933
6156
|
maxChars: opts?.maxChars
|
|
@@ -5955,7 +6178,7 @@ var CrawlPage = class {
|
|
|
5955
6178
|
async waitForRequest(url, opts) {
|
|
5956
6179
|
return waitForRequestViaPlaywright({
|
|
5957
6180
|
cdpUrl: this.cdpUrl,
|
|
5958
|
-
targetId: this.
|
|
6181
|
+
targetId: this._targetId,
|
|
5959
6182
|
url,
|
|
5960
6183
|
method: opts?.method,
|
|
5961
6184
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5973,7 +6196,7 @@ var CrawlPage = class {
|
|
|
5973
6196
|
async consoleLogs(opts) {
|
|
5974
6197
|
return getConsoleMessagesViaPlaywright({
|
|
5975
6198
|
cdpUrl: this.cdpUrl,
|
|
5976
|
-
targetId: this.
|
|
6199
|
+
targetId: this._targetId,
|
|
5977
6200
|
level: opts?.level,
|
|
5978
6201
|
clear: opts?.clear
|
|
5979
6202
|
});
|
|
@@ -5987,7 +6210,7 @@ var CrawlPage = class {
|
|
|
5987
6210
|
async pageErrors(opts) {
|
|
5988
6211
|
const result = await getPageErrorsViaPlaywright({
|
|
5989
6212
|
cdpUrl: this.cdpUrl,
|
|
5990
|
-
targetId: this.
|
|
6213
|
+
targetId: this._targetId,
|
|
5991
6214
|
clear: opts?.clear
|
|
5992
6215
|
});
|
|
5993
6216
|
return result.errors;
|
|
@@ -6008,7 +6231,7 @@ var CrawlPage = class {
|
|
|
6008
6231
|
async networkRequests(opts) {
|
|
6009
6232
|
const result = await getNetworkRequestsViaPlaywright({
|
|
6010
6233
|
cdpUrl: this.cdpUrl,
|
|
6011
|
-
targetId: this.
|
|
6234
|
+
targetId: this._targetId,
|
|
6012
6235
|
filter: opts?.filter,
|
|
6013
6236
|
clear: opts?.clear
|
|
6014
6237
|
});
|
|
@@ -6024,7 +6247,7 @@ var CrawlPage = class {
|
|
|
6024
6247
|
async resize(width, height) {
|
|
6025
6248
|
return resizeViewportViaPlaywright({
|
|
6026
6249
|
cdpUrl: this.cdpUrl,
|
|
6027
|
-
targetId: this.
|
|
6250
|
+
targetId: this._targetId,
|
|
6028
6251
|
width,
|
|
6029
6252
|
height
|
|
6030
6253
|
});
|
|
@@ -6036,7 +6259,7 @@ var CrawlPage = class {
|
|
|
6036
6259
|
* @returns Array of cookie objects
|
|
6037
6260
|
*/
|
|
6038
6261
|
async cookies() {
|
|
6039
|
-
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6262
|
+
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6040
6263
|
return result.cookies;
|
|
6041
6264
|
}
|
|
6042
6265
|
/**
|
|
@@ -6054,11 +6277,11 @@ var CrawlPage = class {
|
|
|
6054
6277
|
* ```
|
|
6055
6278
|
*/
|
|
6056
6279
|
async setCookie(cookie) {
|
|
6057
|
-
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6280
|
+
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId, cookie });
|
|
6058
6281
|
}
|
|
6059
6282
|
/** Clear all cookies in the browser context. */
|
|
6060
6283
|
async clearCookies() {
|
|
6061
|
-
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6284
|
+
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6062
6285
|
}
|
|
6063
6286
|
/**
|
|
6064
6287
|
* Get values from localStorage or sessionStorage.
|
|
@@ -6070,7 +6293,7 @@ var CrawlPage = class {
|
|
|
6070
6293
|
async storageGet(kind, key) {
|
|
6071
6294
|
const result = await storageGetViaPlaywright({
|
|
6072
6295
|
cdpUrl: this.cdpUrl,
|
|
6073
|
-
targetId: this.
|
|
6296
|
+
targetId: this._targetId,
|
|
6074
6297
|
kind,
|
|
6075
6298
|
key
|
|
6076
6299
|
});
|
|
@@ -6086,7 +6309,7 @@ var CrawlPage = class {
|
|
|
6086
6309
|
async storageSet(kind, key, value) {
|
|
6087
6310
|
return storageSetViaPlaywright({
|
|
6088
6311
|
cdpUrl: this.cdpUrl,
|
|
6089
|
-
targetId: this.
|
|
6312
|
+
targetId: this._targetId,
|
|
6090
6313
|
kind,
|
|
6091
6314
|
key,
|
|
6092
6315
|
value
|
|
@@ -6100,7 +6323,7 @@ var CrawlPage = class {
|
|
|
6100
6323
|
async storageClear(kind) {
|
|
6101
6324
|
return storageClearViaPlaywright({
|
|
6102
6325
|
cdpUrl: this.cdpUrl,
|
|
6103
|
-
targetId: this.
|
|
6326
|
+
targetId: this._targetId,
|
|
6104
6327
|
kind
|
|
6105
6328
|
});
|
|
6106
6329
|
}
|
|
@@ -6122,7 +6345,7 @@ var CrawlPage = class {
|
|
|
6122
6345
|
async download(ref, path2, opts) {
|
|
6123
6346
|
return downloadViaPlaywright({
|
|
6124
6347
|
cdpUrl: this.cdpUrl,
|
|
6125
|
-
targetId: this.
|
|
6348
|
+
targetId: this._targetId,
|
|
6126
6349
|
ref,
|
|
6127
6350
|
path: path2,
|
|
6128
6351
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -6140,7 +6363,7 @@ var CrawlPage = class {
|
|
|
6140
6363
|
async waitForDownload(opts) {
|
|
6141
6364
|
return waitForDownloadViaPlaywright({
|
|
6142
6365
|
cdpUrl: this.cdpUrl,
|
|
6143
|
-
targetId: this.
|
|
6366
|
+
targetId: this._targetId,
|
|
6144
6367
|
path: opts?.path,
|
|
6145
6368
|
timeoutMs: opts?.timeoutMs,
|
|
6146
6369
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
@@ -6155,7 +6378,7 @@ var CrawlPage = class {
|
|
|
6155
6378
|
async setOffline(offline) {
|
|
6156
6379
|
return setOfflineViaPlaywright({
|
|
6157
6380
|
cdpUrl: this.cdpUrl,
|
|
6158
|
-
targetId: this.
|
|
6381
|
+
targetId: this._targetId,
|
|
6159
6382
|
offline
|
|
6160
6383
|
});
|
|
6161
6384
|
}
|
|
@@ -6172,7 +6395,7 @@ var CrawlPage = class {
|
|
|
6172
6395
|
async setExtraHeaders(headers) {
|
|
6173
6396
|
return setExtraHTTPHeadersViaPlaywright({
|
|
6174
6397
|
cdpUrl: this.cdpUrl,
|
|
6175
|
-
targetId: this.
|
|
6398
|
+
targetId: this._targetId,
|
|
6176
6399
|
headers
|
|
6177
6400
|
});
|
|
6178
6401
|
}
|
|
@@ -6184,7 +6407,7 @@ var CrawlPage = class {
|
|
|
6184
6407
|
async setHttpCredentials(opts) {
|
|
6185
6408
|
return setHttpCredentialsViaPlaywright({
|
|
6186
6409
|
cdpUrl: this.cdpUrl,
|
|
6187
|
-
targetId: this.
|
|
6410
|
+
targetId: this._targetId,
|
|
6188
6411
|
username: opts.username,
|
|
6189
6412
|
password: opts.password,
|
|
6190
6413
|
clear: opts.clear
|
|
@@ -6204,7 +6427,7 @@ var CrawlPage = class {
|
|
|
6204
6427
|
async setGeolocation(opts) {
|
|
6205
6428
|
return setGeolocationViaPlaywright({
|
|
6206
6429
|
cdpUrl: this.cdpUrl,
|
|
6207
|
-
targetId: this.
|
|
6430
|
+
targetId: this._targetId,
|
|
6208
6431
|
latitude: opts.latitude,
|
|
6209
6432
|
longitude: opts.longitude,
|
|
6210
6433
|
accuracy: opts.accuracy,
|
|
@@ -6225,7 +6448,7 @@ var CrawlPage = class {
|
|
|
6225
6448
|
async emulateMedia(opts) {
|
|
6226
6449
|
return emulateMediaViaPlaywright({
|
|
6227
6450
|
cdpUrl: this.cdpUrl,
|
|
6228
|
-
targetId: this.
|
|
6451
|
+
targetId: this._targetId,
|
|
6229
6452
|
colorScheme: opts.colorScheme
|
|
6230
6453
|
});
|
|
6231
6454
|
}
|
|
@@ -6237,7 +6460,7 @@ var CrawlPage = class {
|
|
|
6237
6460
|
async setLocale(locale) {
|
|
6238
6461
|
return setLocaleViaPlaywright({
|
|
6239
6462
|
cdpUrl: this.cdpUrl,
|
|
6240
|
-
targetId: this.
|
|
6463
|
+
targetId: this._targetId,
|
|
6241
6464
|
locale
|
|
6242
6465
|
});
|
|
6243
6466
|
}
|
|
@@ -6249,7 +6472,7 @@ var CrawlPage = class {
|
|
|
6249
6472
|
async setTimezone(timezoneId) {
|
|
6250
6473
|
return setTimezoneViaPlaywright({
|
|
6251
6474
|
cdpUrl: this.cdpUrl,
|
|
6252
|
-
targetId: this.
|
|
6475
|
+
targetId: this._targetId,
|
|
6253
6476
|
timezoneId
|
|
6254
6477
|
});
|
|
6255
6478
|
}
|
|
@@ -6266,7 +6489,7 @@ var CrawlPage = class {
|
|
|
6266
6489
|
async setDevice(name) {
|
|
6267
6490
|
return setDeviceViaPlaywright({
|
|
6268
6491
|
cdpUrl: this.cdpUrl,
|
|
6269
|
-
targetId: this.
|
|
6492
|
+
targetId: this._targetId,
|
|
6270
6493
|
name
|
|
6271
6494
|
});
|
|
6272
6495
|
}
|
|
@@ -6287,7 +6510,7 @@ var CrawlPage = class {
|
|
|
6287
6510
|
* ```
|
|
6288
6511
|
*/
|
|
6289
6512
|
async detectChallenge() {
|
|
6290
|
-
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6513
|
+
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6291
6514
|
}
|
|
6292
6515
|
/**
|
|
6293
6516
|
* Wait for an anti-bot challenge to resolve on its own.
|
|
@@ -6312,7 +6535,7 @@ var CrawlPage = class {
|
|
|
6312
6535
|
async waitForChallenge(opts) {
|
|
6313
6536
|
return waitForChallengeViaPlaywright({
|
|
6314
6537
|
cdpUrl: this.cdpUrl,
|
|
6315
|
-
targetId: this.
|
|
6538
|
+
targetId: this._targetId,
|
|
6316
6539
|
timeoutMs: opts?.timeoutMs,
|
|
6317
6540
|
pollMs: opts?.pollMs
|
|
6318
6541
|
});
|
|
@@ -6349,8 +6572,24 @@ var CrawlPage = class {
|
|
|
6349
6572
|
*/
|
|
6350
6573
|
async isAuthenticated(rules) {
|
|
6351
6574
|
if (!rules.length) return { authenticated: true, checks: [] };
|
|
6352
|
-
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6575
|
+
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6353
6576
|
const checks = [];
|
|
6577
|
+
const needsBodyText = rules.some((r) => r.text !== void 0 || r.textGone !== void 0);
|
|
6578
|
+
let bodyText = null;
|
|
6579
|
+
if (needsBodyText) {
|
|
6580
|
+
try {
|
|
6581
|
+
const raw = await evaluateViaPlaywright({
|
|
6582
|
+
cdpUrl: this.cdpUrl,
|
|
6583
|
+
targetId: this._targetId,
|
|
6584
|
+
fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
|
|
6585
|
+
});
|
|
6586
|
+
bodyText = typeof raw === "string" ? raw : null;
|
|
6587
|
+
} catch (err) {
|
|
6588
|
+
console.warn(
|
|
6589
|
+
`[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6590
|
+
);
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6354
6593
|
for (const rule of rules) {
|
|
6355
6594
|
if (rule.url !== void 0) {
|
|
6356
6595
|
const currentUrl = page.url();
|
|
@@ -6383,19 +6622,6 @@ var CrawlPage = class {
|
|
|
6383
6622
|
}
|
|
6384
6623
|
}
|
|
6385
6624
|
if (rule.text !== void 0 || rule.textGone !== void 0) {
|
|
6386
|
-
let bodyText = null;
|
|
6387
|
-
try {
|
|
6388
|
-
const raw = await evaluateViaPlaywright({
|
|
6389
|
-
cdpUrl: this.cdpUrl,
|
|
6390
|
-
targetId: this.targetId,
|
|
6391
|
-
fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
|
|
6392
|
-
});
|
|
6393
|
-
bodyText = typeof raw === "string" ? raw : null;
|
|
6394
|
-
} catch (err) {
|
|
6395
|
-
console.warn(
|
|
6396
|
-
`[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6397
|
-
);
|
|
6398
|
-
}
|
|
6399
6625
|
if (rule.text !== void 0) {
|
|
6400
6626
|
if (bodyText === null) {
|
|
6401
6627
|
checks.push({ rule: "text", passed: false, detail: `"${rule.text}" error during evaluation` });
|
|
@@ -6425,7 +6651,7 @@ var CrawlPage = class {
|
|
|
6425
6651
|
try {
|
|
6426
6652
|
const result = await evaluateViaPlaywright({
|
|
6427
6653
|
cdpUrl: this.cdpUrl,
|
|
6428
|
-
targetId: this.
|
|
6654
|
+
targetId: this._targetId,
|
|
6429
6655
|
fn: rule.fn
|
|
6430
6656
|
});
|
|
6431
6657
|
const passed = result !== null && result !== void 0 && result !== false && result !== 0 && result !== "";
|
|
@@ -6474,7 +6700,7 @@ var CrawlPage = class {
|
|
|
6474
6700
|
* ```
|
|
6475
6701
|
*/
|
|
6476
6702
|
async playwrightPage() {
|
|
6477
|
-
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6703
|
+
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6478
6704
|
}
|
|
6479
6705
|
/**
|
|
6480
6706
|
* Create a Playwright `Locator` for a CSS selector on this page.
|
|
@@ -6497,7 +6723,7 @@ var CrawlPage = class {
|
|
|
6497
6723
|
* ```
|
|
6498
6724
|
*/
|
|
6499
6725
|
async locator(selector) {
|
|
6500
|
-
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6726
|
+
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6501
6727
|
return pwPage.locator(selector);
|
|
6502
6728
|
}
|
|
6503
6729
|
};
|
|
@@ -6541,21 +6767,27 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6541
6767
|
static async launch(opts = {}) {
|
|
6542
6768
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6543
6769
|
const chrome = await launchChrome(opts);
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6770
|
+
try {
|
|
6771
|
+
const cdpUrl = `http://127.0.0.1:${String(chrome.cdpPort)}`;
|
|
6772
|
+
const ssrfPolicy = opts.allowInternal === true ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
6773
|
+
const telemetry = {
|
|
6774
|
+
launchMs: chrome.launchMs,
|
|
6775
|
+
timestamps: { startedAt, launchedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
6776
|
+
};
|
|
6777
|
+
const browser = new _BrowserClaw(cdpUrl, chrome, telemetry, ssrfPolicy, opts.recordVideo);
|
|
6778
|
+
if (opts.url !== void 0 && opts.url !== "") {
|
|
6779
|
+
const page = await browser.currentPage();
|
|
6780
|
+
const navT0 = Date.now();
|
|
6781
|
+
await page.goto(opts.url);
|
|
6782
|
+
telemetry.navMs = Date.now() - navT0;
|
|
6783
|
+
telemetry.timestamps.navigatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6784
|
+
}
|
|
6785
|
+
return browser;
|
|
6786
|
+
} catch (err) {
|
|
6787
|
+
await stopChrome(chrome).catch(() => {
|
|
6788
|
+
});
|
|
6789
|
+
throw err;
|
|
6557
6790
|
}
|
|
6558
|
-
return browser;
|
|
6559
6791
|
}
|
|
6560
6792
|
/**
|
|
6561
6793
|
* Connect to an already-running Chrome instance via its CDP endpoint.
|
|
@@ -6712,7 +6944,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6712
6944
|
if (exitReason !== void 0) this._telemetry.exitReason = exitReason;
|
|
6713
6945
|
try {
|
|
6714
6946
|
clearRecordingContext(this.cdpUrl);
|
|
6715
|
-
await
|
|
6947
|
+
await closePlaywrightBrowserConnection({ cdpUrl: this.cdpUrl });
|
|
6716
6948
|
if (this.chrome) {
|
|
6717
6949
|
await stopChrome(this.chrome);
|
|
6718
6950
|
this.chrome = null;
|