browserclaw 0.12.2 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +496 -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 +496 -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,7 +2670,6 @@ 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
|
);
|
|
@@ -2578,7 +2686,6 @@ async function resolvePageByTargetIdOrThrow(opts) {
|
|
|
2578
2686
|
async function getRestoredPageForTarget(opts) {
|
|
2579
2687
|
const page = await getPageForTargetId(opts);
|
|
2580
2688
|
ensurePageState(page);
|
|
2581
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2582
2689
|
return page;
|
|
2583
2690
|
}
|
|
2584
2691
|
|
|
@@ -2632,10 +2739,11 @@ var BROWSER_EVALUATOR = new Function(
|
|
|
2632
2739
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2633
2740
|
' var result = typeof candidate === "function" ? candidate() : candidate;',
|
|
2634
2741
|
' if (result && typeof result.then === "function") {',
|
|
2742
|
+
" var tid;",
|
|
2635
2743
|
" return Promise.race([",
|
|
2636
|
-
" result,",
|
|
2744
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2637
2745
|
" new Promise(function(_, reject) {",
|
|
2638
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2746
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2639
2747
|
" })",
|
|
2640
2748
|
" ]);",
|
|
2641
2749
|
" }",
|
|
@@ -2657,10 +2765,11 @@ var ELEMENT_EVALUATOR = new Function(
|
|
|
2657
2765
|
" catch (_) { candidate = (0, eval)(fnBody); }",
|
|
2658
2766
|
' var result = typeof candidate === "function" ? candidate(el) : candidate;',
|
|
2659
2767
|
' if (result && typeof result.then === "function") {',
|
|
2768
|
+
" var tid;",
|
|
2660
2769
|
" return Promise.race([",
|
|
2661
|
-
" result,",
|
|
2770
|
+
" result.then(function(v) { clearTimeout(tid); return v; }, function(e) { clearTimeout(tid); throw e; }),",
|
|
2662
2771
|
" new Promise(function(_, reject) {",
|
|
2663
|
-
' setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2772
|
+
' tid = setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);',
|
|
2664
2773
|
" })",
|
|
2665
2774
|
" ]);",
|
|
2666
2775
|
" }",
|
|
@@ -2675,10 +2784,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2675
2784
|
if (!fnText) throw new Error("function is required");
|
|
2676
2785
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2677
2786
|
ensurePageState(page);
|
|
2678
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2679
2787
|
const outerTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
2680
|
-
|
|
2681
|
-
evaluateTimeout = Math.min(evaluateTimeout, outerTimeout);
|
|
2788
|
+
const evaluateTimeout = Math.max(1e3, Math.min(12e4, outerTimeout - 1e3));
|
|
2682
2789
|
const signal = opts.signal;
|
|
2683
2790
|
let abortListener;
|
|
2684
2791
|
let abortReject;
|
|
@@ -2692,10 +2799,16 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2692
2799
|
}
|
|
2693
2800
|
if (signal !== void 0) {
|
|
2694
2801
|
const disconnect = () => {
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2802
|
+
const targetId = opts.targetId?.trim() ?? "";
|
|
2803
|
+
if (targetId !== "") {
|
|
2804
|
+
tryTerminateExecutionViaCdp(opts.cdpUrl, targetId).catch(() => {
|
|
2805
|
+
});
|
|
2806
|
+
} else {
|
|
2807
|
+
console.warn("[browserclaw] evaluate abort: no targetId, forcing full disconnect");
|
|
2808
|
+
forceDisconnectPlaywrightConnection({
|
|
2809
|
+
cdpUrl: opts.cdpUrl}).catch(() => {
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2699
2812
|
};
|
|
2700
2813
|
if (signal.aborted) {
|
|
2701
2814
|
disconnect();
|
|
@@ -2731,6 +2844,8 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2731
2844
|
);
|
|
2732
2845
|
} finally {
|
|
2733
2846
|
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
2847
|
+
abortReject = void 0;
|
|
2848
|
+
abortListener = void 0;
|
|
2734
2849
|
}
|
|
2735
2850
|
}
|
|
2736
2851
|
|
|
@@ -2923,7 +3038,7 @@ function isBlockedHostnameOrIp(hostname, policy) {
|
|
|
2923
3038
|
function isPrivateIpAddress(address, policy) {
|
|
2924
3039
|
let normalized = address.trim().toLowerCase();
|
|
2925
3040
|
if (normalized.startsWith("[") && normalized.endsWith("]")) normalized = normalized.slice(1, -1);
|
|
2926
|
-
if (!normalized) return
|
|
3041
|
+
if (!normalized) return true;
|
|
2927
3042
|
const blockOptions = resolveIpv4SpecialUseBlockOptions(policy);
|
|
2928
3043
|
const strictIp = parseCanonicalIpAddress(normalized);
|
|
2929
3044
|
if (strictIp) {
|
|
@@ -3010,6 +3125,25 @@ function createPinnedLookup(params) {
|
|
|
3010
3125
|
cb(null, chosen.address, chosen.family);
|
|
3011
3126
|
});
|
|
3012
3127
|
}
|
|
3128
|
+
var DNS_CACHE_TTL_MS = 3e4;
|
|
3129
|
+
var MAX_DNS_CACHE_SIZE = 100;
|
|
3130
|
+
var dnsCache = /* @__PURE__ */ new Map();
|
|
3131
|
+
function getCachedDnsResult(hostname) {
|
|
3132
|
+
const entry = dnsCache.get(hostname);
|
|
3133
|
+
if (!entry) return void 0;
|
|
3134
|
+
if (Date.now() > entry.expiresAt) {
|
|
3135
|
+
dnsCache.delete(hostname);
|
|
3136
|
+
return void 0;
|
|
3137
|
+
}
|
|
3138
|
+
return entry.result;
|
|
3139
|
+
}
|
|
3140
|
+
function cacheDnsResult(hostname, result) {
|
|
3141
|
+
dnsCache.set(hostname, { result, expiresAt: Date.now() + DNS_CACHE_TTL_MS });
|
|
3142
|
+
if (dnsCache.size > MAX_DNS_CACHE_SIZE) {
|
|
3143
|
+
const first = dnsCache.keys().next();
|
|
3144
|
+
if (first.done !== true) dnsCache.delete(first.value);
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3013
3147
|
async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
3014
3148
|
const normalized = normalizeHostname(hostname);
|
|
3015
3149
|
if (!normalized) throw new InvalidBrowserNavigationUrlError(`Invalid hostname: "${hostname}"`);
|
|
@@ -3028,6 +3162,8 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3028
3162
|
);
|
|
3029
3163
|
}
|
|
3030
3164
|
}
|
|
3165
|
+
const cached = getCachedDnsResult(normalized);
|
|
3166
|
+
if (cached) return cached;
|
|
3031
3167
|
const lookupFn = params.lookupFn ?? promises.lookup;
|
|
3032
3168
|
let results;
|
|
3033
3169
|
try {
|
|
@@ -3057,11 +3193,13 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
3057
3193
|
`Navigation to internal/loopback address blocked: unable to resolve "${hostname}".`
|
|
3058
3194
|
);
|
|
3059
3195
|
}
|
|
3060
|
-
|
|
3196
|
+
const pinned = {
|
|
3061
3197
|
hostname: normalized,
|
|
3062
3198
|
addresses,
|
|
3063
3199
|
lookup: createPinnedLookup({ hostname: normalized, addresses })
|
|
3064
3200
|
};
|
|
3201
|
+
cacheDnsResult(normalized, pinned);
|
|
3202
|
+
return pinned;
|
|
3065
3203
|
}
|
|
3066
3204
|
async function assertBrowserNavigationAllowed(opts) {
|
|
3067
3205
|
const rawUrl = opts.url.trim();
|
|
@@ -3215,13 +3353,27 @@ function buildSiblingTempPath(targetPath) {
|
|
|
3215
3353
|
return path.join(path.dirname(targetPath), `.browserclaw-output-${id}-${safeTail}.part`);
|
|
3216
3354
|
}
|
|
3217
3355
|
async function writeViaSiblingTempPath(params) {
|
|
3218
|
-
|
|
3356
|
+
let rootDir;
|
|
3357
|
+
try {
|
|
3358
|
+
rootDir = await promises$1.realpath(path.resolve(params.rootDir));
|
|
3359
|
+
} catch {
|
|
3360
|
+
console.warn(`[browserclaw] writeViaSiblingTempPath: rootDir realpath failed, using lexical resolve`);
|
|
3361
|
+
rootDir = path.resolve(params.rootDir);
|
|
3362
|
+
}
|
|
3219
3363
|
const requestedTargetPath = path.resolve(params.targetPath);
|
|
3220
3364
|
const targetPath = await promises$1.realpath(path.dirname(requestedTargetPath)).then((realDir) => path.join(realDir, path.basename(requestedTargetPath))).catch(() => requestedTargetPath);
|
|
3221
3365
|
const relativeTargetPath = path.relative(rootDir, targetPath);
|
|
3222
3366
|
if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeTargetPath)) {
|
|
3223
3367
|
throw new Error("Target path is outside the allowed root");
|
|
3224
3368
|
}
|
|
3369
|
+
try {
|
|
3370
|
+
const stat = await promises$1.lstat(targetPath);
|
|
3371
|
+
if (stat.isSymbolicLink()) {
|
|
3372
|
+
throw new Error(`Unsafe output path: "${params.targetPath}" is a symbolic link.`);
|
|
3373
|
+
}
|
|
3374
|
+
} catch (e) {
|
|
3375
|
+
if (e.code !== "ENOENT") throw e;
|
|
3376
|
+
}
|
|
3225
3377
|
const tempPath = buildSiblingTempPath(targetPath);
|
|
3226
3378
|
let renameSucceeded = false;
|
|
3227
3379
|
try {
|
|
@@ -3243,6 +3395,9 @@ async function assertBrowserNavigationResultAllowed(opts) {
|
|
|
3243
3395
|
} catch {
|
|
3244
3396
|
return;
|
|
3245
3397
|
}
|
|
3398
|
+
if (parsed.protocol === "data:" || parsed.protocol === "blob:") {
|
|
3399
|
+
throw new InvalidBrowserNavigationUrlError(`Navigation result blocked: "${parsed.protocol}" URLs are not allowed.`);
|
|
3400
|
+
}
|
|
3246
3401
|
if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || isAllowedNonNetworkNavigationUrl(parsed)) {
|
|
3247
3402
|
await assertBrowserNavigationAllowed(opts);
|
|
3248
3403
|
}
|
|
@@ -3360,9 +3515,10 @@ async function clickViaPlaywright(opts) {
|
|
|
3360
3515
|
if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
|
|
3361
3516
|
const POLL_INTERVAL_MS = 50;
|
|
3362
3517
|
const POLL_TIMEOUT_MS = 500;
|
|
3518
|
+
const ATTR_TIMEOUT_MS = Math.min(timeout, POLL_TIMEOUT_MS);
|
|
3363
3519
|
let changed = false;
|
|
3364
3520
|
for (let elapsed = 0; elapsed < POLL_TIMEOUT_MS; elapsed += POLL_INTERVAL_MS) {
|
|
3365
|
-
const current = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
|
|
3521
|
+
const current = await locator.getAttribute("aria-checked", { timeout: ATTR_TIMEOUT_MS }).catch(() => void 0);
|
|
3366
3522
|
if (current === void 0 || current !== ariaCheckedBefore) {
|
|
3367
3523
|
changed = true;
|
|
3368
3524
|
break;
|
|
@@ -3439,6 +3595,7 @@ async function dragViaPlaywright(opts) {
|
|
|
3439
3595
|
async function fillFormViaPlaywright(opts) {
|
|
3440
3596
|
const page = await getRestoredPageForTarget(opts);
|
|
3441
3597
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3598
|
+
let filledCount = 0;
|
|
3442
3599
|
for (const field of opts.fields) {
|
|
3443
3600
|
const ref = field.ref.trim();
|
|
3444
3601
|
const type = (typeof field.type === "string" ? field.type.trim() : "") || "text";
|
|
@@ -3457,16 +3614,24 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3457
3614
|
try {
|
|
3458
3615
|
await setCheckedViaEvaluate(locator, checked);
|
|
3459
3616
|
} catch (err) {
|
|
3460
|
-
|
|
3617
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3618
|
+
throw new Error(
|
|
3619
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3620
|
+
);
|
|
3461
3621
|
}
|
|
3462
3622
|
}
|
|
3623
|
+
filledCount += 1;
|
|
3463
3624
|
continue;
|
|
3464
3625
|
}
|
|
3465
3626
|
try {
|
|
3466
3627
|
await locator.fill(value, { timeout });
|
|
3467
3628
|
} catch (err) {
|
|
3468
|
-
|
|
3629
|
+
const friendly = toAIFriendlyError(err, ref);
|
|
3630
|
+
throw new Error(
|
|
3631
|
+
`Failed at field "${ref}" (${String(filledCount)}/${String(opts.fields.length)} filled): ${friendly.message}`
|
|
3632
|
+
);
|
|
3469
3633
|
}
|
|
3634
|
+
filledCount += 1;
|
|
3470
3635
|
}
|
|
3471
3636
|
}
|
|
3472
3637
|
async function scrollIntoViewViaPlaywright(opts) {
|
|
@@ -3532,16 +3697,22 @@ async function armDialogViaPlaywright(opts) {
|
|
|
3532
3697
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3533
3698
|
state.armIdDialog = bumpDialogArmId(state);
|
|
3534
3699
|
const armId = state.armIdDialog;
|
|
3700
|
+
const resetArm = () => {
|
|
3701
|
+
if (state.armIdDialog === armId) state.armIdDialog = 0;
|
|
3702
|
+
};
|
|
3703
|
+
page.once("close", resetArm);
|
|
3535
3704
|
page.waitForEvent("dialog", { timeout }).then(async (dialog) => {
|
|
3536
3705
|
if (state.armIdDialog !== armId) return;
|
|
3537
3706
|
try {
|
|
3538
3707
|
if (opts.accept) await dialog.accept(opts.promptText);
|
|
3539
3708
|
else await dialog.dismiss();
|
|
3540
3709
|
} finally {
|
|
3541
|
-
|
|
3710
|
+
resetArm();
|
|
3711
|
+
page.off("close", resetArm);
|
|
3542
3712
|
}
|
|
3543
3713
|
}).catch(() => {
|
|
3544
|
-
|
|
3714
|
+
resetArm();
|
|
3715
|
+
page.off("close", resetArm);
|
|
3545
3716
|
});
|
|
3546
3717
|
}
|
|
3547
3718
|
async function armFileUploadViaPlaywright(opts) {
|
|
@@ -3550,6 +3721,10 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3550
3721
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3551
3722
|
state.armIdUpload = bumpUploadArmId(state);
|
|
3552
3723
|
const armId = state.armIdUpload;
|
|
3724
|
+
const resetArm = () => {
|
|
3725
|
+
if (state.armIdUpload === armId) state.armIdUpload = 0;
|
|
3726
|
+
};
|
|
3727
|
+
page.once("close", resetArm);
|
|
3553
3728
|
page.waitForEvent("filechooser", { timeout }).then(async (fileChooser) => {
|
|
3554
3729
|
if (state.armIdUpload !== armId) return;
|
|
3555
3730
|
if (opts.paths === void 0 || opts.paths.length === 0) {
|
|
@@ -3581,9 +3756,18 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3581
3756
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3582
3757
|
});
|
|
3583
3758
|
}
|
|
3584
|
-
} catch {
|
|
3759
|
+
} catch (e) {
|
|
3760
|
+
console.warn(
|
|
3761
|
+
`[browserclaw] armFileUpload: dispatch events failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3762
|
+
);
|
|
3585
3763
|
}
|
|
3586
|
-
}).catch(() => {
|
|
3764
|
+
}).catch((e) => {
|
|
3765
|
+
console.warn(
|
|
3766
|
+
`[browserclaw] armFileUpload: filechooser wait failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3767
|
+
);
|
|
3768
|
+
}).finally(() => {
|
|
3769
|
+
resetArm();
|
|
3770
|
+
page.off("close", resetArm);
|
|
3587
3771
|
});
|
|
3588
3772
|
}
|
|
3589
3773
|
|
|
@@ -3603,7 +3787,7 @@ function clearRecordingContext(cdpUrl) {
|
|
|
3603
3787
|
}
|
|
3604
3788
|
async function createRecordingContext(browser, cdpUrl, recordVideo) {
|
|
3605
3789
|
const context = await browser.newContext({ recordVideo });
|
|
3606
|
-
observeContext(context);
|
|
3790
|
+
await observeContext(context);
|
|
3607
3791
|
recordingContexts.set(cdpUrl, context);
|
|
3608
3792
|
context.on("close", () => recordingContexts.delete(cdpUrl));
|
|
3609
3793
|
return context;
|
|
@@ -3658,6 +3842,11 @@ async function gotoPageWithNavigationGuard(opts) {
|
|
|
3658
3842
|
await route.continue();
|
|
3659
3843
|
return;
|
|
3660
3844
|
}
|
|
3845
|
+
const isRedirect = request.redirectedFrom() !== null;
|
|
3846
|
+
if (!isRedirect && request.url() !== opts.url) {
|
|
3847
|
+
await route.continue();
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3661
3850
|
try {
|
|
3662
3851
|
await assertBrowserNavigationAllowed({ url: request.url(), ...navigationPolicy });
|
|
3663
3852
|
} catch (err) {
|
|
@@ -3709,6 +3898,7 @@ async function navigateViaPlaywright(opts) {
|
|
|
3709
3898
|
response = await navigate();
|
|
3710
3899
|
} catch (err) {
|
|
3711
3900
|
if (!isRetryableNavigateError(err)) throw err;
|
|
3901
|
+
recordingContexts.delete(opts.cdpUrl);
|
|
3712
3902
|
await forceDisconnectPlaywrightConnection({
|
|
3713
3903
|
cdpUrl: opts.cdpUrl,
|
|
3714
3904
|
targetId: opts.targetId}).catch(() => {
|
|
@@ -3768,6 +3958,7 @@ async function createPageViaPlaywright(opts) {
|
|
|
3768
3958
|
});
|
|
3769
3959
|
} catch (err) {
|
|
3770
3960
|
if (isPolicyDenyNavigationError(err) || err instanceof BlockedBrowserTargetError) throw err;
|
|
3961
|
+
console.warn(`[browserclaw] createPage navigation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3771
3962
|
}
|
|
3772
3963
|
await assertPageNavigationCompletedSafely({
|
|
3773
3964
|
cdpUrl: opts.cdpUrl,
|
|
@@ -3854,39 +4045,46 @@ var MAX_WAIT_TIME_MS = 3e4;
|
|
|
3854
4045
|
async function waitForViaPlaywright(opts) {
|
|
3855
4046
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3856
4047
|
ensurePageState(page);
|
|
3857
|
-
const
|
|
4048
|
+
const totalTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
4049
|
+
const deadline = Date.now() + totalTimeout;
|
|
4050
|
+
const remaining = () => Math.max(500, deadline - Date.now());
|
|
3858
4051
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
3859
4052
|
await page.waitForTimeout(resolveBoundedDelayMs(opts.timeMs, "wait timeMs", MAX_WAIT_TIME_MS));
|
|
3860
4053
|
}
|
|
3861
4054
|
if (opts.text !== void 0 && opts.text !== "") {
|
|
3862
|
-
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4055
|
+
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, {
|
|
4056
|
+
timeout: remaining()
|
|
4057
|
+
});
|
|
3863
4058
|
}
|
|
3864
4059
|
if (opts.textGone !== void 0 && opts.textGone !== "") {
|
|
3865
|
-
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4060
|
+
await page.waitForFunction((text) => !(document.body?.innerText ?? "").includes(text), opts.textGone, {
|
|
4061
|
+
timeout: remaining()
|
|
4062
|
+
});
|
|
3866
4063
|
}
|
|
3867
4064
|
if (opts.selector !== void 0 && opts.selector !== "") {
|
|
3868
4065
|
const selector = opts.selector.trim();
|
|
3869
|
-
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout });
|
|
4066
|
+
if (selector !== "") await page.locator(selector).first().waitFor({ state: "visible", timeout: remaining() });
|
|
3870
4067
|
}
|
|
3871
4068
|
if (opts.url !== void 0 && opts.url !== "") {
|
|
3872
4069
|
const url = opts.url.trim();
|
|
3873
|
-
if (url !== "") await page.waitForURL(url, { timeout });
|
|
4070
|
+
if (url !== "") await page.waitForURL(url, { timeout: remaining() });
|
|
3874
4071
|
}
|
|
3875
4072
|
if (opts.loadState !== void 0) {
|
|
3876
|
-
await page.waitForLoadState(opts.loadState, { timeout });
|
|
4073
|
+
await page.waitForLoadState(opts.loadState, { timeout: remaining() });
|
|
3877
4074
|
}
|
|
3878
4075
|
if (opts.fn !== void 0) {
|
|
3879
4076
|
if (typeof opts.fn === "function") {
|
|
3880
|
-
await page.waitForFunction(opts.fn, opts.arg, { timeout });
|
|
4077
|
+
await page.waitForFunction(opts.fn, opts.arg, { timeout: remaining() });
|
|
3881
4078
|
} else {
|
|
3882
4079
|
const fn = opts.fn.trim();
|
|
3883
|
-
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout });
|
|
4080
|
+
if (fn !== "") await page.waitForFunction(fn, opts.arg, { timeout: remaining() });
|
|
3884
4081
|
}
|
|
3885
4082
|
}
|
|
3886
4083
|
}
|
|
3887
4084
|
|
|
3888
4085
|
// src/actions/batch.ts
|
|
3889
4086
|
var MAX_BATCH_DEPTH = 5;
|
|
4087
|
+
var MAX_BATCH_TIMEOUT_MS = 3e5;
|
|
3890
4088
|
var MAX_BATCH_ACTIONS = 100;
|
|
3891
4089
|
async function executeSingleAction(action, cdpUrl, targetId, evaluateEnabled, depth = 0) {
|
|
3892
4090
|
if (depth > MAX_BATCH_DEPTH) throw new Error(`Batch nesting depth exceeds maximum of ${String(MAX_BATCH_DEPTH)}`);
|
|
@@ -4034,13 +4232,19 @@ async function batchViaPlaywright(opts) {
|
|
|
4034
4232
|
throw new Error(`Batch exceeds maximum of ${String(MAX_BATCH_ACTIONS)} actions`);
|
|
4035
4233
|
const results = [];
|
|
4036
4234
|
const evaluateEnabled = opts.evaluateEnabled !== false;
|
|
4235
|
+
const deadline = Date.now() + MAX_BATCH_TIMEOUT_MS;
|
|
4037
4236
|
for (const action of opts.actions) {
|
|
4237
|
+
if (Date.now() > deadline) {
|
|
4238
|
+
results.push({ ok: false, error: "Batch timeout exceeded" });
|
|
4239
|
+
break;
|
|
4240
|
+
}
|
|
4038
4241
|
try {
|
|
4039
4242
|
await executeSingleAction(action, opts.cdpUrl, opts.targetId, evaluateEnabled, depth);
|
|
4040
4243
|
results.push({ ok: true });
|
|
4041
4244
|
} catch (err) {
|
|
4042
4245
|
const message = err instanceof Error ? err.message : String(err);
|
|
4043
4246
|
results.push({ ok: false, error: message });
|
|
4247
|
+
if (err instanceof BrowserTabNotFoundError || err instanceof BlockedBrowserTargetError) break;
|
|
4044
4248
|
if (opts.stopOnError !== false) break;
|
|
4045
4249
|
}
|
|
4046
4250
|
}
|
|
@@ -4058,8 +4262,10 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4058
4262
|
handler = void 0;
|
|
4059
4263
|
}
|
|
4060
4264
|
};
|
|
4265
|
+
let rejectPromise;
|
|
4061
4266
|
return {
|
|
4062
4267
|
promise: new Promise((resolve2, reject) => {
|
|
4268
|
+
rejectPromise = reject;
|
|
4063
4269
|
handler = (download) => {
|
|
4064
4270
|
if (done) return;
|
|
4065
4271
|
done = true;
|
|
@@ -4078,6 +4284,7 @@ function createPageDownloadWaiter(page, timeoutMs) {
|
|
|
4078
4284
|
if (done) return;
|
|
4079
4285
|
done = true;
|
|
4080
4286
|
cleanup();
|
|
4287
|
+
rejectPromise?.(new Error("Download waiter cancelled"));
|
|
4081
4288
|
}
|
|
4082
4289
|
};
|
|
4083
4290
|
}
|
|
@@ -4109,12 +4316,11 @@ async function downloadViaPlaywright(opts) {
|
|
|
4109
4316
|
await assertSafeOutputPath(opts.path, opts.allowedOutputRoots);
|
|
4110
4317
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4111
4318
|
const state = ensurePageState(page);
|
|
4112
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4113
4319
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
4114
4320
|
const outPath = opts.path.trim();
|
|
4115
4321
|
if (!outPath) throw new Error("path is required");
|
|
4116
|
-
|
|
4117
|
-
|
|
4322
|
+
const armId = bumpDownloadArmId(state);
|
|
4323
|
+
state.armIdDownload = armId;
|
|
4118
4324
|
const waiter = createPageDownloadWaiter(page, timeout);
|
|
4119
4325
|
try {
|
|
4120
4326
|
const locator = refLocator(page, opts.ref);
|
|
@@ -4161,12 +4367,6 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4161
4367
|
}
|
|
4162
4368
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4163
4369
|
ensurePageState(page);
|
|
4164
|
-
if (device.viewport !== null) {
|
|
4165
|
-
await page.setViewportSize({
|
|
4166
|
-
width: device.viewport.width,
|
|
4167
|
-
height: device.viewport.height
|
|
4168
|
-
});
|
|
4169
|
-
}
|
|
4170
4370
|
await withPageScopedCdpClient({
|
|
4171
4371
|
cdpUrl: opts.cdpUrl,
|
|
4172
4372
|
page,
|
|
@@ -4194,11 +4394,24 @@ async function setDeviceViaPlaywright(opts) {
|
|
|
4194
4394
|
}
|
|
4195
4395
|
}
|
|
4196
4396
|
});
|
|
4397
|
+
if (device.viewport !== null) {
|
|
4398
|
+
await page.setViewportSize({
|
|
4399
|
+
width: device.viewport.width,
|
|
4400
|
+
height: device.viewport.height
|
|
4401
|
+
});
|
|
4402
|
+
}
|
|
4197
4403
|
}
|
|
4198
4404
|
async function setExtraHTTPHeadersViaPlaywright(opts) {
|
|
4199
4405
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4200
4406
|
ensurePageState(page);
|
|
4201
|
-
await
|
|
4407
|
+
await withPageScopedCdpClient({
|
|
4408
|
+
cdpUrl: opts.cdpUrl,
|
|
4409
|
+
page,
|
|
4410
|
+
targetId: opts.targetId,
|
|
4411
|
+
fn: async (send) => {
|
|
4412
|
+
await send("Network.setExtraHTTPHeaders", { headers: opts.headers });
|
|
4413
|
+
}
|
|
4414
|
+
});
|
|
4202
4415
|
}
|
|
4203
4416
|
async function setGeolocationViaPlaywright(opts) {
|
|
4204
4417
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
@@ -4206,7 +4419,8 @@ async function setGeolocationViaPlaywright(opts) {
|
|
|
4206
4419
|
const context = page.context();
|
|
4207
4420
|
if (opts.clear === true) {
|
|
4208
4421
|
await context.setGeolocation(null);
|
|
4209
|
-
await context.clearPermissions().catch(() => {
|
|
4422
|
+
await context.clearPermissions().catch((err) => {
|
|
4423
|
+
console.warn(`[browserclaw] clearPermissions failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4210
4424
|
});
|
|
4211
4425
|
return;
|
|
4212
4426
|
}
|
|
@@ -4508,7 +4722,6 @@ async function waitForRequestViaPlaywright(opts) {
|
|
|
4508
4722
|
async function takeScreenshotViaPlaywright(opts) {
|
|
4509
4723
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4510
4724
|
ensurePageState(page);
|
|
4511
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4512
4725
|
const type = opts.type ?? "png";
|
|
4513
4726
|
if (opts.ref !== void 0 && opts.ref !== "") {
|
|
4514
4727
|
if (opts.fullPage === true) throw new Error("fullPage is not supported for element screenshots");
|
|
@@ -4523,7 +4736,6 @@ async function takeScreenshotViaPlaywright(opts) {
|
|
|
4523
4736
|
async function screenshotWithLabelsViaPlaywright(opts) {
|
|
4524
4737
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4525
4738
|
ensurePageState(page);
|
|
4526
|
-
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
4527
4739
|
const maxLabels = typeof opts.maxLabels === "number" && Number.isFinite(opts.maxLabels) ? Math.max(1, Math.floor(opts.maxLabels)) : 150;
|
|
4528
4740
|
const type = opts.type ?? "png";
|
|
4529
4741
|
const refs = opts.refs.slice(0, maxLabels);
|
|
@@ -4587,7 +4799,8 @@ async function screenshotWithLabelsViaPlaywright(opts) {
|
|
|
4587
4799
|
document.querySelectorAll("[data-browserclaw-labels]").forEach((el) => {
|
|
4588
4800
|
el.remove();
|
|
4589
4801
|
});
|
|
4590
|
-
}).catch(() => {
|
|
4802
|
+
}).catch((err) => {
|
|
4803
|
+
console.warn(`[browserclaw] label overlay cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4591
4804
|
});
|
|
4592
4805
|
}
|
|
4593
4806
|
}
|
|
@@ -4615,17 +4828,14 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4615
4828
|
if (!ctxState.traceActive) {
|
|
4616
4829
|
throw new Error("No active trace. Start a trace before stopping it.");
|
|
4617
4830
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
} finally {
|
|
4627
|
-
ctxState.traceActive = false;
|
|
4628
|
-
}
|
|
4831
|
+
ctxState.traceActive = false;
|
|
4832
|
+
await writeViaSiblingTempPath({
|
|
4833
|
+
rootDir: path.dirname(opts.path),
|
|
4834
|
+
targetPath: opts.path,
|
|
4835
|
+
writeTemp: async (tempPath) => {
|
|
4836
|
+
await context.tracing.stop({ path: tempPath });
|
|
4837
|
+
}
|
|
4838
|
+
});
|
|
4629
4839
|
}
|
|
4630
4840
|
|
|
4631
4841
|
// src/snapshot/ref-map.ts
|
|
@@ -5186,17 +5396,27 @@ async function storageClearViaPlaywright(opts) {
|
|
|
5186
5396
|
// src/browser.ts
|
|
5187
5397
|
var CrawlPage = class {
|
|
5188
5398
|
cdpUrl;
|
|
5189
|
-
|
|
5399
|
+
_targetId;
|
|
5190
5400
|
ssrfPolicy;
|
|
5191
5401
|
/** @internal */
|
|
5192
5402
|
constructor(cdpUrl, targetId, ssrfPolicy) {
|
|
5193
5403
|
this.cdpUrl = cdpUrl;
|
|
5194
|
-
this.
|
|
5404
|
+
this._targetId = targetId;
|
|
5195
5405
|
this.ssrfPolicy = ssrfPolicy;
|
|
5196
5406
|
}
|
|
5197
5407
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
5198
5408
|
get id() {
|
|
5199
|
-
return this.
|
|
5409
|
+
return this._targetId;
|
|
5410
|
+
}
|
|
5411
|
+
/**
|
|
5412
|
+
* Refresh the target ID by re-resolving the page from the browser.
|
|
5413
|
+
* Useful after reconnection when the old target ID may be stale.
|
|
5414
|
+
*/
|
|
5415
|
+
async refreshTargetId() {
|
|
5416
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5417
|
+
const newId = await pageTargetId(page);
|
|
5418
|
+
if (newId !== null && newId !== "") this._targetId = newId;
|
|
5419
|
+
return this._targetId;
|
|
5200
5420
|
}
|
|
5201
5421
|
// ── Snapshot ──────────────────────────────────────────────────
|
|
5202
5422
|
/**
|
|
@@ -5224,7 +5444,7 @@ var CrawlPage = class {
|
|
|
5224
5444
|
if (opts?.mode === "role") {
|
|
5225
5445
|
return snapshotRole({
|
|
5226
5446
|
cdpUrl: this.cdpUrl,
|
|
5227
|
-
targetId: this.
|
|
5447
|
+
targetId: this._targetId,
|
|
5228
5448
|
selector: opts.selector,
|
|
5229
5449
|
frameSelector: opts.frameSelector,
|
|
5230
5450
|
refsMode: opts.refsMode,
|
|
@@ -5243,7 +5463,7 @@ var CrawlPage = class {
|
|
|
5243
5463
|
}
|
|
5244
5464
|
return snapshotAi({
|
|
5245
5465
|
cdpUrl: this.cdpUrl,
|
|
5246
|
-
targetId: this.
|
|
5466
|
+
targetId: this._targetId,
|
|
5247
5467
|
maxChars: opts?.maxChars,
|
|
5248
5468
|
options: {
|
|
5249
5469
|
interactive: opts?.interactive,
|
|
@@ -5262,7 +5482,7 @@ var CrawlPage = class {
|
|
|
5262
5482
|
* @returns Array of accessibility tree nodes
|
|
5263
5483
|
*/
|
|
5264
5484
|
async ariaSnapshot(opts) {
|
|
5265
|
-
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5485
|
+
return snapshotAria({ cdpUrl: this.cdpUrl, targetId: this._targetId, limit: opts?.limit });
|
|
5266
5486
|
}
|
|
5267
5487
|
// ── Interactions ─────────────────────────────────────────────
|
|
5268
5488
|
/**
|
|
@@ -5282,7 +5502,7 @@ var CrawlPage = class {
|
|
|
5282
5502
|
async click(ref, opts) {
|
|
5283
5503
|
return clickViaPlaywright({
|
|
5284
5504
|
cdpUrl: this.cdpUrl,
|
|
5285
|
-
targetId: this.
|
|
5505
|
+
targetId: this._targetId,
|
|
5286
5506
|
ref,
|
|
5287
5507
|
doubleClick: opts?.doubleClick,
|
|
5288
5508
|
button: opts?.button,
|
|
@@ -5309,7 +5529,7 @@ var CrawlPage = class {
|
|
|
5309
5529
|
async clickBySelector(selector, opts) {
|
|
5310
5530
|
return clickViaPlaywright({
|
|
5311
5531
|
cdpUrl: this.cdpUrl,
|
|
5312
|
-
targetId: this.
|
|
5532
|
+
targetId: this._targetId,
|
|
5313
5533
|
selector,
|
|
5314
5534
|
doubleClick: opts?.doubleClick,
|
|
5315
5535
|
button: opts?.button,
|
|
@@ -5338,7 +5558,7 @@ var CrawlPage = class {
|
|
|
5338
5558
|
async mouseClick(x, y, opts) {
|
|
5339
5559
|
return mouseClickViaPlaywright({
|
|
5340
5560
|
cdpUrl: this.cdpUrl,
|
|
5341
|
-
targetId: this.
|
|
5561
|
+
targetId: this._targetId,
|
|
5342
5562
|
x,
|
|
5343
5563
|
y,
|
|
5344
5564
|
button: opts?.button,
|
|
@@ -5365,7 +5585,7 @@ var CrawlPage = class {
|
|
|
5365
5585
|
async pressAndHold(x, y, opts) {
|
|
5366
5586
|
return pressAndHoldViaCdp({
|
|
5367
5587
|
cdpUrl: this.cdpUrl,
|
|
5368
|
-
targetId: this.
|
|
5588
|
+
targetId: this._targetId,
|
|
5369
5589
|
x,
|
|
5370
5590
|
y,
|
|
5371
5591
|
delay: opts?.delay,
|
|
@@ -5389,7 +5609,7 @@ var CrawlPage = class {
|
|
|
5389
5609
|
async clickByText(text, opts) {
|
|
5390
5610
|
return clickByTextViaPlaywright({
|
|
5391
5611
|
cdpUrl: this.cdpUrl,
|
|
5392
|
-
targetId: this.
|
|
5612
|
+
targetId: this._targetId,
|
|
5393
5613
|
text,
|
|
5394
5614
|
exact: opts?.exact,
|
|
5395
5615
|
button: opts?.button,
|
|
@@ -5416,7 +5636,7 @@ var CrawlPage = class {
|
|
|
5416
5636
|
async clickByRole(role, name, opts) {
|
|
5417
5637
|
return clickByRoleViaPlaywright({
|
|
5418
5638
|
cdpUrl: this.cdpUrl,
|
|
5419
|
-
targetId: this.
|
|
5639
|
+
targetId: this._targetId,
|
|
5420
5640
|
role,
|
|
5421
5641
|
name,
|
|
5422
5642
|
index: opts?.index,
|
|
@@ -5445,7 +5665,7 @@ var CrawlPage = class {
|
|
|
5445
5665
|
async type(ref, text, opts) {
|
|
5446
5666
|
return typeViaPlaywright({
|
|
5447
5667
|
cdpUrl: this.cdpUrl,
|
|
5448
|
-
targetId: this.
|
|
5668
|
+
targetId: this._targetId,
|
|
5449
5669
|
ref,
|
|
5450
5670
|
text,
|
|
5451
5671
|
submit: opts?.submit,
|
|
@@ -5462,7 +5682,7 @@ var CrawlPage = class {
|
|
|
5462
5682
|
async hover(ref, opts) {
|
|
5463
5683
|
return hoverViaPlaywright({
|
|
5464
5684
|
cdpUrl: this.cdpUrl,
|
|
5465
|
-
targetId: this.
|
|
5685
|
+
targetId: this._targetId,
|
|
5466
5686
|
ref,
|
|
5467
5687
|
timeoutMs: opts?.timeoutMs
|
|
5468
5688
|
});
|
|
@@ -5482,7 +5702,7 @@ var CrawlPage = class {
|
|
|
5482
5702
|
async select(ref, ...values) {
|
|
5483
5703
|
return selectOptionViaPlaywright({
|
|
5484
5704
|
cdpUrl: this.cdpUrl,
|
|
5485
|
-
targetId: this.
|
|
5705
|
+
targetId: this._targetId,
|
|
5486
5706
|
ref,
|
|
5487
5707
|
values
|
|
5488
5708
|
});
|
|
@@ -5497,7 +5717,7 @@ var CrawlPage = class {
|
|
|
5497
5717
|
async drag(startRef, endRef, opts) {
|
|
5498
5718
|
return dragViaPlaywright({
|
|
5499
5719
|
cdpUrl: this.cdpUrl,
|
|
5500
|
-
targetId: this.
|
|
5720
|
+
targetId: this._targetId,
|
|
5501
5721
|
startRef,
|
|
5502
5722
|
endRef,
|
|
5503
5723
|
timeoutMs: opts?.timeoutMs
|
|
@@ -5522,7 +5742,7 @@ var CrawlPage = class {
|
|
|
5522
5742
|
async fill(fields) {
|
|
5523
5743
|
return fillFormViaPlaywright({
|
|
5524
5744
|
cdpUrl: this.cdpUrl,
|
|
5525
|
-
targetId: this.
|
|
5745
|
+
targetId: this._targetId,
|
|
5526
5746
|
fields
|
|
5527
5747
|
});
|
|
5528
5748
|
}
|
|
@@ -5535,7 +5755,7 @@ var CrawlPage = class {
|
|
|
5535
5755
|
async scrollIntoView(ref, opts) {
|
|
5536
5756
|
return scrollIntoViewViaPlaywright({
|
|
5537
5757
|
cdpUrl: this.cdpUrl,
|
|
5538
|
-
targetId: this.
|
|
5758
|
+
targetId: this._targetId,
|
|
5539
5759
|
ref,
|
|
5540
5760
|
timeoutMs: opts?.timeoutMs
|
|
5541
5761
|
});
|
|
@@ -5548,7 +5768,7 @@ var CrawlPage = class {
|
|
|
5548
5768
|
async highlight(ref) {
|
|
5549
5769
|
return highlightViaPlaywright({
|
|
5550
5770
|
cdpUrl: this.cdpUrl,
|
|
5551
|
-
targetId: this.
|
|
5771
|
+
targetId: this._targetId,
|
|
5552
5772
|
ref
|
|
5553
5773
|
});
|
|
5554
5774
|
}
|
|
@@ -5561,7 +5781,7 @@ var CrawlPage = class {
|
|
|
5561
5781
|
async uploadFile(ref, paths) {
|
|
5562
5782
|
return setInputFilesViaPlaywright({
|
|
5563
5783
|
cdpUrl: this.cdpUrl,
|
|
5564
|
-
targetId: this.
|
|
5784
|
+
targetId: this._targetId,
|
|
5565
5785
|
ref,
|
|
5566
5786
|
paths
|
|
5567
5787
|
});
|
|
@@ -5584,7 +5804,7 @@ var CrawlPage = class {
|
|
|
5584
5804
|
async armDialog(opts) {
|
|
5585
5805
|
return armDialogViaPlaywright({
|
|
5586
5806
|
cdpUrl: this.cdpUrl,
|
|
5587
|
-
targetId: this.
|
|
5807
|
+
targetId: this._targetId,
|
|
5588
5808
|
accept: opts.accept,
|
|
5589
5809
|
promptText: opts.promptText,
|
|
5590
5810
|
timeoutMs: opts.timeoutMs
|
|
@@ -5628,7 +5848,7 @@ var CrawlPage = class {
|
|
|
5628
5848
|
async onDialog(handler) {
|
|
5629
5849
|
return setDialogHandler({
|
|
5630
5850
|
cdpUrl: this.cdpUrl,
|
|
5631
|
-
targetId: this.
|
|
5851
|
+
targetId: this._targetId,
|
|
5632
5852
|
handler: handler ?? void 0
|
|
5633
5853
|
});
|
|
5634
5854
|
}
|
|
@@ -5650,7 +5870,7 @@ var CrawlPage = class {
|
|
|
5650
5870
|
async armFileUpload(paths, opts) {
|
|
5651
5871
|
return armFileUploadViaPlaywright({
|
|
5652
5872
|
cdpUrl: this.cdpUrl,
|
|
5653
|
-
targetId: this.
|
|
5873
|
+
targetId: this._targetId,
|
|
5654
5874
|
paths,
|
|
5655
5875
|
timeoutMs: opts?.timeoutMs
|
|
5656
5876
|
});
|
|
@@ -5665,7 +5885,7 @@ var CrawlPage = class {
|
|
|
5665
5885
|
async batch(actions, opts) {
|
|
5666
5886
|
return batchViaPlaywright({
|
|
5667
5887
|
cdpUrl: this.cdpUrl,
|
|
5668
|
-
targetId: this.
|
|
5888
|
+
targetId: this._targetId,
|
|
5669
5889
|
actions,
|
|
5670
5890
|
stopOnError: opts?.stopOnError,
|
|
5671
5891
|
evaluateEnabled: opts?.evaluateEnabled
|
|
@@ -5690,7 +5910,7 @@ var CrawlPage = class {
|
|
|
5690
5910
|
async press(key, opts) {
|
|
5691
5911
|
return pressKeyViaPlaywright({
|
|
5692
5912
|
cdpUrl: this.cdpUrl,
|
|
5693
|
-
targetId: this.
|
|
5913
|
+
targetId: this._targetId,
|
|
5694
5914
|
key,
|
|
5695
5915
|
delayMs: opts?.delayMs
|
|
5696
5916
|
});
|
|
@@ -5700,14 +5920,14 @@ var CrawlPage = class {
|
|
|
5700
5920
|
* Get the current URL of the page.
|
|
5701
5921
|
*/
|
|
5702
5922
|
async url() {
|
|
5703
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5923
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5704
5924
|
return page.url();
|
|
5705
5925
|
}
|
|
5706
5926
|
/**
|
|
5707
5927
|
* Get the page title.
|
|
5708
5928
|
*/
|
|
5709
5929
|
async title() {
|
|
5710
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5930
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5711
5931
|
return page.title();
|
|
5712
5932
|
}
|
|
5713
5933
|
/**
|
|
@@ -5720,7 +5940,7 @@ var CrawlPage = class {
|
|
|
5720
5940
|
async goto(url, opts) {
|
|
5721
5941
|
return navigateViaPlaywright({
|
|
5722
5942
|
cdpUrl: this.cdpUrl,
|
|
5723
|
-
targetId: this.
|
|
5943
|
+
targetId: this._targetId,
|
|
5724
5944
|
url,
|
|
5725
5945
|
timeoutMs: opts?.timeoutMs,
|
|
5726
5946
|
ssrfPolicy: this.ssrfPolicy
|
|
@@ -5732,7 +5952,7 @@ var CrawlPage = class {
|
|
|
5732
5952
|
* @param opts - Timeout options
|
|
5733
5953
|
*/
|
|
5734
5954
|
async reload(opts) {
|
|
5735
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5955
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5736
5956
|
ensurePageState(page);
|
|
5737
5957
|
await page.reload({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5738
5958
|
}
|
|
@@ -5742,7 +5962,7 @@ var CrawlPage = class {
|
|
|
5742
5962
|
* @param opts - Timeout options
|
|
5743
5963
|
*/
|
|
5744
5964
|
async goBack(opts) {
|
|
5745
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5965
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5746
5966
|
ensurePageState(page);
|
|
5747
5967
|
await page.goBack({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5748
5968
|
}
|
|
@@ -5752,7 +5972,7 @@ var CrawlPage = class {
|
|
|
5752
5972
|
* @param opts - Timeout options
|
|
5753
5973
|
*/
|
|
5754
5974
|
async goForward(opts) {
|
|
5755
|
-
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this.
|
|
5975
|
+
const page = await getPageForTargetId({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5756
5976
|
ensurePageState(page);
|
|
5757
5977
|
await page.goForward({ timeout: normalizeTimeoutMs(opts?.timeoutMs, 2e4) });
|
|
5758
5978
|
}
|
|
@@ -5775,7 +5995,7 @@ var CrawlPage = class {
|
|
|
5775
5995
|
async waitFor(opts) {
|
|
5776
5996
|
return waitForViaPlaywright({
|
|
5777
5997
|
cdpUrl: this.cdpUrl,
|
|
5778
|
-
targetId: this.
|
|
5998
|
+
targetId: this._targetId,
|
|
5779
5999
|
...opts
|
|
5780
6000
|
});
|
|
5781
6001
|
}
|
|
@@ -5800,7 +6020,7 @@ var CrawlPage = class {
|
|
|
5800
6020
|
async evaluate(fn, opts) {
|
|
5801
6021
|
return evaluateViaPlaywright({
|
|
5802
6022
|
cdpUrl: this.cdpUrl,
|
|
5803
|
-
targetId: this.
|
|
6023
|
+
targetId: this._targetId,
|
|
5804
6024
|
fn,
|
|
5805
6025
|
ref: opts?.ref,
|
|
5806
6026
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5827,7 +6047,7 @@ var CrawlPage = class {
|
|
|
5827
6047
|
async evaluateInAllFrames(fn) {
|
|
5828
6048
|
return evaluateInAllFramesViaPlaywright({
|
|
5829
6049
|
cdpUrl: this.cdpUrl,
|
|
5830
|
-
targetId: this.
|
|
6050
|
+
targetId: this._targetId,
|
|
5831
6051
|
fn
|
|
5832
6052
|
});
|
|
5833
6053
|
}
|
|
@@ -5848,7 +6068,7 @@ var CrawlPage = class {
|
|
|
5848
6068
|
async screenshot(opts) {
|
|
5849
6069
|
const result = await takeScreenshotViaPlaywright({
|
|
5850
6070
|
cdpUrl: this.cdpUrl,
|
|
5851
|
-
targetId: this.
|
|
6071
|
+
targetId: this._targetId,
|
|
5852
6072
|
fullPage: opts?.fullPage,
|
|
5853
6073
|
ref: opts?.ref,
|
|
5854
6074
|
element: opts?.element,
|
|
@@ -5864,7 +6084,7 @@ var CrawlPage = class {
|
|
|
5864
6084
|
* @returns PDF document as a Buffer
|
|
5865
6085
|
*/
|
|
5866
6086
|
async pdf() {
|
|
5867
|
-
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6087
|
+
const result = await pdfViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
5868
6088
|
return result.buffer;
|
|
5869
6089
|
}
|
|
5870
6090
|
/**
|
|
@@ -5885,7 +6105,7 @@ var CrawlPage = class {
|
|
|
5885
6105
|
async screenshotWithLabels(refs, opts) {
|
|
5886
6106
|
return screenshotWithLabelsViaPlaywright({
|
|
5887
6107
|
cdpUrl: this.cdpUrl,
|
|
5888
|
-
targetId: this.
|
|
6108
|
+
targetId: this._targetId,
|
|
5889
6109
|
refs,
|
|
5890
6110
|
maxLabels: opts?.maxLabels,
|
|
5891
6111
|
type: opts?.type
|
|
@@ -5902,7 +6122,7 @@ var CrawlPage = class {
|
|
|
5902
6122
|
async traceStart(opts) {
|
|
5903
6123
|
return traceStartViaPlaywright({
|
|
5904
6124
|
cdpUrl: this.cdpUrl,
|
|
5905
|
-
targetId: this.
|
|
6125
|
+
targetId: this._targetId,
|
|
5906
6126
|
screenshots: opts?.screenshots,
|
|
5907
6127
|
snapshots: opts?.snapshots,
|
|
5908
6128
|
sources: opts?.sources
|
|
@@ -5917,7 +6137,7 @@ var CrawlPage = class {
|
|
|
5917
6137
|
async traceStop(path2, opts) {
|
|
5918
6138
|
return traceStopViaPlaywright({
|
|
5919
6139
|
cdpUrl: this.cdpUrl,
|
|
5920
|
-
targetId: this.
|
|
6140
|
+
targetId: this._targetId,
|
|
5921
6141
|
path: path2,
|
|
5922
6142
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
5923
6143
|
});
|
|
@@ -5938,7 +6158,7 @@ var CrawlPage = class {
|
|
|
5938
6158
|
async responseBody(url, opts) {
|
|
5939
6159
|
return responseBodyViaPlaywright({
|
|
5940
6160
|
cdpUrl: this.cdpUrl,
|
|
5941
|
-
targetId: this.
|
|
6161
|
+
targetId: this._targetId,
|
|
5942
6162
|
url,
|
|
5943
6163
|
timeoutMs: opts?.timeoutMs,
|
|
5944
6164
|
maxChars: opts?.maxChars
|
|
@@ -5966,7 +6186,7 @@ var CrawlPage = class {
|
|
|
5966
6186
|
async waitForRequest(url, opts) {
|
|
5967
6187
|
return waitForRequestViaPlaywright({
|
|
5968
6188
|
cdpUrl: this.cdpUrl,
|
|
5969
|
-
targetId: this.
|
|
6189
|
+
targetId: this._targetId,
|
|
5970
6190
|
url,
|
|
5971
6191
|
method: opts?.method,
|
|
5972
6192
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -5984,7 +6204,7 @@ var CrawlPage = class {
|
|
|
5984
6204
|
async consoleLogs(opts) {
|
|
5985
6205
|
return getConsoleMessagesViaPlaywright({
|
|
5986
6206
|
cdpUrl: this.cdpUrl,
|
|
5987
|
-
targetId: this.
|
|
6207
|
+
targetId: this._targetId,
|
|
5988
6208
|
level: opts?.level,
|
|
5989
6209
|
clear: opts?.clear
|
|
5990
6210
|
});
|
|
@@ -5998,7 +6218,7 @@ var CrawlPage = class {
|
|
|
5998
6218
|
async pageErrors(opts) {
|
|
5999
6219
|
const result = await getPageErrorsViaPlaywright({
|
|
6000
6220
|
cdpUrl: this.cdpUrl,
|
|
6001
|
-
targetId: this.
|
|
6221
|
+
targetId: this._targetId,
|
|
6002
6222
|
clear: opts?.clear
|
|
6003
6223
|
});
|
|
6004
6224
|
return result.errors;
|
|
@@ -6019,7 +6239,7 @@ var CrawlPage = class {
|
|
|
6019
6239
|
async networkRequests(opts) {
|
|
6020
6240
|
const result = await getNetworkRequestsViaPlaywright({
|
|
6021
6241
|
cdpUrl: this.cdpUrl,
|
|
6022
|
-
targetId: this.
|
|
6242
|
+
targetId: this._targetId,
|
|
6023
6243
|
filter: opts?.filter,
|
|
6024
6244
|
clear: opts?.clear
|
|
6025
6245
|
});
|
|
@@ -6035,7 +6255,7 @@ var CrawlPage = class {
|
|
|
6035
6255
|
async resize(width, height) {
|
|
6036
6256
|
return resizeViewportViaPlaywright({
|
|
6037
6257
|
cdpUrl: this.cdpUrl,
|
|
6038
|
-
targetId: this.
|
|
6258
|
+
targetId: this._targetId,
|
|
6039
6259
|
width,
|
|
6040
6260
|
height
|
|
6041
6261
|
});
|
|
@@ -6047,7 +6267,7 @@ var CrawlPage = class {
|
|
|
6047
6267
|
* @returns Array of cookie objects
|
|
6048
6268
|
*/
|
|
6049
6269
|
async cookies() {
|
|
6050
|
-
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6270
|
+
const result = await cookiesGetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6051
6271
|
return result.cookies;
|
|
6052
6272
|
}
|
|
6053
6273
|
/**
|
|
@@ -6065,11 +6285,11 @@ var CrawlPage = class {
|
|
|
6065
6285
|
* ```
|
|
6066
6286
|
*/
|
|
6067
6287
|
async setCookie(cookie) {
|
|
6068
|
-
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6288
|
+
return cookiesSetViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId, cookie });
|
|
6069
6289
|
}
|
|
6070
6290
|
/** Clear all cookies in the browser context. */
|
|
6071
6291
|
async clearCookies() {
|
|
6072
|
-
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6292
|
+
return cookiesClearViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6073
6293
|
}
|
|
6074
6294
|
/**
|
|
6075
6295
|
* Get values from localStorage or sessionStorage.
|
|
@@ -6081,7 +6301,7 @@ var CrawlPage = class {
|
|
|
6081
6301
|
async storageGet(kind, key) {
|
|
6082
6302
|
const result = await storageGetViaPlaywright({
|
|
6083
6303
|
cdpUrl: this.cdpUrl,
|
|
6084
|
-
targetId: this.
|
|
6304
|
+
targetId: this._targetId,
|
|
6085
6305
|
kind,
|
|
6086
6306
|
key
|
|
6087
6307
|
});
|
|
@@ -6097,7 +6317,7 @@ var CrawlPage = class {
|
|
|
6097
6317
|
async storageSet(kind, key, value) {
|
|
6098
6318
|
return storageSetViaPlaywright({
|
|
6099
6319
|
cdpUrl: this.cdpUrl,
|
|
6100
|
-
targetId: this.
|
|
6320
|
+
targetId: this._targetId,
|
|
6101
6321
|
kind,
|
|
6102
6322
|
key,
|
|
6103
6323
|
value
|
|
@@ -6111,7 +6331,7 @@ var CrawlPage = class {
|
|
|
6111
6331
|
async storageClear(kind) {
|
|
6112
6332
|
return storageClearViaPlaywright({
|
|
6113
6333
|
cdpUrl: this.cdpUrl,
|
|
6114
|
-
targetId: this.
|
|
6334
|
+
targetId: this._targetId,
|
|
6115
6335
|
kind
|
|
6116
6336
|
});
|
|
6117
6337
|
}
|
|
@@ -6133,7 +6353,7 @@ var CrawlPage = class {
|
|
|
6133
6353
|
async download(ref, path2, opts) {
|
|
6134
6354
|
return downloadViaPlaywright({
|
|
6135
6355
|
cdpUrl: this.cdpUrl,
|
|
6136
|
-
targetId: this.
|
|
6356
|
+
targetId: this._targetId,
|
|
6137
6357
|
ref,
|
|
6138
6358
|
path: path2,
|
|
6139
6359
|
timeoutMs: opts?.timeoutMs,
|
|
@@ -6151,7 +6371,7 @@ var CrawlPage = class {
|
|
|
6151
6371
|
async waitForDownload(opts) {
|
|
6152
6372
|
return waitForDownloadViaPlaywright({
|
|
6153
6373
|
cdpUrl: this.cdpUrl,
|
|
6154
|
-
targetId: this.
|
|
6374
|
+
targetId: this._targetId,
|
|
6155
6375
|
path: opts?.path,
|
|
6156
6376
|
timeoutMs: opts?.timeoutMs,
|
|
6157
6377
|
allowedOutputRoots: opts?.allowedOutputRoots
|
|
@@ -6166,7 +6386,7 @@ var CrawlPage = class {
|
|
|
6166
6386
|
async setOffline(offline) {
|
|
6167
6387
|
return setOfflineViaPlaywright({
|
|
6168
6388
|
cdpUrl: this.cdpUrl,
|
|
6169
|
-
targetId: this.
|
|
6389
|
+
targetId: this._targetId,
|
|
6170
6390
|
offline
|
|
6171
6391
|
});
|
|
6172
6392
|
}
|
|
@@ -6183,7 +6403,7 @@ var CrawlPage = class {
|
|
|
6183
6403
|
async setExtraHeaders(headers) {
|
|
6184
6404
|
return setExtraHTTPHeadersViaPlaywright({
|
|
6185
6405
|
cdpUrl: this.cdpUrl,
|
|
6186
|
-
targetId: this.
|
|
6406
|
+
targetId: this._targetId,
|
|
6187
6407
|
headers
|
|
6188
6408
|
});
|
|
6189
6409
|
}
|
|
@@ -6195,7 +6415,7 @@ var CrawlPage = class {
|
|
|
6195
6415
|
async setHttpCredentials(opts) {
|
|
6196
6416
|
return setHttpCredentialsViaPlaywright({
|
|
6197
6417
|
cdpUrl: this.cdpUrl,
|
|
6198
|
-
targetId: this.
|
|
6418
|
+
targetId: this._targetId,
|
|
6199
6419
|
username: opts.username,
|
|
6200
6420
|
password: opts.password,
|
|
6201
6421
|
clear: opts.clear
|
|
@@ -6215,7 +6435,7 @@ var CrawlPage = class {
|
|
|
6215
6435
|
async setGeolocation(opts) {
|
|
6216
6436
|
return setGeolocationViaPlaywright({
|
|
6217
6437
|
cdpUrl: this.cdpUrl,
|
|
6218
|
-
targetId: this.
|
|
6438
|
+
targetId: this._targetId,
|
|
6219
6439
|
latitude: opts.latitude,
|
|
6220
6440
|
longitude: opts.longitude,
|
|
6221
6441
|
accuracy: opts.accuracy,
|
|
@@ -6236,7 +6456,7 @@ var CrawlPage = class {
|
|
|
6236
6456
|
async emulateMedia(opts) {
|
|
6237
6457
|
return emulateMediaViaPlaywright({
|
|
6238
6458
|
cdpUrl: this.cdpUrl,
|
|
6239
|
-
targetId: this.
|
|
6459
|
+
targetId: this._targetId,
|
|
6240
6460
|
colorScheme: opts.colorScheme
|
|
6241
6461
|
});
|
|
6242
6462
|
}
|
|
@@ -6248,7 +6468,7 @@ var CrawlPage = class {
|
|
|
6248
6468
|
async setLocale(locale) {
|
|
6249
6469
|
return setLocaleViaPlaywright({
|
|
6250
6470
|
cdpUrl: this.cdpUrl,
|
|
6251
|
-
targetId: this.
|
|
6471
|
+
targetId: this._targetId,
|
|
6252
6472
|
locale
|
|
6253
6473
|
});
|
|
6254
6474
|
}
|
|
@@ -6260,7 +6480,7 @@ var CrawlPage = class {
|
|
|
6260
6480
|
async setTimezone(timezoneId) {
|
|
6261
6481
|
return setTimezoneViaPlaywright({
|
|
6262
6482
|
cdpUrl: this.cdpUrl,
|
|
6263
|
-
targetId: this.
|
|
6483
|
+
targetId: this._targetId,
|
|
6264
6484
|
timezoneId
|
|
6265
6485
|
});
|
|
6266
6486
|
}
|
|
@@ -6277,7 +6497,7 @@ var CrawlPage = class {
|
|
|
6277
6497
|
async setDevice(name) {
|
|
6278
6498
|
return setDeviceViaPlaywright({
|
|
6279
6499
|
cdpUrl: this.cdpUrl,
|
|
6280
|
-
targetId: this.
|
|
6500
|
+
targetId: this._targetId,
|
|
6281
6501
|
name
|
|
6282
6502
|
});
|
|
6283
6503
|
}
|
|
@@ -6298,7 +6518,7 @@ var CrawlPage = class {
|
|
|
6298
6518
|
* ```
|
|
6299
6519
|
*/
|
|
6300
6520
|
async detectChallenge() {
|
|
6301
|
-
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6521
|
+
return detectChallengeViaPlaywright({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6302
6522
|
}
|
|
6303
6523
|
/**
|
|
6304
6524
|
* Wait for an anti-bot challenge to resolve on its own.
|
|
@@ -6323,7 +6543,7 @@ var CrawlPage = class {
|
|
|
6323
6543
|
async waitForChallenge(opts) {
|
|
6324
6544
|
return waitForChallengeViaPlaywright({
|
|
6325
6545
|
cdpUrl: this.cdpUrl,
|
|
6326
|
-
targetId: this.
|
|
6546
|
+
targetId: this._targetId,
|
|
6327
6547
|
timeoutMs: opts?.timeoutMs,
|
|
6328
6548
|
pollMs: opts?.pollMs
|
|
6329
6549
|
});
|
|
@@ -6360,8 +6580,24 @@ var CrawlPage = class {
|
|
|
6360
6580
|
*/
|
|
6361
6581
|
async isAuthenticated(rules) {
|
|
6362
6582
|
if (!rules.length) return { authenticated: true, checks: [] };
|
|
6363
|
-
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6583
|
+
const page = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6364
6584
|
const checks = [];
|
|
6585
|
+
const needsBodyText = rules.some((r) => r.text !== void 0 || r.textGone !== void 0);
|
|
6586
|
+
let bodyText = null;
|
|
6587
|
+
if (needsBodyText) {
|
|
6588
|
+
try {
|
|
6589
|
+
const raw = await evaluateViaPlaywright({
|
|
6590
|
+
cdpUrl: this.cdpUrl,
|
|
6591
|
+
targetId: this._targetId,
|
|
6592
|
+
fn: '() => { const b = document.body; return b ? b.innerText : ""; }'
|
|
6593
|
+
});
|
|
6594
|
+
bodyText = typeof raw === "string" ? raw : null;
|
|
6595
|
+
} catch (err) {
|
|
6596
|
+
console.warn(
|
|
6597
|
+
`[browserclaw] isAuthenticated body text fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6365
6601
|
for (const rule of rules) {
|
|
6366
6602
|
if (rule.url !== void 0) {
|
|
6367
6603
|
const currentUrl = page.url();
|
|
@@ -6394,19 +6630,6 @@ var CrawlPage = class {
|
|
|
6394
6630
|
}
|
|
6395
6631
|
}
|
|
6396
6632
|
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
6633
|
if (rule.text !== void 0) {
|
|
6411
6634
|
if (bodyText === null) {
|
|
6412
6635
|
checks.push({ rule: "text", passed: false, detail: `"${rule.text}" error during evaluation` });
|
|
@@ -6436,7 +6659,7 @@ var CrawlPage = class {
|
|
|
6436
6659
|
try {
|
|
6437
6660
|
const result = await evaluateViaPlaywright({
|
|
6438
6661
|
cdpUrl: this.cdpUrl,
|
|
6439
|
-
targetId: this.
|
|
6662
|
+
targetId: this._targetId,
|
|
6440
6663
|
fn: rule.fn
|
|
6441
6664
|
});
|
|
6442
6665
|
const passed = result !== null && result !== void 0 && result !== false && result !== 0 && result !== "";
|
|
@@ -6485,7 +6708,7 @@ var CrawlPage = class {
|
|
|
6485
6708
|
* ```
|
|
6486
6709
|
*/
|
|
6487
6710
|
async playwrightPage() {
|
|
6488
|
-
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6711
|
+
return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6489
6712
|
}
|
|
6490
6713
|
/**
|
|
6491
6714
|
* Create a Playwright `Locator` for a CSS selector on this page.
|
|
@@ -6508,7 +6731,7 @@ var CrawlPage = class {
|
|
|
6508
6731
|
* ```
|
|
6509
6732
|
*/
|
|
6510
6733
|
async locator(selector) {
|
|
6511
|
-
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.
|
|
6734
|
+
const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this._targetId });
|
|
6512
6735
|
return pwPage.locator(selector);
|
|
6513
6736
|
}
|
|
6514
6737
|
};
|
|
@@ -6552,21 +6775,27 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6552
6775
|
static async launch(opts = {}) {
|
|
6553
6776
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6554
6777
|
const chrome = await launchChrome(opts);
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6778
|
+
try {
|
|
6779
|
+
const cdpUrl = `http://127.0.0.1:${String(chrome.cdpPort)}`;
|
|
6780
|
+
const ssrfPolicy = opts.allowInternal === true ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
6781
|
+
const telemetry = {
|
|
6782
|
+
launchMs: chrome.launchMs,
|
|
6783
|
+
timestamps: { startedAt, launchedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
6784
|
+
};
|
|
6785
|
+
const browser = new _BrowserClaw(cdpUrl, chrome, telemetry, ssrfPolicy, opts.recordVideo);
|
|
6786
|
+
if (opts.url !== void 0 && opts.url !== "") {
|
|
6787
|
+
const page = await browser.currentPage();
|
|
6788
|
+
const navT0 = Date.now();
|
|
6789
|
+
await page.goto(opts.url);
|
|
6790
|
+
telemetry.navMs = Date.now() - navT0;
|
|
6791
|
+
telemetry.timestamps.navigatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6792
|
+
}
|
|
6793
|
+
return browser;
|
|
6794
|
+
} catch (err) {
|
|
6795
|
+
await stopChrome(chrome).catch(() => {
|
|
6796
|
+
});
|
|
6797
|
+
throw err;
|
|
6568
6798
|
}
|
|
6569
|
-
return browser;
|
|
6570
6799
|
}
|
|
6571
6800
|
/**
|
|
6572
6801
|
* Connect to an already-running Chrome instance via its CDP endpoint.
|
|
@@ -6723,7 +6952,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6723
6952
|
if (exitReason !== void 0) this._telemetry.exitReason = exitReason;
|
|
6724
6953
|
try {
|
|
6725
6954
|
clearRecordingContext(this.cdpUrl);
|
|
6726
|
-
await
|
|
6955
|
+
await closePlaywrightBrowserConnection({ cdpUrl: this.cdpUrl });
|
|
6727
6956
|
if (this.chrome) {
|
|
6728
6957
|
await stopChrome(this.chrome);
|
|
6729
6958
|
this.chrome = null;
|