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.cjs
CHANGED
|
@@ -834,6 +834,19 @@ var require_ipaddr = __commonJS({
|
|
|
834
834
|
})(exports$1);
|
|
835
835
|
}
|
|
836
836
|
});
|
|
837
|
+
function killProcessTree(proc, signal) {
|
|
838
|
+
if (process.platform !== "win32" && proc.pid !== void 0) {
|
|
839
|
+
try {
|
|
840
|
+
process.kill(-proc.pid, signal);
|
|
841
|
+
return;
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
proc.kill(signal);
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
}
|
|
837
850
|
var CHROMIUM_BUNDLE_IDS = /* @__PURE__ */ new Set([
|
|
838
851
|
"com.google.Chrome",
|
|
839
852
|
"com.google.Chrome.beta",
|
|
@@ -1189,17 +1202,30 @@ function resolveBrowserExecutable(opts) {
|
|
|
1189
1202
|
if (platform === "win32") return detectDefaultChromiumWindows() ?? findChromeWindows();
|
|
1190
1203
|
return null;
|
|
1191
1204
|
}
|
|
1192
|
-
async function ensurePortAvailable(port) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1205
|
+
async function ensurePortAvailable(port, retries = 2) {
|
|
1206
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
1207
|
+
try {
|
|
1208
|
+
await new Promise((resolve2, reject) => {
|
|
1209
|
+
const tester = net__default.default.createServer().once("error", (err) => {
|
|
1210
|
+
tester.close(() => {
|
|
1211
|
+
if (err.code === "EADDRINUSE") reject(new Error(`Port ${String(port)} is already in use`));
|
|
1212
|
+
else reject(err);
|
|
1213
|
+
});
|
|
1214
|
+
}).once("listening", () => {
|
|
1215
|
+
tester.close(() => {
|
|
1216
|
+
resolve2();
|
|
1217
|
+
});
|
|
1218
|
+
}).listen(port);
|
|
1200
1219
|
});
|
|
1201
|
-
|
|
1202
|
-
|
|
1220
|
+
return;
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
if (attempt < retries) {
|
|
1223
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
throw err;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1203
1229
|
}
|
|
1204
1230
|
function safeReadJson(filePath) {
|
|
1205
1231
|
try {
|
|
@@ -1472,7 +1498,7 @@ async function launchChrome(opts = {}) {
|
|
|
1472
1498
|
const profileName = opts.profileName ?? DEFAULT_PROFILE_NAME;
|
|
1473
1499
|
const userDataDir = opts.userDataDir ?? resolveUserDataDir(profileName);
|
|
1474
1500
|
fs__default.default.mkdirSync(userDataDir, { recursive: true });
|
|
1475
|
-
const spawnChrome = () => {
|
|
1501
|
+
const spawnChrome = (spawnOpts) => {
|
|
1476
1502
|
const args = [
|
|
1477
1503
|
`--remote-debugging-port=${String(cdpPort)}`,
|
|
1478
1504
|
"--remote-debugging-address=127.0.0.1",
|
|
@@ -1503,33 +1529,29 @@ async function launchChrome(opts = {}) {
|
|
|
1503
1529
|
args.push("about:blank");
|
|
1504
1530
|
return child_process.spawn(exe.path, args, {
|
|
1505
1531
|
stdio: "pipe",
|
|
1506
|
-
env: { ...process.env, HOME: os__default.default.homedir() }
|
|
1532
|
+
env: { ...process.env, HOME: os__default.default.homedir() },
|
|
1533
|
+
...spawnOpts
|
|
1507
1534
|
});
|
|
1508
1535
|
};
|
|
1509
1536
|
const startedAt = Date.now();
|
|
1510
1537
|
const localStatePath = path__default.default.join(userDataDir, "Local State");
|
|
1511
1538
|
const preferencesPath = path__default.default.join(userDataDir, "Default", "Preferences");
|
|
1512
1539
|
if (!fileExists(localStatePath) || !fileExists(preferencesPath)) {
|
|
1513
|
-
const
|
|
1540
|
+
const useDetached = process.platform !== "win32";
|
|
1541
|
+
const bootstrap = spawnChrome(useDetached ? { detached: true } : void 0);
|
|
1514
1542
|
const deadline = Date.now() + 1e4;
|
|
1515
1543
|
while (Date.now() < deadline) {
|
|
1516
1544
|
if (fileExists(localStatePath) && fileExists(preferencesPath)) break;
|
|
1517
1545
|
await new Promise((r) => setTimeout(r, 100));
|
|
1518
1546
|
}
|
|
1519
|
-
|
|
1520
|
-
bootstrap.kill("SIGTERM");
|
|
1521
|
-
} catch {
|
|
1522
|
-
}
|
|
1547
|
+
killProcessTree(bootstrap, "SIGTERM");
|
|
1523
1548
|
const exitDeadline = Date.now() + 5e3;
|
|
1524
1549
|
while (Date.now() < exitDeadline) {
|
|
1525
1550
|
if (bootstrap.exitCode != null) break;
|
|
1526
1551
|
await new Promise((r) => setTimeout(r, 50));
|
|
1527
1552
|
}
|
|
1528
1553
|
if (bootstrap.exitCode == null) {
|
|
1529
|
-
|
|
1530
|
-
bootstrap.kill("SIGKILL");
|
|
1531
|
-
} catch {
|
|
1532
|
-
}
|
|
1554
|
+
killProcessTree(bootstrap, "SIGKILL");
|
|
1533
1555
|
}
|
|
1534
1556
|
}
|
|
1535
1557
|
try {
|
|
@@ -1548,9 +1570,11 @@ async function launchChrome(opts = {}) {
|
|
|
1548
1570
|
};
|
|
1549
1571
|
proc.stderr.on("data", onStderr);
|
|
1550
1572
|
const readyDeadline = Date.now() + 15e3;
|
|
1573
|
+
let pollDelay = 200;
|
|
1551
1574
|
while (Date.now() < readyDeadline) {
|
|
1552
1575
|
if (await isChromeCdpReady(cdpUrl, 500)) break;
|
|
1553
|
-
await new Promise((r) => setTimeout(r,
|
|
1576
|
+
await new Promise((r) => setTimeout(r, pollDelay));
|
|
1577
|
+
pollDelay = Math.min(pollDelay + 100, 1e3);
|
|
1554
1578
|
}
|
|
1555
1579
|
if (!await isChromeCdpReady(cdpUrl, 500)) {
|
|
1556
1580
|
const stderrOutput = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
@@ -1562,6 +1586,11 @@ ${stderrOutput.slice(0, 2e3)}` : "";
|
|
|
1562
1586
|
proc.kill("SIGKILL");
|
|
1563
1587
|
} catch {
|
|
1564
1588
|
}
|
|
1589
|
+
try {
|
|
1590
|
+
const lockFile = path__default.default.join(userDataDir, "SingletonLock");
|
|
1591
|
+
if (fs__default.default.existsSync(lockFile)) fs__default.default.unlinkSync(lockFile);
|
|
1592
|
+
} catch {
|
|
1593
|
+
}
|
|
1565
1594
|
throw new Error(`Failed to start Chrome CDP on port ${String(cdpPort)}.${sandboxHint}${stderrHint}`);
|
|
1566
1595
|
}
|
|
1567
1596
|
proc.stderr.off("data", onStderr);
|
|
@@ -1580,19 +1609,13 @@ ${stderrOutput.slice(0, 2e3)}` : "";
|
|
|
1580
1609
|
async function stopChrome(running, timeoutMs = 2500) {
|
|
1581
1610
|
const proc = running.proc;
|
|
1582
1611
|
if (proc.exitCode !== null) return;
|
|
1583
|
-
|
|
1584
|
-
proc.kill("SIGTERM");
|
|
1585
|
-
} catch {
|
|
1586
|
-
}
|
|
1612
|
+
killProcessTree(proc, "SIGTERM");
|
|
1587
1613
|
const start = Date.now();
|
|
1588
1614
|
while (Date.now() - start < timeoutMs) {
|
|
1589
1615
|
if (proc.exitCode !== null) return;
|
|
1590
1616
|
await new Promise((r) => setTimeout(r, 100));
|
|
1591
1617
|
}
|
|
1592
|
-
|
|
1593
|
-
proc.kill("SIGKILL");
|
|
1594
|
-
} catch {
|
|
1595
|
-
}
|
|
1618
|
+
killProcessTree(proc, "SIGKILL");
|
|
1596
1619
|
}
|
|
1597
1620
|
|
|
1598
1621
|
// src/stealth.ts
|
|
@@ -1665,6 +1688,9 @@ var STEALTH_SCRIPT = `(function() {
|
|
|
1665
1688
|
});
|
|
1666
1689
|
|
|
1667
1690
|
// \u2500\u2500 4. window.chrome \u2500\u2500
|
|
1691
|
+
// Stub the chrome.runtime API surface that detection scripts probe for.
|
|
1692
|
+
// Only applied when the real chrome.runtime.connect is absent (headless/CDP mode).
|
|
1693
|
+
// The stubs are intentionally non-functional \u2014 they exist solely to pass presence checks.
|
|
1668
1694
|
p(function() {
|
|
1669
1695
|
if (window.chrome && window.chrome.runtime && window.chrome.runtime.connect) return;
|
|
1670
1696
|
|
|
@@ -1715,6 +1741,9 @@ var STEALTH_SCRIPT = `(function() {
|
|
|
1715
1741
|
});
|
|
1716
1742
|
|
|
1717
1743
|
// \u2500\u2500 6. WebGL vendor / renderer \u2500\u2500
|
|
1744
|
+
// Hardcoded to Intel Iris \u2014 the most common discrete GPU on macOS. These strings
|
|
1745
|
+
// are fingerprinting targets; a more sophisticated approach would randomize per-session,
|
|
1746
|
+
// but static values are sufficient to avoid the default "Google SwiftShader" headless signal.
|
|
1718
1747
|
p(function() {
|
|
1719
1748
|
var h = {
|
|
1720
1749
|
apply: function(target, self, args) {
|
|
@@ -1850,7 +1879,7 @@ function ensurePageState(page) {
|
|
|
1850
1879
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1851
1880
|
location: msg.location()
|
|
1852
1881
|
});
|
|
1853
|
-
if (state.console.length > MAX_CONSOLE_MESSAGES) state.console.
|
|
1882
|
+
if (state.console.length > MAX_CONSOLE_MESSAGES + 50) state.console.splice(0, 50);
|
|
1854
1883
|
});
|
|
1855
1884
|
page.on("pageerror", (err) => {
|
|
1856
1885
|
state.errors.push({
|
|
@@ -1859,7 +1888,7 @@ function ensurePageState(page) {
|
|
|
1859
1888
|
stack: err.stack !== void 0 && err.stack !== "" ? err.stack : void 0,
|
|
1860
1889
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1861
1890
|
});
|
|
1862
|
-
if (state.errors.length > MAX_PAGE_ERRORS) state.errors.
|
|
1891
|
+
if (state.errors.length > MAX_PAGE_ERRORS + 20) state.errors.splice(0, 20);
|
|
1863
1892
|
});
|
|
1864
1893
|
page.on("request", (req) => {
|
|
1865
1894
|
state.nextRequestId += 1;
|
|
@@ -1872,7 +1901,7 @@ function ensurePageState(page) {
|
|
|
1872
1901
|
url: req.url(),
|
|
1873
1902
|
resourceType: req.resourceType()
|
|
1874
1903
|
});
|
|
1875
|
-
if (state.requests.length > MAX_NETWORK_REQUESTS) state.requests.
|
|
1904
|
+
if (state.requests.length > MAX_NETWORK_REQUESTS + 50) state.requests.splice(0, 50);
|
|
1876
1905
|
});
|
|
1877
1906
|
page.on("response", (resp) => {
|
|
1878
1907
|
const req = resp.request();
|
|
@@ -1946,31 +1975,40 @@ function setDialogHandlerOnPage(page, handler) {
|
|
|
1946
1975
|
const state = ensurePageState(page);
|
|
1947
1976
|
state.dialogHandler = handler;
|
|
1948
1977
|
}
|
|
1949
|
-
function applyStealthToPage(page) {
|
|
1950
|
-
|
|
1978
|
+
async function applyStealthToPage(page) {
|
|
1979
|
+
try {
|
|
1980
|
+
await page.evaluate(STEALTH_SCRIPT);
|
|
1981
|
+
} catch (e) {
|
|
1951
1982
|
if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
|
|
1952
1983
|
console.warn("[browserclaw] stealth evaluate failed:", e instanceof Error ? e.message : String(e));
|
|
1953
|
-
}
|
|
1984
|
+
}
|
|
1954
1985
|
}
|
|
1955
|
-
function observeContext(context) {
|
|
1986
|
+
async function observeContext(context) {
|
|
1956
1987
|
if (observedContexts.has(context)) return;
|
|
1957
1988
|
observedContexts.add(context);
|
|
1958
1989
|
ensureContextState(context);
|
|
1959
|
-
|
|
1990
|
+
try {
|
|
1991
|
+
await context.addInitScript(STEALTH_SCRIPT);
|
|
1992
|
+
} catch (e) {
|
|
1960
1993
|
if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
|
|
1961
1994
|
console.warn("[browserclaw] stealth initScript failed:", e instanceof Error ? e.message : String(e));
|
|
1962
|
-
}
|
|
1995
|
+
}
|
|
1963
1996
|
for (const page of context.pages()) {
|
|
1964
1997
|
ensurePageState(page);
|
|
1965
|
-
applyStealthToPage(page);
|
|
1998
|
+
await applyStealthToPage(page);
|
|
1966
1999
|
}
|
|
1967
|
-
|
|
2000
|
+
const onPage = (page) => {
|
|
1968
2001
|
ensurePageState(page);
|
|
1969
|
-
applyStealthToPage(page)
|
|
2002
|
+
applyStealthToPage(page).catch(() => {
|
|
2003
|
+
});
|
|
2004
|
+
};
|
|
2005
|
+
context.on("page", onPage);
|
|
2006
|
+
context.once("close", () => {
|
|
2007
|
+
context.off("page", onPage);
|
|
1970
2008
|
});
|
|
1971
2009
|
}
|
|
1972
|
-
function observeBrowser(browser) {
|
|
1973
|
-
for (const context of browser.contexts()) observeContext(context);
|
|
2010
|
+
async function observeBrowser(browser) {
|
|
2011
|
+
for (const context of browser.contexts()) await observeContext(context);
|
|
1974
2012
|
}
|
|
1975
2013
|
function toAIFriendlyError(error, selector) {
|
|
1976
2014
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2005,6 +2043,7 @@ function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
|
2005
2043
|
}
|
|
2006
2044
|
|
|
2007
2045
|
// src/ref-resolver.ts
|
|
2046
|
+
var REFS_STALENESS_THRESHOLD_MS = 3e4;
|
|
2008
2047
|
var roleRefsByTarget = /* @__PURE__ */ new Map();
|
|
2009
2048
|
var MAX_ROLE_REFS_CACHE = 50;
|
|
2010
2049
|
function normalizeCdpUrl(raw) {
|
|
@@ -2019,7 +2058,8 @@ function rememberRoleRefsForTarget(opts) {
|
|
|
2019
2058
|
roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), {
|
|
2020
2059
|
refs: opts.refs,
|
|
2021
2060
|
...opts.frameSelector !== void 0 && opts.frameSelector !== "" ? { frameSelector: opts.frameSelector } : {},
|
|
2022
|
-
...opts.mode !== void 0 ? { mode: opts.mode } : {}
|
|
2061
|
+
...opts.mode !== void 0 ? { mode: opts.mode } : {},
|
|
2062
|
+
storedAt: Date.now()
|
|
2023
2063
|
});
|
|
2024
2064
|
while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) {
|
|
2025
2065
|
const first = roleRefsByTarget.keys().next();
|
|
@@ -2032,6 +2072,7 @@ function storeRoleRefsForTarget(opts) {
|
|
|
2032
2072
|
state.roleRefs = opts.refs;
|
|
2033
2073
|
state.roleRefsFrameSelector = opts.frameSelector;
|
|
2034
2074
|
state.roleRefsMode = opts.mode;
|
|
2075
|
+
state.roleRefsStoredAt = Date.now();
|
|
2035
2076
|
if (opts.targetId === void 0 || opts.targetId.trim() === "") return;
|
|
2036
2077
|
rememberRoleRefsForTarget({
|
|
2037
2078
|
cdpUrl: opts.cdpUrl,
|
|
@@ -2041,17 +2082,6 @@ function storeRoleRefsForTarget(opts) {
|
|
|
2041
2082
|
mode: opts.mode
|
|
2042
2083
|
});
|
|
2043
2084
|
}
|
|
2044
|
-
function restoreRoleRefsForTarget(opts) {
|
|
2045
|
-
const targetId = opts.targetId?.trim() ?? "";
|
|
2046
|
-
if (targetId === "") return;
|
|
2047
|
-
const entry = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId));
|
|
2048
|
-
if (!entry) return;
|
|
2049
|
-
const state = ensurePageState(opts.page);
|
|
2050
|
-
if (state.roleRefs) return;
|
|
2051
|
-
state.roleRefs = entry.refs;
|
|
2052
|
-
state.roleRefsFrameSelector = entry.frameSelector;
|
|
2053
|
-
state.roleRefsMode = entry.mode;
|
|
2054
|
-
}
|
|
2055
2085
|
function clearRoleRefsForCdpUrl(cdpUrl) {
|
|
2056
2086
|
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2057
2087
|
for (const key of roleRefsByTarget.keys()) {
|
|
@@ -2090,6 +2120,14 @@ function refLocator(page, ref) {
|
|
|
2090
2120
|
if (normalized.trim() === "") throw new Error("ref is required");
|
|
2091
2121
|
if (/^e\d+$/.test(normalized)) {
|
|
2092
2122
|
const state = getPageState(page);
|
|
2123
|
+
if (state?.roleRefsStoredAt !== void 0) {
|
|
2124
|
+
const ageMs = Date.now() - state.roleRefsStoredAt;
|
|
2125
|
+
if (ageMs > REFS_STALENESS_THRESHOLD_MS) {
|
|
2126
|
+
console.warn(
|
|
2127
|
+
`[browserclaw] refs are ${String(Math.round(ageMs / 1e3))}s old \u2014 consider re-snapshotting for fresh refs`
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2093
2131
|
if (state?.roleRefsMode === "aria") {
|
|
2094
2132
|
return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
|
|
2095
2133
|
}
|
|
@@ -2138,14 +2176,16 @@ function appendCdpPath2(cdpUrl, cdpPath) {
|
|
|
2138
2176
|
}
|
|
2139
2177
|
async function withPlaywrightPageCdpSession(page, fn) {
|
|
2140
2178
|
const CDP_SESSION_TIMEOUT_MS = 1e4;
|
|
2179
|
+
let timer;
|
|
2141
2180
|
const session = await Promise.race([
|
|
2142
2181
|
page.context().newCDPSession(page),
|
|
2143
2182
|
new Promise((_, reject) => {
|
|
2144
|
-
setTimeout(() => {
|
|
2183
|
+
timer = setTimeout(() => {
|
|
2145
2184
|
reject(new Error("newCDPSession timed out after 10s"));
|
|
2146
2185
|
}, CDP_SESSION_TIMEOUT_MS);
|
|
2147
2186
|
})
|
|
2148
2187
|
]);
|
|
2188
|
+
clearTimeout(timer);
|
|
2149
2189
|
try {
|
|
2150
2190
|
return await fn(session);
|
|
2151
2191
|
} finally {
|
|
@@ -2171,8 +2211,10 @@ function isLoopbackCdpUrl(url) {
|
|
|
2171
2211
|
}
|
|
2172
2212
|
}
|
|
2173
2213
|
var envMutexPromise = Promise.resolve();
|
|
2214
|
+
var envMutexDepth = 0;
|
|
2174
2215
|
async function withNoProxyForCdpUrl(url, fn) {
|
|
2175
2216
|
if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return fn();
|
|
2217
|
+
if (envMutexDepth > 0) return fn();
|
|
2176
2218
|
const prev = envMutexPromise;
|
|
2177
2219
|
let release = () => {
|
|
2178
2220
|
};
|
|
@@ -2193,9 +2235,11 @@ async function withNoProxyForCdpUrl(url, fn) {
|
|
|
2193
2235
|
const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
|
|
2194
2236
|
process.env.NO_PROXY = applied;
|
|
2195
2237
|
process.env.no_proxy = applied;
|
|
2238
|
+
envMutexDepth += 1;
|
|
2196
2239
|
try {
|
|
2197
2240
|
return await fn();
|
|
2198
2241
|
} finally {
|
|
2242
|
+
envMutexDepth -= 1;
|
|
2199
2243
|
if (process.env.NO_PROXY === applied) {
|
|
2200
2244
|
if (savedNoProxy !== void 0) process.env.NO_PROXY = savedNoProxy;
|
|
2201
2245
|
else delete process.env.NO_PROXY;
|
|
@@ -2226,12 +2270,28 @@ function getHeadersWithAuth(endpoint, baseHeaders = {}) {
|
|
|
2226
2270
|
}
|
|
2227
2271
|
var cachedByCdpUrl = /* @__PURE__ */ new Map();
|
|
2228
2272
|
var connectingByCdpUrl = /* @__PURE__ */ new Map();
|
|
2273
|
+
var connectionMutex = Promise.resolve();
|
|
2274
|
+
async function withConnectionLock(fn) {
|
|
2275
|
+
const prev = connectionMutex;
|
|
2276
|
+
let release = () => {
|
|
2277
|
+
};
|
|
2278
|
+
connectionMutex = new Promise((r) => {
|
|
2279
|
+
release = r;
|
|
2280
|
+
});
|
|
2281
|
+
await prev;
|
|
2282
|
+
try {
|
|
2283
|
+
return await fn();
|
|
2284
|
+
} finally {
|
|
2285
|
+
release();
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2229
2288
|
var BlockedBrowserTargetError = class extends Error {
|
|
2230
2289
|
constructor() {
|
|
2231
2290
|
super("Browser target is unavailable after SSRF policy blocked its navigation.");
|
|
2232
2291
|
this.name = "BlockedBrowserTargetError";
|
|
2233
2292
|
}
|
|
2234
2293
|
};
|
|
2294
|
+
var MAX_BLOCKED_TARGETS = 200;
|
|
2235
2295
|
var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
|
|
2236
2296
|
var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
|
|
2237
2297
|
function blockedTargetKey(cdpUrl, targetId) {
|
|
@@ -2246,6 +2306,10 @@ function markTargetBlocked(cdpUrl, targetId) {
|
|
|
2246
2306
|
const normalized = targetId?.trim() ?? "";
|
|
2247
2307
|
if (normalized === "") return;
|
|
2248
2308
|
blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
|
|
2309
|
+
if (blockedTargetsByCdpUrl.size > MAX_BLOCKED_TARGETS) {
|
|
2310
|
+
const first = blockedTargetsByCdpUrl.values().next();
|
|
2311
|
+
if (first.done !== true) blockedTargetsByCdpUrl.delete(first.value);
|
|
2312
|
+
}
|
|
2249
2313
|
}
|
|
2250
2314
|
function clearBlockedTarget(cdpUrl, targetId) {
|
|
2251
2315
|
const normalized = targetId?.trim() ?? "";
|
|
@@ -2259,6 +2323,16 @@ function hasBlockedTargetsForCdpUrl(cdpUrl) {
|
|
|
2259
2323
|
}
|
|
2260
2324
|
return false;
|
|
2261
2325
|
}
|
|
2326
|
+
function clearBlockedTargetsForCdpUrl(cdpUrl) {
|
|
2327
|
+
if (cdpUrl === void 0) {
|
|
2328
|
+
blockedTargetsByCdpUrl.clear();
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
|
|
2332
|
+
for (const key of blockedTargetsByCdpUrl) {
|
|
2333
|
+
if (key.startsWith(prefix)) blockedTargetsByCdpUrl.delete(key);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2262
2336
|
function blockedPageRefsForCdpUrl(cdpUrl) {
|
|
2263
2337
|
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2264
2338
|
const existing = blockedPageRefsByCdpUrl.get(normalized);
|
|
@@ -2273,6 +2347,13 @@ function isBlockedPageRef(cdpUrl, page) {
|
|
|
2273
2347
|
function markPageRefBlocked(cdpUrl, page) {
|
|
2274
2348
|
blockedPageRefsForCdpUrl(cdpUrl).add(page);
|
|
2275
2349
|
}
|
|
2350
|
+
function clearBlockedPageRefsForCdpUrl(cdpUrl) {
|
|
2351
|
+
if (cdpUrl === void 0) {
|
|
2352
|
+
blockedPageRefsByCdpUrl.clear();
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
blockedPageRefsByCdpUrl.delete(normalizeCdpUrl(cdpUrl));
|
|
2356
|
+
}
|
|
2276
2357
|
function clearBlockedPageRef(cdpUrl, page) {
|
|
2277
2358
|
blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
|
|
2278
2359
|
}
|
|
@@ -2286,64 +2367,98 @@ async function connectBrowser(cdpUrl, authToken) {
|
|
|
2286
2367
|
if (existing_cached) return existing_cached;
|
|
2287
2368
|
const existing = connectingByCdpUrl.get(normalized);
|
|
2288
2369
|
if (existing) return await existing;
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
endpoint,
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2370
|
+
return withConnectionLock(async () => {
|
|
2371
|
+
const rechecked = cachedByCdpUrl.get(normalized);
|
|
2372
|
+
if (rechecked) return rechecked;
|
|
2373
|
+
const recheckPending = connectingByCdpUrl.get(normalized);
|
|
2374
|
+
if (recheckPending) return await recheckPending;
|
|
2375
|
+
const connectWithRetry = async () => {
|
|
2376
|
+
let lastErr;
|
|
2377
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2378
|
+
try {
|
|
2379
|
+
const timeout = 5e3 + attempt * 2e3;
|
|
2380
|
+
const endpoint = await getChromeWebSocketUrl(normalized, timeout, authToken).catch(() => null) ?? normalized;
|
|
2381
|
+
const headers = getHeadersWithAuth(endpoint);
|
|
2382
|
+
if (authToken !== void 0 && authToken !== "" && !headers.Authorization)
|
|
2383
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
2384
|
+
const browser = await withNoProxyForCdpUrl(
|
|
2385
|
+
endpoint,
|
|
2386
|
+
() => playwrightCore.chromium.connectOverCDP(endpoint, { timeout, headers })
|
|
2387
|
+
);
|
|
2388
|
+
const onDisconnected = () => {
|
|
2389
|
+
if (cachedByCdpUrl.get(normalized)?.browser === browser) {
|
|
2390
|
+
cachedByCdpUrl.delete(normalized);
|
|
2391
|
+
clearRoleRefsForCdpUrl(normalized);
|
|
2392
|
+
}
|
|
2393
|
+
};
|
|
2394
|
+
const connected = { browser, cdpUrl: normalized, onDisconnected };
|
|
2395
|
+
cachedByCdpUrl.set(normalized, connected);
|
|
2396
|
+
await observeBrowser(browser);
|
|
2397
|
+
browser.on("disconnected", onDisconnected);
|
|
2398
|
+
return connected;
|
|
2399
|
+
} catch (err) {
|
|
2400
|
+
lastErr = err;
|
|
2401
|
+
if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) {
|
|
2402
|
+
await new Promise((r) => setTimeout(r, 1e3 + attempt * 1e3));
|
|
2403
|
+
continue;
|
|
2306
2404
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
cachedByCdpUrl.set(normalized, connected);
|
|
2310
|
-
observeBrowser(browser);
|
|
2311
|
-
browser.on("disconnected", onDisconnected);
|
|
2312
|
-
return connected;
|
|
2313
|
-
} catch (err) {
|
|
2314
|
-
lastErr = err;
|
|
2315
|
-
if ((err instanceof Error ? err.message : String(err)).includes("rate limit")) break;
|
|
2316
|
-
await new Promise((r) => setTimeout(r, 250 + attempt * 250));
|
|
2405
|
+
await new Promise((r) => setTimeout(r, 250 + attempt * 250));
|
|
2406
|
+
}
|
|
2317
2407
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2408
|
+
throw lastErr instanceof Error ? lastErr : new Error("CDP connect failed");
|
|
2409
|
+
};
|
|
2410
|
+
const promise = connectWithRetry().finally(() => {
|
|
2411
|
+
connectingByCdpUrl.delete(normalized);
|
|
2412
|
+
});
|
|
2413
|
+
connectingByCdpUrl.set(normalized, promise);
|
|
2414
|
+
return await promise;
|
|
2323
2415
|
});
|
|
2324
|
-
connectingByCdpUrl.set(normalized, promise);
|
|
2325
|
-
return await promise;
|
|
2326
2416
|
}
|
|
2327
2417
|
async function disconnectBrowser() {
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2418
|
+
return withConnectionLock(async () => {
|
|
2419
|
+
if (connectingByCdpUrl.size) {
|
|
2420
|
+
for (const p of connectingByCdpUrl.values()) {
|
|
2421
|
+
try {
|
|
2422
|
+
await p;
|
|
2423
|
+
} catch (err) {
|
|
2424
|
+
console.warn(
|
|
2425
|
+
`[browserclaw] disconnectBrowser: pending connect failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2426
|
+
);
|
|
2427
|
+
}
|
|
2336
2428
|
}
|
|
2337
2429
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
cur.browser.
|
|
2343
|
-
|
|
2430
|
+
for (const cur of cachedByCdpUrl.values()) {
|
|
2431
|
+
clearRoleRefsForCdpUrl(cur.cdpUrl);
|
|
2432
|
+
if (cur.onDisconnected && typeof cur.browser.off === "function")
|
|
2433
|
+
cur.browser.off("disconnected", cur.onDisconnected);
|
|
2434
|
+
await cur.browser.close().catch(() => {
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
cachedByCdpUrl.clear();
|
|
2438
|
+
clearBlockedTargetsForCdpUrl();
|
|
2439
|
+
clearBlockedPageRefsForCdpUrl();
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
async function closePlaywrightBrowserConnection(opts) {
|
|
2443
|
+
if (opts?.cdpUrl !== void 0 && opts.cdpUrl !== "") {
|
|
2444
|
+
return withConnectionLock(async () => {
|
|
2445
|
+
const cdpUrl = opts.cdpUrl;
|
|
2446
|
+
if (cdpUrl === void 0 || cdpUrl === "") return;
|
|
2447
|
+
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2448
|
+
clearBlockedTargetsForCdpUrl(normalized);
|
|
2449
|
+
clearBlockedPageRefsForCdpUrl(normalized);
|
|
2450
|
+
const cur = cachedByCdpUrl.get(normalized);
|
|
2451
|
+
cachedByCdpUrl.delete(normalized);
|
|
2452
|
+
connectingByCdpUrl.delete(normalized);
|
|
2453
|
+
if (!cur) return;
|
|
2454
|
+
if (cur.onDisconnected && typeof cur.browser.off === "function")
|
|
2455
|
+
cur.browser.off("disconnected", cur.onDisconnected);
|
|
2456
|
+
await cur.browser.close().catch(() => {
|
|
2457
|
+
});
|
|
2344
2458
|
});
|
|
2459
|
+
} else {
|
|
2460
|
+
await disconnectBrowser();
|
|
2345
2461
|
}
|
|
2346
|
-
cachedByCdpUrl.clear();
|
|
2347
2462
|
}
|
|
2348
2463
|
function cdpSocketNeedsAttach(wsUrl) {
|
|
2349
2464
|
try {
|
|
@@ -2452,7 +2567,7 @@ async function forceDisconnectPlaywrightConnection(opts) {
|
|
|
2452
2567
|
await tryTerminateExecutionViaCdp(normalized, targetId).catch(() => {
|
|
2453
2568
|
});
|
|
2454
2569
|
}
|
|
2455
|
-
cur.browser.close().catch(() => {
|
|
2570
|
+
await cur.browser.close().catch(() => {
|
|
2456
2571
|
});
|
|
2457
2572
|
}
|
|
2458
2573
|
var forceDisconnectPlaywrightForTarget = forceDisconnectPlaywrightConnection;
|
|
@@ -2463,17 +2578,13 @@ var pageTargetIdCache = /* @__PURE__ */ new WeakMap();
|
|
|
2463
2578
|
async function pageTargetId(page) {
|
|
2464
2579
|
const cached = pageTargetIdCache.get(page);
|
|
2465
2580
|
if (cached !== void 0) return cached;
|
|
2466
|
-
|
|
2467
|
-
try {
|
|
2581
|
+
return withPlaywrightPageCdpSession(page, async (session) => {
|
|
2468
2582
|
const info = await session.send("Target.getTargetInfo");
|
|
2469
2583
|
const targetInfo = info.targetInfo;
|
|
2470
2584
|
const id = (targetInfo?.targetId ?? "").trim() || null;
|
|
2471
2585
|
if (id !== null) pageTargetIdCache.set(page, id);
|
|
2472
2586
|
return id;
|
|
2473
|
-
}
|
|
2474
|
-
await session.detach().catch(() => {
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2587
|
+
});
|
|
2477
2588
|
}
|
|
2478
2589
|
function matchPageByTargetList(pages, targets, targetId) {
|
|
2479
2590
|
const target = targets.find((entry) => entry.id === targetId);
|
|
@@ -2509,7 +2620,6 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
|
2509
2620
|
}
|
|
2510
2621
|
})
|
|
2511
2622
|
);
|
|
2512
|
-
const resolvedViaCdp = results.some(({ tid }) => tid !== null);
|
|
2513
2623
|
const matched = results.find(({ tid }) => tid !== null && tid !== "" && tid === targetId);
|
|
2514
2624
|
if (matched) return matched.page;
|
|
2515
2625
|
if (cdpUrl !== void 0 && cdpUrl !== "") {
|
|
@@ -2518,7 +2628,6 @@ async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
|
2518
2628
|
} catch {
|
|
2519
2629
|
}
|
|
2520
2630
|
}
|
|
2521
|
-
if (!resolvedViaCdp && pages.length === 1) return pages[0] ?? null;
|
|
2522
2631
|
return null;
|
|
2523
2632
|
}
|
|
2524
2633
|
async function partitionAccessiblePages(opts) {
|
|
@@ -2561,12 +2670,14 @@ async function getPageForTargetId(opts) {
|
|
|
2561
2670
|
if (opts.targetId === void 0 || opts.targetId === "") return first;
|
|
2562
2671
|
const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
|
2563
2672
|
if (!found) {
|
|
2564
|
-
if (pages.length === 1) return first;
|
|
2565
2673
|
throw new BrowserTabNotFoundError(
|
|
2566
2674
|
`Tab not found (targetId: ${opts.targetId}). Call browser.tabs() to list open tabs.`
|
|
2567
2675
|
);
|
|
2568
2676
|
}
|
|
2569
2677
|
if (isBlockedPageRef(opts.cdpUrl, found)) throw new BlockedBrowserTargetError();
|
|
2678
|
+
const foundTargetId = await pageTargetId(found).catch(() => null);
|
|
2679
|
+
if (foundTargetId !== null && foundTargetId !== "" && isBlockedTarget(opts.cdpUrl, foundTargetId))
|
|
2680
|
+
throw new BlockedBrowserTargetError();
|
|
2570
2681
|
return found;
|
|
2571
2682
|
}
|
|
2572
2683
|
async function resolvePageByTargetIdOrThrow(opts) {
|
|
@@ -2578,7 +2689,6 @@ async function resolvePageByTargetIdOrThrow(opts) {
|
|
|
2578
2689
|
async function getRestoredPageForTarget(opts) {
|
|
2579
2690
|
const page = await getPageForTargetId(opts);
|
|
2580
2691
|
ensurePageState(page);
|
|
2581
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2582
2692
|
return page;
|
|
2583
2693
|
}
|
|
2584
2694
|
|
|
@@ -2632,10 +2742,11 @@ var BROWSER_EVALUATOR = new Function(
|
|
|
2632
2742
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2633
2743
|
' var result = typeof candidate === "function" ? candidate() : candidate;',
|
|
2634
2744
|
' if (result && typeof result.then === "function") {',
|
|
2745
|
+
" var tid;",
|
|
2635
2746
|
" return Promise.race([",
|
|
2636
|
-
" result,",
|
|
2747
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2637
2748
|
" new Promise(function(_, reject) {",
|
|
2638
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2749
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2639
2750
|
" })",
|
|
2640
2751
|
" ]);",
|
|
2641
2752
|
" }",
|
|
@@ -2657,10 +2768,11 @@ var ELEMENT_EVALUATOR = new Function(
|
|
|
2657
2768
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2658
2769
|
' var result = typeof candidate === "function" ? candidate(el) : candidate;',
|
|
2659
2770
|
' if (result && typeof result.then === "function") {',
|
|
2771
|
+
" var tid;",
|
|
2660
2772
|
" return Promise.race([",
|
|
2661
|
-
" result,",
|
|
2773
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2662
2774
|
" new Promise(function(_, reject) {",
|
|
2663
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2775
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2664
2776
|
" })",
|
|
2665
2777
|
" ]);",
|
|
2666
2778
|
" }",
|
|
@@ -2675,10 +2787,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2675
2787
|
if (!fnText) throw new Error("function is required");
|
|
2676
2788
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2677
2789
|
ensurePageState(page);
|
|
2678
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2679
2790
|
const outerTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
2680
|
-
|
|
2681
|
-
evaluateTimeout = Math.min(evaluateTimeout, outerTimeout);
|
|
2791
|
+
const evaluateTimeout = Math.max(1e3, Math.min(12e4, outerTimeout - 1e3));
|
|
2682
2792
|
const signal = opts.signal;
|
|
2683
2793
|
let abortListener;
|
|
2684
2794
|
let abortReject;
|
|
@@ -2692,10 +2802,16 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2692
2802
|
}
|
|
2693
2803
|
if (signal !== void 0) {
|
|
2694
2804
|
const disconnect = () => {
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2805
|
+
const targetId = opts.targetId?.trim() ?? "";
|
|
2806
|
+
if (targetId !== "") {
|
|
2807
|
+
tryTerminateExecutionViaCdp(opts.cdpUrl, targetId).catch(() => {
|
|
2808
|
+
});
|
|
2809
|
+
} else {
|
|
2810
|
+
console.warn("[browserclaw] evaluate abort: no targetId, forcing full disconnect");
|
|
2811
|
+
forceDisconnectPlaywrightConnection({
|
|
2812
|
+
cdpUrl: opts.cdpUrl}).catch(() => {
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2699
2815
|
};
|
|
2700
2816
|
if (signal.aborted) {
|
|
2701
2817
|
disconnect();
|
|
@@ -2731,6 +2847,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2731
2847
|
);
|
|
2732
2848
|
} finally {
|
|
2733
2849
|
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
2850
|
+
abortReject = void 0;
|
|
2851
|
+
abortListener = void 0;
|
|
2734
2852
|
}
|
|
2735
2853
|
}
|
|
2736
2854
|
|
|
@@ -2923,7 +3041,7 @@ function isBlockedHostnameOrIp(hostname, policy) {
|
|
|
2923
3041
|
function isPrivateIpAddress(address, policy) {
|
|
2924
3042
|
let normalized = address.trim().toLowerCase();
|
|
2925
3043
|
if (normalized.startsWith("[") && normalized.endsWith("]")) normalized = normalized.slice(1, -1);
|
|
2926
|
-
if (!normalized) return
|
|
3044
|
+
if (!normalized) return true;
|
|
2927
3045
|
const blockOptions = resolveIpv4SpecialUseBlockOptions(policy);
|
|
2928
3046
|
const strictIp = parseCanonicalIpAddress(normalized);
|
|
2929
3047
|
if (strictIp) {
|
|
@@ -3010,6 +3128,25 @@ function createPinnedLookup(params) {
|
|
|
3010
3128
|
cb(null, chosen.address, chosen.family);
|
|
3011
3129
|
});
|
|
3012
3130
|
}
|
|
3131
|
+
var DNS_CACHE_TTL_MS = 3e4;
|
|
3132
|
+
var MAX_DNS_CACHE_SIZE = 100;
|
|
3133
|
+
var dnsCache = /* @__PURE__ */ new Map();
|
|
3134
|
+
function getCachedDnsResult(hostname) {
|
|
3135
|
+
const entry = dnsCache.get(hostname);
|
|
3136
|
+
if (!entry) return void 0;
|
|
3137
|
+
if (Date.now() > entry.expiresAt) {
|
|
3138
|
+
dnsCache.delete(hostname);
|
|
3139
|
+
return void 0;
|
|
3140
|
+
}
|
|
3141
|
+
return entry.result;
|
|
3142
|
+
}
|
|
3143
|
+
function cacheDnsResult(hostname, result) {
|
|
3144
|
+
dnsCache.set(hostname, { result, expiresAt: Date.now() + DNS_CACHE_TTL_MS });
|
|
3145
|
+
if (dnsCache.size > MAX_DNS_CACHE_SIZE) {
|
|
3146
|
+
const first = dnsCache.keys().next();
|
|
3147
|
+
if (first.done !== true) dnsCache.delete(first.value);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3013
3150
|
async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
3014
3151
|
const normalized = normalizeHostname(hostname);
|
|
3015
3152
|
if (!normalized) throw new InvalidBrowserNavigationUrlError(`Invalid hostname: "${hostname}"`);
|
|
@@ -3028,6 +3165,8 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3028
3165
|
);
|
|
3029
3166
|
}
|
|
3030
3167
|
}
|
|
3168
|
+
const cached = getCachedDnsResult(normalized);
|
|
3169
|
+
if (cached) return cached;
|
|
3031
3170
|
const lookupFn = params.lookupFn ?? promises.lookup;
|
|
3032
3171
|
let results;
|
|
3033
3172
|
try {
|
|
@@ -3057,11 +3196,13 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3057
3196
|
`Navigation to internal/loopback address blocked: unable to resolve "${hostname}".`
|
|
3058
3197
|
);
|
|
3059
3198
|
}
|
|
3060
|
-
|
|
3199
|
+
const pinned = {
|
|
3061
3200
|
hostname: normalized,
|
|
3062
3201
|
addresses,
|
|
3063
3202
|
lookup: createPinnedLookup({ hostname: normalized, addresses })
|
|
3064
3203
|
};
|
|
3204
|
+
cacheDnsResult(normalized, pinned);
|
|
3205
|
+
return pinned;
|
|
3065
3206
|
}
|
|
3066
3207
|
async function assertBrowserNavigationAllowed(opts) {
|
|
3067
3208
|
const rawUrl = opts.url.trim();
|
|
@@ -3215,13 +3356,27 @@ function buildSiblingTempPath(targetPath) {
|
|
|
3215
3356
|
return path.join(path.dirname(targetPath), `.browserclaw-output-${id}-${safeTail}.part`);
|
|
3216
3357
|
}
|
|
3217
3358
|
async function writeViaSiblingTempPath(params) {
|
|
3218
|
-
|
|
3359
|
+
let rootDir;
|
|
3360
|
+
try {
|
|
3361
|
+
rootDir = await promises$1.realpath(path.resolve(params.rootDir));
|
|
3362
|
+
} catch {
|
|
3363
|
+
console.warn(`[browserclaw] writeViaSiblingTempPath: rootDir realpath failed, using lexical resolve`);
|
|
3364
|
+
rootDir = path.resolve(params.rootDir);
|
|
3365
|
+
}
|
|
3219
3366
|
const requestedTargetPath = path.resolve(params.targetPath);
|
|
3220
3367
|
const targetPath = await promises$1.realpath(path.dirname(requestedTargetPath)).then((realDir) => path.join(realDir, path.basename(requestedTargetPath))).catch(() => requestedTargetPath);
|
|
3221
3368
|
const relativeTargetPath = path.relative(rootDir, targetPath);
|
|
3222
3369
|
if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeTargetPath)) {
|
|
3223
3370
|
throw new Error("Target path is outside the allowed root");
|
|
3224
3371
|
}
|
|
3372
|
+
try {
|
|
3373
|
+
const stat = await promises$1.lstat(targetPath);
|
|
3374
|
+
if (stat.isSymbolicLink()) {
|
|
3375
|
+
throw new Error(`Unsafe output path: "${params.targetPath}" is a symbolic link.`);
|
|
3376
|
+
}
|
|
3377
|
+
} catch (e) {
|
|
3378
|
+
if (e.code !== "ENOENT") throw e;
|
|
3379
|
+
}
|
|
3225
3380
|
const tempPath = buildSiblingTempPath(targetPath);
|
|
3226
3381
|
let renameSucceeded = false;
|
|
3227
3382
|
try {
|
|
@@ -3243,6 +3398,9 @@ async function assertBrowserNavigationResultAllowed(opts) {
|
|
|
3243
3398
|
} catch {
|
|
3244
3399
|
return;
|
|
3245
3400
|
}
|
|
3401
|
+
if (parsed.protocol === "data:" || parsed.protocol === "blob:") {
|
|
3402
|
+
throw new InvalidBrowserNavigationUrlError(`Navigation result blocked: "${parsed.protocol}" URLs are not allowed.`);
|
|
3403
|
+
}
|
|
3246
3404
|
if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || isAllowedNonNetworkNavigationUrl(parsed)) {
|
|
3247
3405
|
await assertBrowserNavigationAllowed(opts);
|
|
3248
3406
|
}
|
|
@@ -3360,9 +3518,10 @@ async function clickViaPlaywright(opts) {
|
|
|
3360
3518
|
if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
|
|
3361
3519
|
const POLL_INTERVAL_MS = 50;
|
|
3362
3520
|
const POLL_TIMEOUT_MS = 500;
|
|
3521
|
+
const ATTR_TIMEOUT_MS = Math.min(timeout, POLL_TIMEOUT_MS);
|
|
3363
3522
|
let changed = false;
|
|
3364
3523
|
for (let elapsed = 0; elapsed < POLL_TIMEOUT_MS; elapsed += POLL_INTERVAL_MS) {
|
|
3365
|
-
const current = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
|
|
3524
|
+
const current = await locator.getAttribute("aria-checked", { timeout: ATTR_TIMEOUT_MS }).catch(() => void 0);
|
|
3366
3525
|
if (current === void 0 || current !== ariaCheckedBefore) {
|
|
3367
3526
|
changed = true;
|
|
3368
3527
|
break;
|
|
@@ -3439,6 +3598,7 @@ async function dragViaPlaywright(opts) {
|
|
|
3439
3598
|
async function fillFormViaPlaywright(opts) {
|
|
3440
3599
|
const page = await getRestoredPageForTarget(opts);
|
|
3441
3600
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3601
|
+
let filledCount = 0;
|
|
3442
3602
|
for (const field of opts.fields) {
|
|
3443
3603
|
const ref = field.ref.trim();
|
|
3444
3604
|
const type = (typeof field.type === "string" ? field.type.trim() : "") || "text";
|
|
@@ -3457,16 +3617,24 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3457
3617
|
try {
|
|
3458
3618
|
await setCheckedViaEvaluate(locator, checked);
|
|
3459
3619
|
} catch (err) {
|
|
3460
|
-
|
|
3620
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3621
|
+
throw new Error(
|
|
3622
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3623
|
+
);
|
|
3461
3624
|
}
|
|
3462
3625
|
}
|
|
3626
|
+
filledCount += 1;
|
|
3463
3627
|
continue;
|
|
3464
3628
|
}
|
|
3465
3629
|
try {
|
|
3466
3630
|
await locator.fill(value, { timeout });
|
|
3467
3631
|
} catch (err) {
|
|
3468
|
-
|
|
3632
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3633
|
+
throw new Error(
|
|
3634
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3635
|
+
);
|
|
3469
3636
|
}
|
|
3637
|
+
filledCount += 1;
|
|
3470
3638
|
}
|
|
3471
3639
|
}
|
|
3472
3640
|
async function scrollIntoViewViaPlaywright(opts) {
|
|
@@ -3532,16 +3700,22 @@ async function armDialogViaPlaywright(opts) {
|
|
|
3532
3700
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3533
3701
|
state.armIdDialog = bumpDialogArmId(state);
|
|
3534
3702
|
const armId = state.armIdDialog;
|
|
3703
|
+
const resetArm = () => {
|
|
3704
|
+
if (state.armIdDialog === armId) state.armIdDialog = 0;
|
|
3705
|
+
};
|
|
3706
|
+
page.once("close", resetArm);
|
|
3535
3707
|
page.waitForEvent("dialog", { timeout }).then(async (dialog) => {
|
|
3536
3708
|
if (state.armIdDialog !== armId) return;
|
|
3537
3709
|
try {
|
|
3538
3710
|
if (opts.accept) await dialog.accept(opts.promptText);
|
|
3539
3711
|
else await dialog.dismiss();
|
|
3540
3712
|
} finally {
|
|
3541
|
-
|
|
3713
|
+
resetArm();
|
|
3714
|
+
page.off("close", resetArm);
|
|
3542
3715
|
}
|
|
3543
3716
|
}).catch(() => {
|
|
3544
|
-
|
|
3717
|
+
resetArm();
|
|
3718
|
+
page.off("close", resetArm);
|
|
3545
3719
|
});
|
|
3546
3720
|
}
|
|
3547
3721
|
async function armFileUploadViaPlaywright(opts) {
|
|
@@ -3550,6 +3724,10 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3550
3724
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3551
3725
|
state.armIdUpload = bumpUploadArmId(state);
|
|
3552
3726
|
const armId = state.armIdUpload;
|
|
3727
|
+
const resetArm = () => {
|
|
3728
|
+
if (state.armIdUpload === armId) state.armIdUpload = 0;
|
|
3729
|
+
};
|
|
3730
|
+
page.once("close", resetArm);
|
|
3553
3731
|
page.waitForEvent("filechooser", { timeout }).then(async (fileChooser) => {
|
|
3554
3732
|
if (state.armIdUpload !== armId) return;
|
|
3555
3733
|
if (opts.paths === void 0 || opts.paths.length === 0) {
|
|
@@ -3581,9 +3759,18 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3581
3759
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3582
3760
|
});
|
|
3583
3761
|
}
|
|
3584
|
-
} catch {
|
|
3762
|
+
} catch (e) {
|
|
3763
|
+
console.warn(
|
|
3764
|
+
`[browserclaw] armFileUpload: dispatch events failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3765
|
+
);
|
|
3585
3766
|
}
|
|
3586
|
-
}).catch(() => {
|
|
3767
|
+
}).catch((e) => {
|
|
3768
|
+
console.warn(
|
|
3769
|
+
`[browserclaw] armFileUpload: filechooser wait failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3770
|
+
);
|
|
3771
|
+
}).finally(() => {
|
|
3772
|
+
resetArm();
|
|
3773
|
+
page.off("close", resetArm);
|
|
3587
3774
|
});
|
|
3588
3775
|
}
|
|
3589
3776
|
|
|
@@ -3603,7 +3790,7 @@ function clearRecordingContext(cdpUrl) {
|
|
|
3603
3790
|
}
|
|
3604
3791
|
async function createRecordingContext(browser, cdpUrl, recordVideo) {
|
|
3605
3792
|
const context = await browser.newContext({ recordVideo });
|
|
3606
|
-
observeContext(context);
|
|
3793
|
+
await observeContext(context);
|
|
3607
3794
|
recordingContexts.set(cdpUrl, context);
|
|
3608
3795
|
context.on("close", () => recordingContexts.delete(cdpUrl));
|
|
3609
3796
|
return context;
|
|
@@ -3658,6 +3845,11 @@ async function gotoPageWithNavigationGuard(opts) {
|
|
|
3658
3845
|
await route.continue();
|
|
3659
3846
|
return;
|
|
3660
3847
|
}
|
|
3848
|
+
const isRedirect = request.redirectedFrom() !== null;
|
|
3849
|
+
if (!isRedirect && request.url() !== opts.url) {
|
|
3850
|
+
await route.continue();
|
|
3851
|
+
return;
|
|
3852
|
+
}
|
|
3661
3853
|
try {
|
|
3662
3854
|
await assertBrowserNavigationAllowed({ url: request.url(), ...navigationPolicy });
|
|
3663
3855
|
} catch (err) {
|
|
@@ -3709,6 +3901,7 @@ async function navigateViaPlaywright(opts) {
|
|
|
3709
3901
|
response = await navigate();
|
|
3710
3902
|
} catch (err) {
|
|
3711
3903
|
if (!isRetryableNavigateError(err)) throw err;
|
|
3904
|
+
recordingContexts.delete(opts.cdpUrl);
|
|
3712
3905
|
await forceDisconnectPlaywrightConnection({
|
|
3713
3906
|
cdpUrl: opts.cdpUrl,
|
|
3714
3907
|
targetId: opts.targetId}).catch(() => {
|
|
@@ -3768,6 +3961,7 @@ async function createPageViaPlaywright(opts) {
|
|
|
3768
3961
|
});
|
|
3769
3962
|
} catch (err) {
|
|
3770
3963
|
if (isPolicyDenyNavigationError(err) || err instanceof BlockedBrowserTargetError) throw err;
|
|
3964
|
+
console.warn(`[browserclaw] createPage navigation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3771
3965
|
}
|
|
3772
3966
|
await assertPageNavigationCompletedSafely({
|
|
3773
3967
|
cdpUrl: opts.cdpUrl,
|
|
@@ -3854,39 +4048,46 @@ var MAX_WAIT_TIME_MS = 3e4;
|
|
|
3854
4048
|
async function waitForViaPlaywright(opts) {
|
|
3855
4049
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3856
4050
|
ensurePageState(page);
|
|
3857
|
-
const
|
|
4051
|
+
const totalTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
4052
|
+
const deadline = Date.now() + totalTimeout;
|
|
4053
|
+
const remaining = () => Math.max(500, deadline - Date.now());
|
|
3858
4054
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
3859
4055
|
await page.waitForTimeout(resolveBoundedDelayMs(opts.timeMs, "wait timeMs", MAX_WAIT_TIME_MS));
|
|
3860
4056
|
}
|
|
3861
4057
|
if (opts.text !== void 0 && opts.text !== "") {
|
|
3862
|
-
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4058
|
+
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4059
|
+
timeout: remaining()
|
|
4060
|
+
});
|
|
3863
4061
|
}
|
|
3864
4062
|
if (opts.textGone !== void 0 && opts.textGone !== "") {
|
|
3865
|
-
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4063
|
+
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4064
|
+
timeout: remaining()
|
|
4065
|
+
});
|
|
3866
4066
|
}
|
|
3867
4067
|
if (opts.selector !== void 0 && opts.selector !== "") {
|
|
3868
4068
|
const selector = opts.selector.trim();
|
|
3869
|
-
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout });
|
|
4069
|
+
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout: remaining() });
|
|
3870
4070
|
}
|
|
3871
4071
|
if (opts.url !== void 0 && opts.url !== "") {
|
|
3872
4072
|
const url = opts.url.trim();
|
|
3873
|
-
if (url !== "") await page.waitForURL(url, { timeout });
|
|
4073
|
+
if (url !== "") await page.waitForURL(url, { timeout: remaining() });
|
|
3874
4074
|
}
|
|
3875
4075
|
if (opts.loadState !== void 0) {
|
|
3876
|
-
await page.waitForLoadState(opts.loadState, { timeout });
|
|
4076
|
+
await page.waitForLoadState(opts.loadState, { timeout: remaining() });
|
|
3877
4077
|
}
|
|
3878
4078
|
if (opts.fn !== void 0) {
|
|
3879
4079
|
if (typeof opts.fn === "function") {
|
|
3880
|
-
await page.waitForFunction(opts.fn, opts.arg, { timeout });
|
|
4080
|
+
await page.waitForFunction(opts.fn, opts.arg, { timeout: remaining() });
|
|
3881
4081
|
} else {
|
|
3882
4082
|
const fn = opts.fn.trim();
|
|
3883
|
-
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout });
|
|
4083
|
+
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout: remaining() });
|
|
3884
4084
|
}
|
|
3885
4085
|
}
|
|
3886
4086
|
}
|
|
3887
4087
|
|
|
3888
4088
|
// src/actions/batch.ts
|
|
3889
4089
|
var MAX_BATCH_DEPTH = 5;
|
|
4090
|
+
var MAX_BATCH_TIMEOUT_MS = 3e5;
|
|
3890
4091
|
var MAX_BATCH_ACTIONS = 100;
|
|
3891
4092
|
async function executeSingleAction(action, cdpUrl, targetId, evaluateEnabled, depth = 0) {
|
|
3892
4093
|
if (depth > MAX_BATCH_DEPTH) throw new Error(`Batch nesting depth exceeds maximum of ${String(MAX_BATCH_DEPTH)}`);
|
|
@@ -4034,13 +4235,19 @@ async function batchViaPlaywright(opts) {
|
|
|
4034
4235
|
throw new Error(`Batch exceeds maximum of ${String(MAX_BATCH_ACTIONS)} actions`);
|
|
4035
4236
|
const results = [];
|
|
4036
4237
|
const evaluateEnabled = opts.evaluateEnabled !== false;
|
|
4238
|
+
const deadline = Date.now() + MAX_BATCH_TIMEOUT_MS;
|
|
4037
4239
|
for (const action of opts.actions) {
|
|
4240
|
+
if (Date.now() > deadline) {
|
|
4241
|
+
results.push({ ok: false, error: "Batch timeout exceeded" });
|
|
4242
|
+
break;
|
|
4243
|
+
}
|
|
4038
4244
|
try {
|
|
4039
4245
|
await executeSingleAction(action, opts.cdpUrl, opts.targetId, evaluateEnabled, depth);
|
|
4040
4246
|
results.push({ ok: true });
|
|
4041
4247
|
} catch (err) {
|
|
4042
4248
|
const message = err instanceof Error ? err.message : String(err);
|
|
4043
4249
|
results.push({ ok: false, error: message });
|
|
4250
|
+
if (err instanceof BrowserTabNotFoundError || err instanceof BlockedBrowserTargetError) break;
|
|
4044
4251
|
if (opts.stopOnError !== false) break;
|
|
4045
4252
|
}
|
|
4046
4253
|
}
|
|
@@ -4058,8 +4265,10 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4058
4265
|
handler = void 0;
|
|
4059
4266
|
}
|
|
4060
4267
|
};
|
|
4268
|
+
let rejectPromise;
|
|
4061
4269
|
return {
|
|
4062
4270
|
promise: new Promise((resolve2, reject) => {
|
|
4271
|
+
rejectPromise = reject;
|
|
4063
4272
|
handler = (download) => {
|
|
4064
4273
|
if (done) return;
|
|
4065
4274
|
done = true;
|
|
@@ -4078,6 +4287,7 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4078
4287
|
if (done) return;
|
|
4079
4288
|
done = true;
|
|
4080
4289
|
cleanup();
|
|
4290
|
+
rejectPromise?.(new Error("Download waiter cancelled"));
|
|
4081
4291
|
}
|
|
4082
4292
|
};
|
|
4083
4293
|
}
|
|
@@ -4109,12 +4319,11 @@ async function downloadViaPlaywright(opts) {
|
|
|
4109
4319
|
await assertSafeOutputPath(opts.path, opts.allowedOutputRoots);
|
|
4110
4320
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4111
4321
|
const state = ensurePageState(page);
|
|
4112
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4113
4322
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
4114
4323
|
const outPath = opts.path.trim();
|
|
4115
4324
|
if (!outPath) throw new Error("path is required");
|
|
4116
|
-
|
|
4117
|
-
|
|
4325
|
+
const armId = bumpDownloadArmId(state);
|
|
4326
|
+
state.armIdDownload = armId;
|
|
4118
4327
|
const waiter = createPageDownloadWaiter(page, timeout);
|
|
4119
4328
|
try {
|
|
4120
4329
|
const locator = refLocator(page, opts.ref);
|
|
@@ -4161,12 +4370,6 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4161
4370
|
}
|
|
4162
4371
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4163
4372
|
ensurePageState(page);
|
|
4164
|
-
if (device.viewport !== null) {
|
|
4165
|
-
await page.setViewportSize({
|
|
4166
|
-
width: device.viewport.width,
|
|
4167
|
-
height: device.viewport.height
|
|
4168
|
-
});
|
|
4169
|
-
}
|
|
4170
4373
|
await withPageScopedCdpClient({
|
|
4171
4374
|
cdpUrl: opts.cdpUrl,
|
|
4172
4375
|
page,
|
|
@@ -4194,11 +4397,24 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4194
4397
|
}
|
|
4195
4398
|
}
|
|
4196
4399
|
});
|
|
4400
|
+
if (device.viewport !== null) {
|
|
4401
|
+
await page.setViewportSize({
|
|
4402
|
+
width: device.viewport.width,
|
|
4403
|
+
height: device.viewport.height
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4197
4406
|
}
|
|
4198
4407
|
async function setExtraHTTPHeadersViaPlaywright(opts) {
|
|
4199
4408
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4200
4409
|
ensurePageState(page);
|
|
4201
|
-
await
|
|
4410
|
+
await withPageScopedCdpClient({
|
|
4411
|
+
cdpUrl: opts.cdpUrl,
|
|
4412
|
+
page,
|
|
4413
|
+
targetId: opts.targetId,
|
|
4414
|
+
fn: async (send) => {
|
|
4415
|
+
await send("Network.setExtraHTTPHeaders", { headers: opts.headers });
|
|
4416
|
+
}
|
|
4417
|
+
});
|
|
4202
4418
|
}
|
|
4203
4419
|
async function setGeolocationViaPlaywright(opts) {
|
|
4204
4420
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
@@ -4206,7 +4422,8 @@ async function setGeolocationViaPlaywright(opts) {
|
|
|
4206
4422
|
const context = page.context();
|
|
4207
4423
|
if (opts.clear === true) {
|
|
4208
4424
|
await context.setGeolocation(null);
|
|
4209
|
-
await context.clearPermissions().catch(() => {
|
|
4425
|
+
await context.clearPermissions().catch((err) => {
|
|
4426
|
+
console.warn(`[browserclaw] clearPermissions failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4210
4427
|
});
|
|
4211
4428
|
return;
|
|
4212
4429
|
}
|
|
@@ -4508,7 +4725,6 @@ async function waitForRequestViaPlaywright(opts) {
|
|
|
4508
4725
|
async function takeScreenshotViaPlaywright(opts) {
|
|
4509
4726
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4510
4727
|
ensurePageState(page);
|
|
4511
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4512
4728
|
const type = opts.type ?? "png";
|
|
4513
4729
|
if (opts.ref !== void 0 && opts.ref !== "") {
|
|
4514
4730
|
if (opts.fullPage === true) throw new Error("fullPage is not supported for element screenshots");
|
|
@@ -4523,7 +4739,6 @@ async function takeScreenshotViaPlaywright(opts) {
|
|
|
4523
4739
|
async function screenshotWithLabelsViaPlaywright(opts) {
|
|
4524
4740
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4525
4741
|
ensurePageState(page);
|
|
4526
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4527
4742
|
const maxLabels = typeof opts.maxLabels === "number" && Number.isFinite(opts.maxLabels) ? Math.max(1, Math.floor(opts.maxLabels)) : 150;
|
|
4528
4743
|
const type = opts.type ?? "png";
|
|
4529
4744
|
const refs = opts.refs.slice(0, maxLabels);
|
|
@@ -4587,7 +4802,8 @@ async function screenshotWithLabelsViaPlaywright(opts) {
|
|
|
4587
4802
|
document.querySelectorAll("[data-browserclaw-labels]").forEach((el) => {
|
|
4588
4803
|
el.remove();
|
|
4589
4804
|
});
|
|
4590
|
-
}).catch(() => {
|
|
4805
|
+
}).catch((err) => {
|
|
4806
|
+
console.warn(`[browserclaw] label overlay cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4591
4807
|
});
|
|
4592
4808
|
}
|
|
4593
4809
|
}
|
|
@@ -4615,17 +4831,14 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4615
4831
|
if (!ctxState.traceActive) {
|
|
4616
4832
|
throw new Error("No active trace. Start a trace before stopping it.");
|
|
4617
4833
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
} finally {
|
|
4627
|
-
ctxState.traceActive = false;
|
|
4628
|
-
}
|
|
4834
|
+
ctxState.traceActive = false;
|
|
4835
|
+
await writeViaSiblingTempPath({
|
|
4836
|
+
rootDir: path.dirname(opts.path),
|
|
4837
|
+
targetPath: opts.path,
|
|
4838
|
+
writeTemp: async (tempPath) => {
|
|
4839
|
+
await context.tracing.stop({ path: tempPath });
|
|
4840
|
+
}
|
|
4841
|
+
});
|
|
4629
4842
|
}
|
|
4630
4843
|
|
|
4631
4844
|
// src/snapshot/ref-map.ts
|
|
@@ -5186,17 +5399,27 @@ async function storageClearViaPlaywright(opts) {
|
|
|
5186
5399
|
// src/browser.ts
|
|
5187
5400
|
var CrawlPage = class {
|
|
5188
5401
|
cdpUrl;
|
|
5189
|
-
|
|
5402
|
+
_targetId;
|
|
5190
5403
|
ssrfPolicy;
|
|
5191
5404
|
/** @internal */
|
|
5192
5405
|
constructor(cdpUrl, targetId, ssrfPolicy) {
|
|
5193
5406
|
this.cdpUrl = cdpUrl;
|
|
5194
|
-
this.
|
|
5407
|
+
this._targetId = targetId;
|
|
5195
5408
|
this.ssrfPolicy = ssrfPolicy;
|
|
5196
5409
|
}
|
|
5197
5410
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
5198
5411
|
get id() {
|
|
5199
|
-
return this.
|
|
5412
|
+
return this._targetId;
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* Refresh the target ID by re-resolving the page from the browser.
|
|
5416
|
+
* Useful after reconnection when the old target ID may be stale.
|
|
5417
|
+
*/
|
|
5418
|
+
async refreshTargetId() {
|
|
5419
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5420
|
+
const newId = await pageTargetId(page);
|
|
5421
|
+
if (newId !== null && newId !== "") this._targetId = newId;
|
|
5422
|
+
return this._targetId;
|
|
5200
5423
|
}
|
|
5201
5424
|
// ── Snapshot ──────────────────────────────────────────────────
|
|
5202
5425
|
/**
|
|
@@ -5224,7 +5447,7 @@ var CrawlPage = class {
|
|
|
5224
5447
|
if (opts?.mode === "role") {
|
|
5225
5448
|
return snapshotRole({
|
|
5226
5449
|
cdpUrl: this.cdpUrl,
|
|
5227
|
-
targetId: this.
|
|
5450
|
+
targetId: this._targetId,
|
|
5228
5451
|
selector: opts.selector,
|
|
5229
5452
|
frameSelector: opts.frameSelector,
|
|
5230
5453
|
refsMode: opts.refsMode,
|
|
@@ -5243,7 +5466,7 @@ var CrawlPage = class {
|
|
|
5243
5466
|
}
|
|
5244
5467
|
return snapshotAi({
|
|
5245
5468
|
cdpUrl: this.cdpUrl,
|
|
5246
|
-
targetId: this.
|
|
5469
|
+
targetId: this._targetId,
|
|
5247
5470
|
maxChars: opts?.maxChars,
|
|
5248
5471
|
options: {
|
|
5249
5472
|
interactive: opts?.interactive,
|
|
@@ -5262,7 +5485,7 @@ var CrawlPage = class {
|
|
|
5262
5485
|
* @returns Array of accessibility tree nodes
|
|
5263
5486
|
*/
|
|
5264
5487
|
async ariaSnapshot(opts) {
|
|
5265
|
-
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5488
|
+
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this._targetId, limit: opts?.limit });
|
|
5266
5489
|
}
|
|
5267
5490
|
// ── Interactions ─────────────────────────────────────────────
|
|
5268
5491
|
/**
|
|
@@ -5282,7 +5505,7 @@ var CrawlPage = class {
|
|
|
5282
5505
|
async click(ref, opts) {
|
|
5283
5506
|
return clickViaPlaywright({
|
|
5284
5507
|
cdpUrl: this.cdpUrl,
|
|
5285
|
-
targetId: this.
|
|
5508
|
+
targetId: this._targetId,
|
|
5286
5509
|
ref,
|
|
5287
5510
|
doubleClick: opts?.doubleClick,
|
|
5288
5511
|
button: opts?.button,
|
|
@@ -5309,7 +5532,7 @@ var CrawlPage = class {
|
|
|
5309
5532
|
async clickBySelector(selector, opts) {
|
|
5310
5533
|
return clickViaPlaywright({
|
|
5311
5534
|
cdpUrl: this.cdpUrl,
|
|
5312
|
-
targetId: this.
|
|
5535
|
+
targetId: this._targetId,
|
|
5313
5536
|
selector,
|
|
5314
5537
|
doubleClick: opts?.doubleClick,
|
|
5315
5538
|
button: opts?.button,
|
|
@@ -5338,7 +5561,7 @@ var CrawlPage = class {
|
|
|
5338
5561
|
async mouseClick(x, y, opts) {
|
|
5339
5562
|
return mouseClickViaPlaywright({
|
|
5340
5563
|
cdpUrl: this.cdpUrl,
|
|
5341
|
-
targetId: this.
|
|
5564
|
+
targetId: this._targetId,
|
|
5342
5565
|
x,
|
|
5343
5566
|
y,
|
|
5344
5567
|
button: opts?.button,
|
|
@@ -5365,7 +5588,7 @@ var CrawlPage = class {
|
|
|
5365
5588
|
async pressAndHold(x, y, opts) {
|
|
5366
5589
|
return pressAndHoldViaCdp({
|
|
5367
5590
|
cdpUrl: this.cdpUrl,
|
|
5368
|
-
targetId: this.
|
|
5591
|
+
targetId: this._targetId,
|
|
5369
5592
|
x,
|
|
5370
5593
|
y,
|
|
5371
5594
|
delay: opts?.delay,
|
|
@@ -5389,7 +5612,7 @@ var CrawlPage = class {
|
|
|
5389
5612
|
async clickByText(text, opts) {
|
|
5390
5613
|
return clickByTextViaPlaywright({
|
|
5391
5614
|
cdpUrl: this.cdpUrl,
|
|
5392
|
-
targetId: this.
|
|
5615
|
+
targetId: this._targetId,
|
|
5393
5616
|
text,
|
|
5394
5617
|
exact: opts?.exact,
|
|
5395
5618
|
button: opts?.button,
|
|
@@ -5416,7 +5639,7 @@ var CrawlPage = class {
|
|
|
5416
5639
|
async clickByRole(role, name, opts) {
|
|
5417
5640
|
return clickByRoleViaPlaywright({
|
|
5418
5641
|
cdpUrl: this.cdpUrl,
|
|
5419
|
-
targetId: this.
|
|
5642
|
+
targetId: this._targetId,
|
|
5420
5643
|
role,
|
|
5421
5644
|
name,
|
|
5422
5645
|
index: opts?.index,
|
|
@@ -5445,7 +5668,7 @@ var CrawlPage = class {
|
|
|
5445
5668
|
async type(ref, text, opts) {
|
|
5446
5669
|
return typeViaPlaywright({
|
|
5447
5670
|
cdpUrl: this.cdpUrl,
|
|
5448
|
-
targetId: this.
|
|
5671
|
+
targetId: this._targetId,
|
|
5449
5672
|
ref,
|
|
5450
5673
|
text,
|
|
5451
5674
|
submit: opts?.submit,
|
|
@@ -5462,7 +5685,7 @@ var CrawlPage = class {
|
|
|
5462
5685
|
async hover(ref, opts) {
|
|
5463
5686
|
return hoverViaPlaywright({
|
|
5464
5687
|
cdpUrl: this.cdpUrl,
|
|
5465
|
-
targetId: this.
|
|
5688
|
+
targetId: this._targetId,
|
|
5466
5689
|
ref,
|
|
5467
5690
|
timeoutMs: opts?.timeoutMs
|
|
5468
5691
|
});
|
|
@@ -5482,7 +5705,7 @@ var CrawlPage = class {
|
|
|
5482
5705
|
async select(ref, ...values) {
|
|
5483
5706
|
return selectOptionViaPlaywright({
|
|
5484
5707
|
cdpUrl: this.cdpUrl,
|
|
5485
|
-
targetId: this.
|
|
5708
|
+
targetId: this._targetId,
|
|
5486
5709
|
ref,
|
|
5487
5710
|
values
|
|
5488
5711
|
});
|
|
@@ -5497,7 +5720,7 @@ var CrawlPage = class {
|
|
|
5497
5720
|
async drag(startRef, endRef, opts) {
|
|
5498
5721
|
return dragViaPlaywright({
|
|
5499
5722
|
cdpUrl: this.cdpUrl,
|
|
5500
|
-
targetId: this.
|
|
5723
|
+
targetId: this._targetId,
|
|
5501
5724
|
startRef,
|
|
5502
5725
|
endRef,
|
|
5503
5726
|
timeoutMs: opts?.timeoutMs
|
|
@@ -5522,7 +5745,7 @@ var CrawlPage = class {
|
|
|
5522
5745
|
async fill(fields) {
|
|
5523
5746
|
return fillFormViaPlaywright({
|
|
5524
5747
|
cdpUrl: this.cdpUrl,
|
|
5525
|
-
targetId: this.
|
|
5748
|
+
targetId: this._targetId,
|
|
5526
5749
|
fields
|
|
5527
5750
|
});
|
|
5528
5751
|
}
|
|
@@ -5535,7 +5758,7 @@ var CrawlPage = class {
|
|
|
5535
5758
|
async scrollIntoView(ref, opts) {
|
|
5536
5759
|
return scrollIntoViewViaPlaywright({
|
|
5537
5760
|
cdpUrl: this.cdpUrl,
|
|
5538
|
-
targetId: this.
|
|
5761
|
+
targetId: this._targetId,
|
|
5539
5762
|
ref,
|
|
5540
5763
|
timeoutMs: opts?.timeoutMs
|
|
5541
5764
|
});
|
|
@@ -5548,7 +5771,7 @@ var CrawlPage = class {
|
|
|
5548
5771
|
async highlight(ref) {
|
|
5549
5772
|
return highlightViaPlaywright({
|
|
5550
5773
|
cdpUrl: this.cdpUrl,
|
|
5551
|
-
targetId: this.
|
|
5774
|
+
targetId: this._targetId,
|
|
5552
5775
|
ref
|
|
5553
5776
|
});
|
|
5554
5777
|
}
|
|
@@ -5561,7 +5784,7 @@ var CrawlPage = class {
|
|
|
5561
5784
|
async uploadFile(ref, paths) {
|
|
5562
5785
|
return setInputFilesViaPlaywright({
|
|
5563
5786
|
cdpUrl: this.cdpUrl,
|
|
5564
|
-
targetId: this.
|
|
5787
|
+
targetId: this._targetId,
|
|
5565
5788
|
ref,
|
|
5566
5789
|
paths
|
|
5567
5790
|
});
|
|
@@ -5584,7 +5807,7 @@ var CrawlPage = class {
|
|
|
5584
5807
|
async armDialog(opts) {
|
|
5585
5808
|
return armDialogViaPlaywright({
|
|
5586
5809
|
cdpUrl: this.cdpUrl,
|
|
5587
|
-
targetId: this.
|
|
5810
|
+
targetId: this._targetId,
|
|
5588
5811
|
accept: opts.accept,
|
|
5589
5812
|
promptText: opts.promptText,
|
|
5590
5813
|
timeoutMs: opts.timeoutMs
|
|
@@ -5628,7 +5851,7 @@ var CrawlPage = class {
|
|
|
5628
5851
|
async onDialog(handler) {
|
|
5629
5852
|
return setDialogHandler({
|
|
5630
5853
|
cdpUrl: this.cdpUrl,
|
|
5631
|
-
targetId: this.
|
|
5854
|
+
targetId: this._targetId,
|
|
5632
5855
|
handler: handler ?? void 0
|
|
5633
5856
|
});
|
|
5634
5857
|
}
|
|
@@ -5650,7 +5873,7 @@ var CrawlPage = class {
|
|
|
5650
5873
|
async armFileUpload(paths, opts) {
|
|
5651
5874
|
return armFileUploadViaPlaywright({
|
|
5652
5875
|
cdpUrl: this.cdpUrl,
|
|
5653
|
-
targetId: this.
|
|
5876
|
+
targetId: this._targetId,
|
|
5654
5877
|
paths,
|
|
5655
5878
|
timeoutMs: opts?.timeoutMs
|
|
5656
5879
|
});
|
|
@@ -5665,7 +5888,7 @@ var CrawlPage = class {
|
|
|
5665
5888
|
async batch(actions, opts) {
|
|
5666
5889
|
return batchViaPlaywright({
|
|
5667
5890
|
cdpUrl: this.cdpUrl,
|
|
5668
|
-
targetId: this.
|
|
5891
|
+
targetId: this._targetId,
|
|
5669
5892
|
actions,
|
|
5670
5893
|
stopOnError: opts?.stopOnError,
|
|
5671
5894
|
evaluateEnabled: opts?.evaluateEnabled
|
|
@@ -5690,7 +5913,7 @@ var CrawlPage = class {
|
|
|
5690
5913
|
async press(key, opts) {
|
|
5691
5914
|
return pressKeyViaPlaywright({
|
|
5692
5915
|
cdpUrl: this.cdpUrl,
|
|
5693
|
-
targetId: this.
|
|
5916
|
+
targetId: this._targetId,
|
|
5694
5917
|
key,
|
|
5695
5918
|
delayMs: opts?.delayMs
|
|
5696
5919
|
});
|
|
@@ -5700,14 +5923,14 @@ var CrawlPage = class {
|
|
|
5700
5923
|
* Get the current URL of the page.
|
|
5701
5924
|
*/
|
|
5702
5925
|
async url() {
|
|
5703
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5926
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5704
5927
|
return page.url();
|
|
5705
5928
|
}
|
|
5706
5929
|
/**
|
|
5707
5930
|
* Get the page title.
|
|
5708
5931
|
*/
|
|
5709
5932
|
async title() {
|
|
5710
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5933
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5711
5934
|
return page.title();
|
|
5712
5935
|
}
|
|
5713
5936
|
/**
|
|
@@ -5720,7 +5943,7 @@ var CrawlPage = class {
|
|
|
5720
5943
|
async goto(url, opts) {
|
|
5721
5944
|
return navigateViaPlaywright({
|
|
5722
5945
|
cdpUrl: this.cdpUrl,
|
|
5723
|
-
targetId: this.
|
|
5946
|
+
targetId: this._targetId,
|
|
5724
5947
|
url,
|
|
5725
5948
|
timeoutMs: opts?.timeoutMs,
|
|
5726
5949
|
ssrfPolicy: this.ssrfPolicy
|
|
@@ -5732,7 +5955,7 @@ var CrawlPage = class {
|
|
|
5732
5955
|
* @param opts - Timeout options
|
|
5733
5956
|
*/
|
|
5734
5957
|
async reload(opts) {
|
|
5735
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5958
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5736
5959
|
ensurePageState(page);
|
|
5737
5960
|
await page.reload({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5738
5961
|
}
|
|
@@ -5742,7 +5965,7 @@ var CrawlPage = class {
|
|
|
5742
5965
|
* @param opts - Timeout options
|
|
5743
5966
|
*/
|
|
5744
5967
|
async goBack(opts) {
|
|
5745
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5968
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5746
5969
|
ensurePageState(page);
|
|
5747
5970
|
await page.goBack({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5748
5971
|
}
|
|
@@ -5752,7 +5975,7 @@ var CrawlPage = class {
|
|
|
5752
5975
|
* @param opts - Timeout options
|
|
5753
5976
|
*/
|
|
5754
5977
|
async goForward(opts) {
|
|
5755
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5978
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5756
5979
|
ensurePageState(page);
|
|
5757
5980
|
await page.goForward({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5758
5981
|
}
|
|
@@ -5775,7 +5998,7 @@ var CrawlPage = class {
|
|
|
5775
5998
|
async waitFor(opts) {
|
|
5776
5999
|
return waitForViaPlaywright({
|
|
5777
6000
|
cdpUrl: this.cdpUrl,
|
|
5778
|
-
targetId: this.
|
|
6001
|
+
targetId: this._targetId,
|
|
5779
6002
|
...opts
|
|
5780
6003
|
});
|
|
5781
6004
|
}
|
|
@@ -5800,7 +6023,7 @@ var CrawlPage = class {
|
|
|
5800
6023
|
async evaluate(fn, opts) {
|
|
5801
6024
|
return evaluateViaPlaywright({
|
|
5802
6025
|
cdpUrl: this.cdpUrl,
|
|
5803
|
-
targetId: this.
|
|
6026
|
+
targetId: this._targetId,
|
|
5804
6027
|
fn,
|
|
5805
6028
|
ref: opts?.ref,
|
|
5806
6029
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5827,7 +6050,7 @@ var CrawlPage = class {
|
|
|
5827
6050
|
async evaluateInAllFrames(fn) {
|
|
5828
6051
|
return evaluateInAllFramesViaPlaywright({
|
|
5829
6052
|
cdpUrl: this.cdpUrl,
|
|
5830
|
-
targetId: this.
|
|
6053
|
+
targetId: this._targetId,
|
|
5831
6054
|
fn
|
|
5832
6055
|
});
|
|
5833
6056
|
}
|
|
@@ -5848,7 +6071,7 @@ var CrawlPage = class {
|
|
|
5848
6071
|
async screenshot(opts) {
|
|
5849
6072
|
const result = await takeScreenshotViaPlaywright({
|
|
5850
6073
|
cdpUrl: this.cdpUrl,
|
|
5851
|
-
targetId: this.
|
|
6074
|
+
targetId: this._targetId,
|
|
5852
6075
|
fullPage: opts?.fullPage,
|
|
5853
6076
|
ref: opts?.ref,
|
|
5854
6077
|
element: opts?.element,
|
|
@@ -5864,7 +6087,7 @@ var CrawlPage = class {
|
|
|
5864
6087
|
* @returns PDF document as a Buffer
|
|
5865
6088
|
*/
|
|
5866
6089
|
async pdf() {
|
|
5867
|
-
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6090
|
+
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5868
6091
|
return result.buffer;
|
|
5869
6092
|
}
|
|
5870
6093
|
/**
|
|
@@ -5885,7 +6108,7 @@ var CrawlPage = class {
|
|
|
5885
6108
|
async screenshotWithLabels(refs, opts) {
|
|
5886
6109
|
return screenshotWithLabelsViaPlaywright({
|
|
5887
6110
|
cdpUrl: this.cdpUrl,
|
|
5888
|
-
targetId: this.
|
|
6111
|
+
targetId: this._targetId,
|
|
5889
6112
|
refs,
|
|
5890
6113
|
maxLabels: opts?.maxLabels,
|
|
5891
6114
|
type: opts?.type
|
|
@@ -5902,7 +6125,7 @@ var CrawlPage = class {
|
|
|
5902
6125
|
async traceStart(opts) {
|
|
5903
6126
|
return traceStartViaPlaywright({
|
|
5904
6127
|
cdpUrl: this.cdpUrl,
|
|
5905
|
-
targetId: this.
|
|
6128
|
+
targetId: this._targetId,
|
|
5906
6129
|
screenshots: opts?.screenshots,
|
|
5907
6130
|
snapshots: opts?.snapshots,
|
|
5908
6131
|
sources: opts?.sources
|
|
@@ -5917,7 +6140,7 @@ var CrawlPage = class {
|
|
|
5917
6140
|
async traceStop(path2, opts) {
|
|
5918
6141
|
return traceStopViaPlaywright({
|
|
5919
6142
|
cdpUrl: this.cdpUrl,
|
|
5920
|
-
targetId: this.
|
|
6143
|
+
targetId: this._targetId,
|
|
5921
6144
|
path: path2,
|
|
5922
6145
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
5923
6146
|
});
|
|
@@ -5938,7 +6161,7 @@ var CrawlPage = class {
|
|
|
5938
6161
|
async responseBody(url, opts) {
|
|
5939
6162
|
return responseBodyViaPlaywright({
|
|
5940
6163
|
cdpUrl: this.cdpUrl,
|
|
5941
|
-
targetId: this.
|
|
6164
|
+
targetId: this._targetId,
|
|
5942
6165
|
url,
|
|
5943
6166
|
timeoutMs: opts?.timeoutMs,
|
|
5944
6167
|
maxChars: opts?.maxChars
|
|
@@ -5966,7 +6189,7 @@ var CrawlPage = class {
|
|
|
5966
6189
|
async waitForRequest(url, opts) {
|
|
5967
6190
|
return waitForRequestViaPlaywright({
|
|
5968
6191
|
cdpUrl: this.cdpUrl,
|
|
5969
|
-
targetId: this.
|
|
6192
|
+
targetId: this._targetId,
|
|
5970
6193
|
url,
|
|
5971
6194
|
method: opts?.method,
|
|
5972
6195
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5984,7 +6207,7 @@ var CrawlPage = class {
|
|
|
5984
6207
|
async consoleLogs(opts) {
|
|
5985
6208
|
return getConsoleMessagesViaPlaywright({
|
|
5986
6209
|
cdpUrl: this.cdpUrl,
|
|
5987
|
-
targetId: this.
|
|
6210
|
+
targetId: this._targetId,
|
|
5988
6211
|
level: opts?.level,
|
|
5989
6212
|
clear: opts?.clear
|
|
5990
6213
|
});
|
|
@@ -5998,7 +6221,7 @@ var CrawlPage = class {
|
|
|
5998
6221
|
async pageErrors(opts) {
|
|
5999
6222
|
const result = await getPageErrorsViaPlaywright({
|
|
6000
6223
|
cdpUrl: this.cdpUrl,
|
|
6001
|
-
targetId: this.
|
|
6224
|
+
targetId: this._targetId,
|
|
6002
6225
|
clear: opts?.clear
|
|
6003
6226
|
});
|
|
6004
6227
|
return result.errors;
|
|
@@ -6019,7 +6242,7 @@ var CrawlPage = class {
|
|
|
6019
6242
|
async networkRequests(opts) {
|
|
6020
6243
|
const result = await getNetworkRequestsViaPlaywright({
|
|
6021
6244
|
cdpUrl: this.cdpUrl,
|
|
6022
|
-
targetId: this.
|
|
6245
|
+
targetId: this._targetId,
|
|
6023
6246
|
filter: opts?.filter,
|
|
6024
6247
|
clear: opts?.clear
|
|
6025
6248
|
});
|
|
@@ -6035,7 +6258,7 @@ var CrawlPage = class {
|
|
|
6035
6258
|
async resize(width, height) {
|
|
6036
6259
|
return resizeViewportViaPlaywright({
|
|
6037
6260
|
cdpUrl: this.cdpUrl,
|
|
6038
|
-
targetId: this.
|
|
6261
|
+
targetId: this._targetId,
|
|
6039
6262
|
width,
|
|
6040
6263
|
height
|
|
6041
6264
|
});
|
|
@@ -6047,7 +6270,7 @@ var CrawlPage = class {
|
|
|
6047
6270
|
* @returns Array of cookie objects
|
|
6048
6271
|
*/
|
|
6049
6272
|
async cookies() {
|
|
6050
|
-
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6273
|
+
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6051
6274
|
return result.cookies;
|
|
6052
6275
|
}
|
|
6053
6276
|
/**
|
|
@@ -6065,11 +6288,11 @@ var CrawlPage = class {
|
|
|
6065
6288
|
* ```
|
|
6066
6289
|
*/
|
|
6067
6290
|
async setCookie(cookie) {
|
|
6068
|
-
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6291
|
+
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId, cookie });
|
|
6069
6292
|
}
|
|
6070
6293
|
/** Clear all cookies in the browser context. */
|
|
6071
6294
|
async clearCookies() {
|
|
6072
|
-
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6295
|
+
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6073
6296
|
}
|
|
6074
6297
|
/**
|
|
6075
6298
|
* Get values from localStorage or sessionStorage.
|
|
@@ -6081,7 +6304,7 @@ var CrawlPage = class {
|
|
|
6081
6304
|
async storageGet(kind, key) {
|
|
6082
6305
|
const result = await storageGetViaPlaywright({
|
|
6083
6306
|
cdpUrl: this.cdpUrl,
|
|
6084
|
-
targetId: this.
|
|
6307
|
+
targetId: this._targetId,
|
|
6085
6308
|
kind,
|
|
6086
6309
|
key
|
|
6087
6310
|
});
|
|
@@ -6097,7 +6320,7 @@ var CrawlPage = class {
|
|
|
6097
6320
|
async storageSet(kind, key, value) {
|
|
6098
6321
|
return storageSetViaPlaywright({
|
|
6099
6322
|
cdpUrl: this.cdpUrl,
|
|
6100
|
-
targetId: this.
|
|
6323
|
+
targetId: this._targetId,
|
|
6101
6324
|
kind,
|
|
6102
6325
|
key,
|
|
6103
6326
|
value
|
|
@@ -6111,7 +6334,7 @@ var CrawlPage = class {
|
|
|
6111
6334
|
async storageClear(kind) {
|
|
6112
6335
|
return storageClearViaPlaywright({
|
|
6113
6336
|
cdpUrl: this.cdpUrl,
|
|
6114
|
-
targetId: this.
|
|
6337
|
+
targetId: this._targetId,
|
|
6115
6338
|
kind
|
|
6116
6339
|
});
|
|
6117
6340
|
}
|
|
@@ -6133,7 +6356,7 @@ var CrawlPage = class {
|
|
|
6133
6356
|
async download(ref, path2, opts) {
|
|
6134
6357
|
return downloadViaPlaywright({
|
|
6135
6358
|
cdpUrl: this.cdpUrl,
|
|
6136
|
-
targetId: this.
|
|
6359
|
+
targetId: this._targetId,
|
|
6137
6360
|
ref,
|
|
6138
6361
|
path: path2,
|
|
6139
6362
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -6151,7 +6374,7 @@ var CrawlPage = class {
|
|
|
6151
6374
|
async waitForDownload(opts) {
|
|
6152
6375
|
return waitForDownloadViaPlaywright({
|
|
6153
6376
|
cdpUrl: this.cdpUrl,
|
|
6154
|
-
targetId: this.
|
|
6377
|
+
targetId: this._targetId,
|
|
6155
6378
|
path: opts?.path,
|
|
6156
6379
|
timeoutMs: opts?.timeoutMs,
|
|
6157
6380
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
@@ -6166,7 +6389,7 @@ var CrawlPage = class {
|
|
|
6166
6389
|
async setOffline(offline) {
|
|
6167
6390
|
return setOfflineViaPlaywright({
|
|
6168
6391
|
cdpUrl: this.cdpUrl,
|
|
6169
|
-
targetId: this.
|
|
6392
|
+
targetId: this._targetId,
|
|
6170
6393
|
offline
|
|
6171
6394
|
});
|
|
6172
6395
|
}
|
|
@@ -6183,7 +6406,7 @@ var CrawlPage = class {
|
|
|
6183
6406
|
async setExtraHeaders(headers) {
|
|
6184
6407
|
return setExtraHTTPHeadersViaPlaywright({
|
|
6185
6408
|
cdpUrl: this.cdpUrl,
|
|
6186
|
-
targetId: this.
|
|
6409
|
+
targetId: this._targetId,
|
|
6187
6410
|
headers
|
|
6188
6411
|
});
|
|
6189
6412
|
}
|
|
@@ -6195,7 +6418,7 @@ var CrawlPage = class {
|
|
|
6195
6418
|
async setHttpCredentials(opts) {
|
|
6196
6419
|
return setHttpCredentialsViaPlaywright({
|
|
6197
6420
|
cdpUrl: this.cdpUrl,
|
|
6198
|
-
targetId: this.
|
|
6421
|
+
targetId: this._targetId,
|
|
6199
6422
|
username: opts.username,
|
|
6200
6423
|
password: opts.password,
|
|
6201
6424
|
clear: opts.clear
|
|
@@ -6215,7 +6438,7 @@ var CrawlPage = class {
|
|
|
6215
6438
|
async setGeolocation(opts) {
|
|
6216
6439
|
return setGeolocationViaPlaywright({
|
|
6217
6440
|
cdpUrl: this.cdpUrl,
|
|
6218
|
-
targetId: this.
|
|
6441
|
+
targetId: this._targetId,
|
|
6219
6442
|
latitude: opts.latitude,
|
|
6220
6443
|
longitude: opts.longitude,
|
|
6221
6444
|
accuracy: opts.accuracy,
|
|
@@ -6236,7 +6459,7 @@ var CrawlPage = class {
|
|
|
6236
6459
|
async emulateMedia(opts) {
|
|
6237
6460
|
return emulateMediaViaPlaywright({
|
|
6238
6461
|
cdpUrl: this.cdpUrl,
|
|
6239
|
-
targetId: this.
|
|
6462
|
+
targetId: this._targetId,
|
|
6240
6463
|
colorScheme: opts.colorScheme
|
|
6241
6464
|
});
|
|
6242
6465
|
}
|
|
@@ -6248,7 +6471,7 @@ var CrawlPage = class {
|
|
|
6248
6471
|
async setLocale(locale) {
|
|
6249
6472
|
return setLocaleViaPlaywright({
|
|
6250
6473
|
cdpUrl: this.cdpUrl,
|
|
6251
|
-
targetId: this.
|
|
6474
|
+
targetId: this._targetId,
|
|
6252
6475
|
locale
|
|
6253
6476
|
});
|
|
6254
6477
|
}
|
|
@@ -6260,7 +6483,7 @@ var CrawlPage = class {
|
|
|
6260
6483
|
async setTimezone(timezoneId) {
|
|
6261
6484
|
return setTimezoneViaPlaywright({
|
|
6262
6485
|
cdpUrl: this.cdpUrl,
|
|
6263
|
-
targetId: this.
|
|
6486
|
+
targetId: this._targetId,
|
|
6264
6487
|
timezoneId
|
|
6265
6488
|
});
|
|
6266
6489
|
}
|
|
@@ -6277,7 +6500,7 @@ var CrawlPage = class {
|
|
|
6277
6500
|
async setDevice(name) {
|
|
6278
6501
|
return setDeviceViaPlaywright({
|
|
6279
6502
|
cdpUrl: this.cdpUrl,
|
|
6280
|
-
targetId: this.
|
|
6503
|
+
targetId: this._targetId,
|
|
6281
6504
|
name
|
|
6282
6505
|
});
|
|
6283
6506
|
}
|
|
@@ -6298,7 +6521,7 @@ var CrawlPage = class {
|
|
|
6298
6521
|
* ```
|
|
6299
6522
|
*/
|
|
6300
6523
|
async detectChallenge() {
|
|
6301
|
-
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6524
|
+
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6302
6525
|
}
|
|
6303
6526
|
/**
|
|
6304
6527
|
* Wait for an anti-bot challenge to resolve on its own.
|
|
@@ -6323,7 +6546,7 @@ var CrawlPage = class {
|
|
|
6323
6546
|
async waitForChallenge(opts) {
|
|
6324
6547
|
return waitForChallengeViaPlaywright({
|
|
6325
6548
|
cdpUrl: this.cdpUrl,
|
|
6326
|
-
targetId: this.
|
|
6549
|
+
targetId: this._targetId,
|
|
6327
6550
|
timeoutMs: opts?.timeoutMs,
|
|
6328
6551
|
pollMs: opts?.pollMs
|
|
6329
6552
|
});
|
|
@@ -6360,8 +6583,24 @@ var CrawlPage = class {
|
|
|
6360
6583
|
*/
|
|
6361
6584
|
async isAuthenticated(rules) {
|
|
6362
6585
|
if (!rules.length) return { authenticated: true, checks: [] };
|
|
6363
|
-
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6586
|
+
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6364
6587
|
const checks = [];
|
|
6588
|
+
const needsBodyText = rules.some((r) => r.text !== void 0 || r.textGone !== void 0);
|
|
6589
|
+
let bodyText = null;
|
|
6590
|
+
if (needsBodyText) {
|
|
6591
|
+
try {
|
|
6592
|
+
const raw = await evaluateViaPlaywright({
|
|
6593
|
+
cdpUrl: this.cdpUrl,
|
|
6594
|
+
targetId: this._targetId,
|
|
6595
|
+
fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
|
|
6596
|
+
});
|
|
6597
|
+
bodyText = typeof raw === "string" ? raw : null;
|
|
6598
|
+
} catch (err) {
|
|
6599
|
+
console.warn(
|
|
6600
|
+
`[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6601
|
+
);
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6365
6604
|
for (const rule of rules) {
|
|
6366
6605
|
if (rule.url !== void 0) {
|
|
6367
6606
|
const currentUrl = page.url();
|
|
@@ -6394,19 +6633,6 @@ var CrawlPage = class {
|
|
|
6394
6633
|
}
|
|
6395
6634
|
}
|
|
6396
6635
|
if (rule.text !== void 0 || rule.textGone !== void 0) {
|
|
6397
|
-
let bodyText = null;
|
|
6398
|
-
try {
|
|
6399
|
-
const raw = await evaluateViaPlaywright({
|
|
6400
|
-
cdpUrl: this.cdpUrl,
|
|
6401
|
-
targetId: this.targetId,
|
|
6402
|
-
fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
|
|
6403
|
-
});
|
|
6404
|
-
bodyText = typeof raw === "string" ? raw : null;
|
|
6405
|
-
} catch (err) {
|
|
6406
|
-
console.warn(
|
|
6407
|
-
`[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6408
|
-
);
|
|
6409
|
-
}
|
|
6410
6636
|
if (rule.text !== void 0) {
|
|
6411
6637
|
if (bodyText === null) {
|
|
6412
6638
|
checks.push({ rule: "text", passed: false, detail: `"${rule.text}" error during evaluation` });
|
|
@@ -6436,7 +6662,7 @@ var CrawlPage = class {
|
|
|
6436
6662
|
try {
|
|
6437
6663
|
const result = await evaluateViaPlaywright({
|
|
6438
6664
|
cdpUrl: this.cdpUrl,
|
|
6439
|
-
targetId: this.
|
|
6665
|
+
targetId: this._targetId,
|
|
6440
6666
|
fn: rule.fn
|
|
6441
6667
|
});
|
|
6442
6668
|
const passed = result !== null && result !== void 0 && result !== false && result !== 0 && result !== "";
|
|
@@ -6485,7 +6711,7 @@ var CrawlPage = class {
|
|
|
6485
6711
|
* ```
|
|
6486
6712
|
*/
|
|
6487
6713
|
async playwrightPage() {
|
|
6488
|
-
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6714
|
+
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6489
6715
|
}
|
|
6490
6716
|
/**
|
|
6491
6717
|
* Create a Playwright `Locator` for a CSS selector on this page.
|
|
@@ -6508,7 +6734,7 @@ var CrawlPage = class {
|
|
|
6508
6734
|
* ```
|
|
6509
6735
|
*/
|
|
6510
6736
|
async locator(selector) {
|
|
6511
|
-
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6737
|
+
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6512
6738
|
return pwPage.locator(selector);
|
|
6513
6739
|
}
|
|
6514
6740
|
};
|
|
@@ -6552,21 +6778,27 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6552
6778
|
static async launch(opts = {}) {
|
|
6553
6779
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6554
6780
|
const chrome = await launchChrome(opts);
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6781
|
+
try {
|
|
6782
|
+
const cdpUrl = `http://127.0.0.1:${String(chrome.cdpPort)}`;
|
|
6783
|
+
const ssrfPolicy = opts.allowInternal === true ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
6784
|
+
const telemetry = {
|
|
6785
|
+
launchMs: chrome.launchMs,
|
|
6786
|
+
timestamps: { startedAt, launchedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
6787
|
+
};
|
|
6788
|
+
const browser = new _BrowserClaw(cdpUrl, chrome, telemetry, ssrfPolicy, opts.recordVideo);
|
|
6789
|
+
if (opts.url !== void 0 && opts.url !== "") {
|
|
6790
|
+
const page = await browser.currentPage();
|
|
6791
|
+
const navT0 = Date.now();
|
|
6792
|
+
await page.goto(opts.url);
|
|
6793
|
+
telemetry.navMs = Date.now() - navT0;
|
|
6794
|
+
telemetry.timestamps.navigatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6795
|
+
}
|
|
6796
|
+
return browser;
|
|
6797
|
+
} catch (err) {
|
|
6798
|
+
await stopChrome(chrome).catch(() => {
|
|
6799
|
+
});
|
|
6800
|
+
throw err;
|
|
6568
6801
|
}
|
|
6569
|
-
return browser;
|
|
6570
6802
|
}
|
|
6571
6803
|
/**
|
|
6572
6804
|
* Connect to an already-running Chrome instance via its CDP endpoint.
|
|
@@ -6723,7 +6955,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6723
6955
|
if (exitReason !== void 0) this._telemetry.exitReason = exitReason;
|
|
6724
6956
|
try {
|
|
6725
6957
|
clearRecordingContext(this.cdpUrl);
|
|
6726
|
-
await
|
|
6958
|
+
await closePlaywrightBrowserConnection({ cdpUrl: this.cdpUrl });
|
|
6727
6959
|
if (this.chrome) {
|
|
6728
6960
|
await stopChrome(this.chrome);
|
|
6729
6961
|
this.chrome = null;
|