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