browserclaw 0.2.4 → 0.2.6
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/README.md +5 -3
- package/dist/index.cjs +100 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +100 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<h2 align="center">🦞 BrowserClaw — Standalone OpenClaw browser module</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/browserclaw"><img src="https://img.shields.io/npm/v/browserclaw.svg" alt="npm version" /></a>
|
|
5
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
6
|
+
</p>
|
|
5
7
|
|
|
6
8
|
Extracted and refined from [OpenClaw](https://github.com/openclaw/openclaw)'s browser automation module. A standalone, typed library for AI-friendly browser control with **snapshot + ref targeting** — no CSS selectors, no XPath, no vision, just numbered refs that map to interactive elements.
|
|
7
9
|
|
package/dist/index.cjs
CHANGED
|
@@ -408,7 +408,8 @@ async function launchChrome(opts = {}) {
|
|
|
408
408
|
args.push("--no-sandbox", "--disable-setuid-sandbox");
|
|
409
409
|
}
|
|
410
410
|
if (process.platform === "linux") args.push("--disable-dev-shm-usage");
|
|
411
|
-
|
|
411
|
+
const extraArgs = Array.isArray(opts.chromeArgs) ? opts.chromeArgs.filter((a) => typeof a === "string" && a.trim().length > 0) : [];
|
|
412
|
+
if (extraArgs.length) args.push(...extraArgs);
|
|
412
413
|
args.push("about:blank");
|
|
413
414
|
return child_process.spawn(exe.path, args, {
|
|
414
415
|
stdio: "pipe",
|
|
@@ -701,10 +702,20 @@ async function pageTargetId(page) {
|
|
|
701
702
|
}
|
|
702
703
|
async function findPageByTargetId(browser, targetId, cdpUrl) {
|
|
703
704
|
const pages = await getAllPages(browser);
|
|
705
|
+
let resolvedViaCdp = false;
|
|
704
706
|
for (const page of pages) {
|
|
705
|
-
|
|
707
|
+
let tid = null;
|
|
708
|
+
try {
|
|
709
|
+
tid = await pageTargetId(page);
|
|
710
|
+
resolvedViaCdp = true;
|
|
711
|
+
} catch {
|
|
712
|
+
tid = null;
|
|
713
|
+
}
|
|
706
714
|
if (tid && tid === targetId) return page;
|
|
707
715
|
}
|
|
716
|
+
if (!resolvedViaCdp && pages.length === 1) {
|
|
717
|
+
return pages[0];
|
|
718
|
+
}
|
|
708
719
|
if (cdpUrl) {
|
|
709
720
|
try {
|
|
710
721
|
const listUrl = `${cdpUrl.replace(/\/+$/, "").replace(/^ws:/, "http:").replace(/\/cdp$/, "")}/json/list`;
|
|
@@ -835,6 +846,27 @@ function getIndentLevel(line) {
|
|
|
835
846
|
const match = line.match(/^(\s*)/);
|
|
836
847
|
return match ? Math.floor(match[1].length / 2) : 0;
|
|
837
848
|
}
|
|
849
|
+
function matchInteractiveSnapshotLine(line, options) {
|
|
850
|
+
const depth = getIndentLevel(line);
|
|
851
|
+
if (options.maxDepth !== void 0 && depth > options.maxDepth) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
|
|
855
|
+
if (!match) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
const [, , roleRaw, name, suffix] = match;
|
|
859
|
+
if (roleRaw.startsWith("/")) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const role = roleRaw.toLowerCase();
|
|
863
|
+
return {
|
|
864
|
+
roleRaw,
|
|
865
|
+
role,
|
|
866
|
+
...name ? { name } : {},
|
|
867
|
+
suffix
|
|
868
|
+
};
|
|
869
|
+
}
|
|
838
870
|
function createRoleNameTracker() {
|
|
839
871
|
const counts = /* @__PURE__ */ new Map();
|
|
840
872
|
const refsByKey = /* @__PURE__ */ new Map();
|
|
@@ -908,14 +940,11 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
908
940
|
if (options.interactive) {
|
|
909
941
|
const result2 = [];
|
|
910
942
|
for (const line of lines) {
|
|
911
|
-
const
|
|
912
|
-
if (
|
|
913
|
-
const
|
|
914
|
-
if (!match) continue;
|
|
915
|
-
const [, prefix, roleRaw, name, suffix] = match;
|
|
916
|
-
if (roleRaw.startsWith("/")) continue;
|
|
917
|
-
const role = roleRaw.toLowerCase();
|
|
943
|
+
const parsed = matchInteractiveSnapshotLine(line, options);
|
|
944
|
+
if (!parsed) continue;
|
|
945
|
+
const { roleRaw, role, name, suffix } = parsed;
|
|
918
946
|
if (!INTERACTIVE_ROLES.has(role)) continue;
|
|
947
|
+
const prefix = line.match(/^(\s*-\s*)/)?.[1] ?? "";
|
|
919
948
|
const ref = nextRef();
|
|
920
949
|
const nth = tracker.getNextIndex(role, name);
|
|
921
950
|
tracker.trackRef(role, name, ref);
|
|
@@ -978,16 +1007,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
978
1007
|
if (options.interactive) {
|
|
979
1008
|
const out2 = [];
|
|
980
1009
|
for (const line of lines) {
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
983
|
-
const
|
|
984
|
-
if (!match) continue;
|
|
985
|
-
const [, prefix, roleRaw, name, suffix] = match;
|
|
986
|
-
if (roleRaw.startsWith("/")) continue;
|
|
987
|
-
const role = roleRaw.toLowerCase();
|
|
1010
|
+
const parsed = matchInteractiveSnapshotLine(line, options);
|
|
1011
|
+
if (!parsed) continue;
|
|
1012
|
+
const { roleRaw, role, name, suffix } = parsed;
|
|
988
1013
|
if (!INTERACTIVE_ROLES.has(role)) continue;
|
|
989
1014
|
const ref = parseAiSnapshotRef(suffix);
|
|
990
1015
|
if (!ref) continue;
|
|
1016
|
+
const prefix = line.match(/^(\s*-\s*)/)?.[1] ?? "";
|
|
991
1017
|
refs[ref] = { role, ...name ? { name } : {} };
|
|
992
1018
|
out2.push(`${prefix}${roleRaw}${name ? ` "${name}"` : ""}${suffix}`);
|
|
993
1019
|
}
|
|
@@ -1380,6 +1406,60 @@ function assertSafeOutputPath(path2, allowedRoots) {
|
|
|
1380
1406
|
}
|
|
1381
1407
|
}
|
|
1382
1408
|
}
|
|
1409
|
+
function expandIPv6(ip) {
|
|
1410
|
+
let normalized = ip;
|
|
1411
|
+
const v4Match = normalized.match(/^(.+:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
1412
|
+
if (v4Match) {
|
|
1413
|
+
const octets = v4Match[2].split(".").map(Number);
|
|
1414
|
+
if (octets.some((o) => o > 255)) return null;
|
|
1415
|
+
const hexHi = (octets[0] << 8 | octets[1]).toString(16).padStart(4, "0");
|
|
1416
|
+
const hexLo = (octets[2] << 8 | octets[3]).toString(16).padStart(4, "0");
|
|
1417
|
+
normalized = v4Match[1] + hexHi + ":" + hexLo;
|
|
1418
|
+
}
|
|
1419
|
+
const halves = normalized.split("::");
|
|
1420
|
+
if (halves.length > 2) return null;
|
|
1421
|
+
if (halves.length === 2) {
|
|
1422
|
+
const left = halves[0] !== "" ? halves[0].split(":") : [];
|
|
1423
|
+
const right = halves[1] !== "" ? halves[1].split(":") : [];
|
|
1424
|
+
const needed = 8 - left.length - right.length;
|
|
1425
|
+
if (needed < 0) return null;
|
|
1426
|
+
const groups2 = [...left, ...Array(needed).fill("0"), ...right];
|
|
1427
|
+
if (groups2.length !== 8) return null;
|
|
1428
|
+
return groups2.map((g) => g.padStart(4, "0")).join(":");
|
|
1429
|
+
}
|
|
1430
|
+
const groups = normalized.split(":");
|
|
1431
|
+
if (groups.length !== 8) return null;
|
|
1432
|
+
return groups.map((g) => g.padStart(4, "0")).join(":");
|
|
1433
|
+
}
|
|
1434
|
+
function hexToIPv4(hiHex, loHex) {
|
|
1435
|
+
const hi = parseInt(hiHex, 16);
|
|
1436
|
+
const lo = parseInt(loHex, 16);
|
|
1437
|
+
return `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
|
|
1438
|
+
}
|
|
1439
|
+
function extractEmbeddedIPv4(lower) {
|
|
1440
|
+
if (lower.startsWith("::ffff:")) {
|
|
1441
|
+
return lower.slice(7);
|
|
1442
|
+
}
|
|
1443
|
+
const expanded = expandIPv6(lower);
|
|
1444
|
+
if (expanded === null) return "";
|
|
1445
|
+
const groups = expanded.split(":");
|
|
1446
|
+
if (groups.length !== 8) return "";
|
|
1447
|
+
if (groups[0] === "0064" && groups[1] === "ff9b" && groups[2] === "0000" && groups[3] === "0000" && groups[4] === "0000" && groups[5] === "0000") {
|
|
1448
|
+
return hexToIPv4(groups[6], groups[7]);
|
|
1449
|
+
}
|
|
1450
|
+
if (groups[0] === "0064" && groups[1] === "ff9b" && groups[2] === "0001") {
|
|
1451
|
+
return hexToIPv4(groups[6], groups[7]);
|
|
1452
|
+
}
|
|
1453
|
+
if (groups[0] === "2002") {
|
|
1454
|
+
return hexToIPv4(groups[1], groups[2]);
|
|
1455
|
+
}
|
|
1456
|
+
if (groups[0] === "2001" && groups[1] === "0000") {
|
|
1457
|
+
const hiXored = (parseInt(groups[6], 16) ^ 65535).toString(16).padStart(4, "0");
|
|
1458
|
+
const loXored = (parseInt(groups[7], 16) ^ 65535).toString(16).padStart(4, "0");
|
|
1459
|
+
return hexToIPv4(hiXored, loXored);
|
|
1460
|
+
}
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1383
1463
|
function isInternalIP(ip) {
|
|
1384
1464
|
if (/^127\./.test(ip)) return true;
|
|
1385
1465
|
if (/^10\./.test(ip)) return true;
|
|
@@ -1392,9 +1472,10 @@ function isInternalIP(ip) {
|
|
|
1392
1472
|
if (lower === "::1") return true;
|
|
1393
1473
|
if (lower.startsWith("fe80:")) return true;
|
|
1394
1474
|
if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1475
|
+
const embedded = extractEmbeddedIPv4(lower);
|
|
1476
|
+
if (embedded !== null) {
|
|
1477
|
+
if (embedded === "") return true;
|
|
1478
|
+
return isInternalIP(embedded);
|
|
1398
1479
|
}
|
|
1399
1480
|
return false;
|
|
1400
1481
|
}
|