browserclaw 0.2.8 → 0.3.0
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 +64 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -9
- package/dist/index.d.ts +47 -9
- package/dist/index.js +63 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1393,6 +1393,32 @@ var InvalidBrowserNavigationUrlError = class extends Error {
|
|
|
1393
1393
|
this.name = "InvalidBrowserNavigationUrlError";
|
|
1394
1394
|
}
|
|
1395
1395
|
};
|
|
1396
|
+
function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
1397
|
+
return { ssrfPolicy };
|
|
1398
|
+
}
|
|
1399
|
+
async function assertBrowserNavigationAllowed(opts) {
|
|
1400
|
+
const policy = opts.ssrfPolicy;
|
|
1401
|
+
if (policy?.allowPrivateNetwork) return;
|
|
1402
|
+
const allowedHostnames = [
|
|
1403
|
+
...policy?.allowedHostnames ?? [],
|
|
1404
|
+
...policy?.hostnameAllowlist ?? []
|
|
1405
|
+
];
|
|
1406
|
+
if (allowedHostnames.length) {
|
|
1407
|
+
let parsed;
|
|
1408
|
+
try {
|
|
1409
|
+
parsed = new URL(opts.url);
|
|
1410
|
+
} catch {
|
|
1411
|
+
throw new InvalidBrowserNavigationUrlError(`Invalid URL: "${opts.url}"`);
|
|
1412
|
+
}
|
|
1413
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
1414
|
+
if (allowedHostnames.some((h) => h.toLowerCase() === hostname)) return;
|
|
1415
|
+
}
|
|
1416
|
+
if (await isInternalUrlResolved(opts.url, opts.lookupFn)) {
|
|
1417
|
+
throw new InvalidBrowserNavigationUrlError(
|
|
1418
|
+
`Navigation to internal/loopback address blocked: "${opts.url}". Use ssrfPolicy: { allowPrivateNetwork: true } if this is intentional.`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1396
1422
|
function assertSafeOutputPath(path2, allowedRoots) {
|
|
1397
1423
|
if (!path2 || typeof path2 !== "string") {
|
|
1398
1424
|
throw new Error("Output path is required.");
|
|
@@ -1515,7 +1541,7 @@ function isInternalUrl(url) {
|
|
|
1515
1541
|
}
|
|
1516
1542
|
return false;
|
|
1517
1543
|
}
|
|
1518
|
-
async function isInternalUrlResolved(url) {
|
|
1544
|
+
async function isInternalUrlResolved(url, lookupFn = promises.lookup) {
|
|
1519
1545
|
if (isInternalUrl(url)) return true;
|
|
1520
1546
|
let parsed;
|
|
1521
1547
|
try {
|
|
@@ -1524,7 +1550,7 @@ async function isInternalUrlResolved(url) {
|
|
|
1524
1550
|
return true;
|
|
1525
1551
|
}
|
|
1526
1552
|
try {
|
|
1527
|
-
const { address } = await
|
|
1553
|
+
const { address } = await lookupFn(parsed.hostname);
|
|
1528
1554
|
if (isInternalIP(address)) return true;
|
|
1529
1555
|
} catch {
|
|
1530
1556
|
return true;
|
|
@@ -1536,9 +1562,8 @@ async function isInternalUrlResolved(url) {
|
|
|
1536
1562
|
async function navigateViaPlaywright(opts) {
|
|
1537
1563
|
const url = String(opts.url ?? "").trim();
|
|
1538
1564
|
if (!url) throw new Error("url is required");
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1565
|
+
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1566
|
+
await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
|
|
1542
1567
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1543
1568
|
ensurePageState(page);
|
|
1544
1569
|
await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
|
|
@@ -1561,8 +1586,9 @@ async function listPagesViaPlaywright(opts) {
|
|
|
1561
1586
|
}
|
|
1562
1587
|
async function createPageViaPlaywright(opts) {
|
|
1563
1588
|
const targetUrl = (opts.url ?? "").trim() || "about:blank";
|
|
1564
|
-
if (targetUrl !== "about:blank"
|
|
1565
|
-
|
|
1589
|
+
if (targetUrl !== "about:blank") {
|
|
1590
|
+
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1591
|
+
await assertBrowserNavigationAllowed({ url: targetUrl, ssrfPolicy: policy });
|
|
1566
1592
|
}
|
|
1567
1593
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
|
1568
1594
|
const context = browser.contexts()[0] ?? await browser.newContext();
|
|
@@ -1682,6 +1708,7 @@ async function evaluateViaPlaywright(opts) {
|
|
|
1682
1708
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1683
1709
|
ensurePageState(page);
|
|
1684
1710
|
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
1711
|
+
const timeout = opts.timeoutMs != null ? opts.timeoutMs : void 0;
|
|
1685
1712
|
if (opts.ref) {
|
|
1686
1713
|
const locator = refLocator(page, opts.ref);
|
|
1687
1714
|
return await locator.evaluate(
|
|
@@ -1694,10 +1721,11 @@ async function evaluateViaPlaywright(opts) {
|
|
|
1694
1721
|
throw new Error("Invalid evaluate function: " + (err instanceof Error ? err.message : String(err)));
|
|
1695
1722
|
}
|
|
1696
1723
|
},
|
|
1697
|
-
fnText
|
|
1724
|
+
fnText,
|
|
1725
|
+
{ timeout }
|
|
1698
1726
|
);
|
|
1699
1727
|
}
|
|
1700
|
-
|
|
1728
|
+
const evalPromise = page.evaluate(
|
|
1701
1729
|
// eslint-disable-next-line no-eval
|
|
1702
1730
|
(fnBody) => {
|
|
1703
1731
|
try {
|
|
@@ -1709,6 +1737,13 @@ async function evaluateViaPlaywright(opts) {
|
|
|
1709
1737
|
},
|
|
1710
1738
|
fnText
|
|
1711
1739
|
);
|
|
1740
|
+
if (!opts.signal) return evalPromise;
|
|
1741
|
+
return Promise.race([
|
|
1742
|
+
evalPromise,
|
|
1743
|
+
new Promise((_, reject) => {
|
|
1744
|
+
opts.signal.addEventListener("abort", () => reject(new Error("Evaluate aborted")), { once: true });
|
|
1745
|
+
})
|
|
1746
|
+
]);
|
|
1712
1747
|
}
|
|
1713
1748
|
|
|
1714
1749
|
// src/actions/download.ts
|
|
@@ -2070,12 +2105,12 @@ async function storageClearViaPlaywright(opts) {
|
|
|
2070
2105
|
var CrawlPage = class {
|
|
2071
2106
|
cdpUrl;
|
|
2072
2107
|
targetId;
|
|
2073
|
-
|
|
2108
|
+
ssrfPolicy;
|
|
2074
2109
|
/** @internal */
|
|
2075
|
-
constructor(cdpUrl, targetId,
|
|
2110
|
+
constructor(cdpUrl, targetId, ssrfPolicy) {
|
|
2076
2111
|
this.cdpUrl = cdpUrl;
|
|
2077
2112
|
this.targetId = targetId;
|
|
2078
|
-
this.
|
|
2113
|
+
this.ssrfPolicy = ssrfPolicy;
|
|
2079
2114
|
}
|
|
2080
2115
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
2081
2116
|
get id() {
|
|
@@ -2408,7 +2443,7 @@ var CrawlPage = class {
|
|
|
2408
2443
|
targetId: this.targetId,
|
|
2409
2444
|
url,
|
|
2410
2445
|
timeoutMs: opts?.timeoutMs,
|
|
2411
|
-
|
|
2446
|
+
ssrfPolicy: this.ssrfPolicy
|
|
2412
2447
|
});
|
|
2413
2448
|
}
|
|
2414
2449
|
/**
|
|
@@ -2487,7 +2522,9 @@ var CrawlPage = class {
|
|
|
2487
2522
|
cdpUrl: this.cdpUrl,
|
|
2488
2523
|
targetId: this.targetId,
|
|
2489
2524
|
fn,
|
|
2490
|
-
ref: opts?.ref
|
|
2525
|
+
ref: opts?.ref,
|
|
2526
|
+
timeoutMs: opts?.timeoutMs,
|
|
2527
|
+
signal: opts?.signal
|
|
2491
2528
|
});
|
|
2492
2529
|
}
|
|
2493
2530
|
/**
|
|
@@ -2938,12 +2975,12 @@ var CrawlPage = class {
|
|
|
2938
2975
|
};
|
|
2939
2976
|
var BrowserClaw = class _BrowserClaw {
|
|
2940
2977
|
cdpUrl;
|
|
2941
|
-
|
|
2978
|
+
ssrfPolicy;
|
|
2942
2979
|
chrome;
|
|
2943
|
-
constructor(cdpUrl, chrome,
|
|
2980
|
+
constructor(cdpUrl, chrome, ssrfPolicy) {
|
|
2944
2981
|
this.cdpUrl = cdpUrl;
|
|
2945
2982
|
this.chrome = chrome;
|
|
2946
|
-
this.
|
|
2983
|
+
this.ssrfPolicy = ssrfPolicy;
|
|
2947
2984
|
}
|
|
2948
2985
|
/**
|
|
2949
2986
|
* Launch a new Chrome instance and connect to it.
|
|
@@ -2971,7 +3008,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
2971
3008
|
static async launch(opts = {}) {
|
|
2972
3009
|
const chrome = await launchChrome(opts);
|
|
2973
3010
|
const cdpUrl = `http://127.0.0.1:${chrome.cdpPort}`;
|
|
2974
|
-
|
|
3011
|
+
const ssrfPolicy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
3012
|
+
return new _BrowserClaw(cdpUrl, chrome, ssrfPolicy);
|
|
2975
3013
|
}
|
|
2976
3014
|
/**
|
|
2977
3015
|
* Connect to an already-running Chrome instance via its CDP endpoint.
|
|
@@ -2992,7 +3030,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
2992
3030
|
throw new Error(`Cannot connect to Chrome at ${cdpUrl}. Is Chrome running with --remote-debugging-port?`);
|
|
2993
3031
|
}
|
|
2994
3032
|
await connectBrowser(cdpUrl, opts?.authToken);
|
|
2995
|
-
|
|
3033
|
+
const ssrfPolicy = opts?.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts?.ssrfPolicy;
|
|
3034
|
+
return new _BrowserClaw(cdpUrl, null, ssrfPolicy);
|
|
2996
3035
|
}
|
|
2997
3036
|
/**
|
|
2998
3037
|
* Open a URL in a new tab and return the page handle.
|
|
@@ -3007,8 +3046,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3007
3046
|
* ```
|
|
3008
3047
|
*/
|
|
3009
3048
|
async open(url) {
|
|
3010
|
-
const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url,
|
|
3011
|
-
return new CrawlPage(this.cdpUrl, tab.targetId, this.
|
|
3049
|
+
const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, ssrfPolicy: this.ssrfPolicy });
|
|
3050
|
+
return new CrawlPage(this.cdpUrl, tab.targetId, this.ssrfPolicy);
|
|
3012
3051
|
}
|
|
3013
3052
|
/**
|
|
3014
3053
|
* Get a CrawlPage handle for the currently active tab.
|
|
@@ -3021,7 +3060,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3021
3060
|
if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
|
|
3022
3061
|
const tid = await pageTargetId(pages[0]).catch(() => null);
|
|
3023
3062
|
if (!tid) throw new Error("Failed to get targetId for the current page.");
|
|
3024
|
-
return new CrawlPage(this.cdpUrl, tid, this.
|
|
3063
|
+
return new CrawlPage(this.cdpUrl, tid, this.ssrfPolicy);
|
|
3025
3064
|
}
|
|
3026
3065
|
/**
|
|
3027
3066
|
* List all open tabs.
|
|
@@ -3056,7 +3095,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3056
3095
|
* @returns CrawlPage for the specified tab
|
|
3057
3096
|
*/
|
|
3058
3097
|
page(targetId) {
|
|
3059
|
-
return new CrawlPage(this.cdpUrl, targetId, this.
|
|
3098
|
+
return new CrawlPage(this.cdpUrl, targetId, this.ssrfPolicy);
|
|
3060
3099
|
}
|
|
3061
3100
|
/** The CDP endpoint URL for this browser connection. */
|
|
3062
3101
|
get url() {
|
|
@@ -3081,5 +3120,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3081
3120
|
exports.BrowserClaw = BrowserClaw;
|
|
3082
3121
|
exports.CrawlPage = CrawlPage;
|
|
3083
3122
|
exports.InvalidBrowserNavigationUrlError = InvalidBrowserNavigationUrlError;
|
|
3123
|
+
exports.assertBrowserNavigationAllowed = assertBrowserNavigationAllowed;
|
|
3124
|
+
exports.withBrowserNavigationPolicy = withBrowserNavigationPolicy;
|
|
3084
3125
|
//# sourceMappingURL=index.cjs.map
|
|
3085
3126
|
//# sourceMappingURL=index.cjs.map
|