@xbrowser/cli 1.10.0 → 1.11.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/{anti-bot-DR56Y63V.js → anti-bot-GTTYNEFB.js} +1 -1
- package/dist/{browser-T3V3JWVH.js → browser-Q5APBNF6.js} +1 -1
- package/dist/{browser-HBL72GPZ.js → browser-V3JIWSTR.js} +2 -2
- package/dist/{browser-WQZ3D6AE.js → browser-XNU7JQYC.js} +3 -2
- package/dist/{cdp-driver-J3YQ4LJV.js → cdp-driver-GD6YBBGE.js} +1 -1
- package/dist/cdp-driver-VEK6BNN6.js +49 -0
- package/dist/{cdp-driver-2T4P4ZE2.js → cdp-driver-XFTTZI5O.js} +436 -56
- package/dist/chunk-3FWLW7FS.js +106 -0
- package/dist/chunk-7OFM755Z.js +3509 -0
- package/dist/{chunk-5UGR6MUK.js → chunk-HBHOKZPN.js} +51 -16
- package/dist/{chunk-BAHSRZIX.js → chunk-HVPUVVSA.js} +13 -10
- package/dist/{chunk-NITFVWWS.js → chunk-JEDP4PJW.js} +448 -65
- package/dist/{chunk-INTQPBYF.js → chunk-JKVUFP3G.js} +6 -2
- package/dist/{chunk-4452SPFI.js → chunk-NODRQGOK.js} +51 -16
- package/dist/{chunk-YMUSHPU4.js → chunk-WHPBNUKB.js} +35 -1
- package/dist/{chunk-WSCP7QCJ.js → chunk-XKQVUFUS.js} +13 -10
- package/dist/{chunk-RRBXV7KE.js → chunk-XQ4HRPDJ.js} +384 -102
- package/dist/cli.js +72 -26
- package/dist/{daemon-client-GKEPT4NY.js → daemon-client-MMDRYCF5.js} +35 -1
- package/dist/{daemon-client-O6BYVRXV.js → daemon-client-Y2YSZCGK.js} +1 -1
- package/dist/daemon-main.js +89 -31
- package/dist/index.d.ts +8 -0
- package/dist/index.js +73 -27
- package/dist/{session-recorder-H3KEYU26.js → session-recorder-3BEVWHOK.js} +1 -1
- package/dist/{session-recorder-QRZMKFVL.js → session-recorder-SLDBENVF.js} +1 -1
- package/dist/{session-replayer-LJUC4TI7.js → session-replayer-WIUYVN5J.js} +90 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
forwardSessionList,
|
|
23
23
|
forwardViewerCheckSelector,
|
|
24
24
|
version
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WHPBNUKB.js";
|
|
26
26
|
import {
|
|
27
27
|
buildViewerUrl
|
|
28
28
|
} from "./chunk-3OD76SUE.js";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
} from "./chunk-Q2DFJQGS.js";
|
|
36
36
|
import {
|
|
37
37
|
SessionRecorder
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-NODRQGOK.js";
|
|
39
39
|
import {
|
|
40
40
|
addKnownIssue,
|
|
41
41
|
getKnowledgePath,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
resolveLaunchOpts,
|
|
60
60
|
saveSessionDiskMeta,
|
|
61
61
|
setActivePage
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-JEDP4PJW.js";
|
|
63
63
|
import "./chunk-TNEN6VQ2.js";
|
|
64
64
|
import {
|
|
65
65
|
errMsg
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import {
|
|
68
68
|
detectAntiBot,
|
|
69
69
|
formatDetectionMessage
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-JKVUFP3G.js";
|
|
71
71
|
import {
|
|
72
72
|
NPM_REGISTRY_URL,
|
|
73
73
|
NPM_SCOPE,
|
|
@@ -460,14 +460,23 @@ var CAPTCHA_SELECTORS = {
|
|
|
460
460
|
generic: '#captcha, .captcha-container, [class*="captcha"]'
|
|
461
461
|
};
|
|
462
462
|
async function detectCaptcha(page) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
463
|
+
const hit = await page.evaluate(`
|
|
464
|
+
(function() {
|
|
465
|
+
var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
|
|
466
|
+
for (var type in S) {
|
|
467
|
+
var sel = S[type];
|
|
468
|
+
var el;
|
|
469
|
+
try { el = document.querySelector(sel); } catch (e) { continue; }
|
|
470
|
+
if (el) {
|
|
471
|
+
var url = el.getAttribute && el.getAttribute('src');
|
|
472
|
+
return { type: type, selector: sel, iframeUrl: url || undefined };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
})()
|
|
477
|
+
`).catch(() => null);
|
|
478
|
+
if (!hit) return null;
|
|
479
|
+
return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
|
|
471
480
|
}
|
|
472
481
|
async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
|
|
473
482
|
const startTime = Date.now();
|
|
@@ -518,12 +527,24 @@ var clickCommand = registerCommand({
|
|
|
518
527
|
let detectedNewPage;
|
|
519
528
|
let cleanup;
|
|
520
529
|
if (ctx.browserContext?.on) {
|
|
530
|
+
const targetIdOf = (p2) => p2._targetId;
|
|
531
|
+
let targetIdsBefore;
|
|
532
|
+
try {
|
|
533
|
+
targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
|
|
534
|
+
} catch {
|
|
535
|
+
targetIdsBefore = /* @__PURE__ */ new Set();
|
|
536
|
+
}
|
|
521
537
|
const pagePromise = new Promise((resolve10) => {
|
|
522
538
|
const timer = setTimeout(() => {
|
|
523
539
|
ctx.browserContext.off("page", handler);
|
|
524
540
|
resolve10(void 0);
|
|
525
541
|
}, 3e3);
|
|
526
542
|
const handler = (page2) => {
|
|
543
|
+
if (targetIdsBefore.has(targetIdOf(page2))) return;
|
|
544
|
+
try {
|
|
545
|
+
if ((page2.url?.() ?? "") === "about:blank") return;
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
527
548
|
clearTimeout(timer);
|
|
528
549
|
ctx.browserContext.off("page", handler);
|
|
529
550
|
resolve10(page2);
|
|
@@ -924,9 +945,11 @@ var mouseCommand = registerCommand({
|
|
|
924
945
|
await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
|
|
925
946
|
break;
|
|
926
947
|
case "down":
|
|
948
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
927
949
|
await ctx.page.mouse.down({ button });
|
|
928
950
|
break;
|
|
929
951
|
case "up":
|
|
952
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
930
953
|
await ctx.page.mouse.up({ button });
|
|
931
954
|
break;
|
|
932
955
|
case "click":
|
|
@@ -945,10 +968,11 @@ import { z as z7 } from "zod";
|
|
|
945
968
|
import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
946
969
|
var evaluateCommand = registerCommand({
|
|
947
970
|
name: "eval",
|
|
948
|
-
description: "Evaluate JavaScript expression in the browser",
|
|
971
|
+
description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
|
|
949
972
|
scope: "page",
|
|
950
973
|
parameters: z7.object({
|
|
951
|
-
expression: z7.string()
|
|
974
|
+
expression: z7.string(),
|
|
975
|
+
frame: z7.string().optional().describe("\u5728 URL \u542B\u6B64\u5B50\u4E32\u7684 iframe \u91CC\u6267\u884C\uFF08\u540C\u8FDB\u7A0B\u7528 contextId\uFF0C\u8DE8\u57DF OOPIF \u7528 Target auto-attach\uFF09")
|
|
952
976
|
}),
|
|
953
977
|
handler: async (p, ctx) => {
|
|
954
978
|
const engine = createRuleEngine();
|
|
@@ -960,7 +984,7 @@ var evaluateCommand = registerCommand({
|
|
|
960
984
|
direction: "client\u2192browser"
|
|
961
985
|
});
|
|
962
986
|
engine.stop();
|
|
963
|
-
const result = await ctx.page.evaluate(p.expression);
|
|
987
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, p.expression) : await ctx.page.evaluate(p.expression);
|
|
964
988
|
const response = ok7({ result });
|
|
965
989
|
if (decision && decision.severity === "danger") {
|
|
966
990
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
@@ -5448,6 +5472,11 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5448
5472
|
const targetPage = pages[p.index];
|
|
5449
5473
|
await targetPage.bringToFront().catch(() => {
|
|
5450
5474
|
});
|
|
5475
|
+
for (let i = 0; i < 5; i++) {
|
|
5476
|
+
const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
|
|
5477
|
+
if (okProbe) break;
|
|
5478
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5479
|
+
}
|
|
5451
5480
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5452
5481
|
if (session) {
|
|
5453
5482
|
setActivePage(session, targetPage);
|
|
@@ -5871,7 +5900,7 @@ function describeSelector(strategy, value, name) {
|
|
|
5871
5900
|
// src/context.ts
|
|
5872
5901
|
function attachDetectAntiBot(ctx) {
|
|
5873
5902
|
ctx.detectAntiBot = async (page, config) => {
|
|
5874
|
-
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-
|
|
5903
|
+
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
|
|
5875
5904
|
return detectAntiBot2(page, config);
|
|
5876
5905
|
};
|
|
5877
5906
|
}
|
|
@@ -7051,7 +7080,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7051
7080
|
}
|
|
7052
7081
|
let targetPageOverride = null;
|
|
7053
7082
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7054
|
-
const { findTargetPage } = await import("./browser-
|
|
7083
|
+
const { findTargetPage } = await import("./browser-Q5APBNF6.js");
|
|
7055
7084
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7056
7085
|
if (!targetPageOverride) {
|
|
7057
7086
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7068,7 +7097,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7068
7097
|
params = result.data;
|
|
7069
7098
|
}
|
|
7070
7099
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7071
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7100
|
+
const { forwardExec } = await import("./daemon-client-Y2YSZCGK.js");
|
|
7072
7101
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7073
7102
|
if (result) return result;
|
|
7074
7103
|
}
|
|
@@ -9679,10 +9708,12 @@ var HTML_TAGS = /* @__PURE__ */ new Set([
|
|
|
9679
9708
|
"map",
|
|
9680
9709
|
"area"
|
|
9681
9710
|
]);
|
|
9711
|
+
var SELECTOR_DSL_PREFIXES = /^(text=|popup-text=|xpath=|role=|css=|chain=)/;
|
|
9682
9712
|
function normalizeSelector(input) {
|
|
9683
9713
|
if (!input) return input;
|
|
9684
9714
|
if (/^[#\.\[\:\/]/.test(input)) return input;
|
|
9685
9715
|
if (input === "*") return input;
|
|
9716
|
+
if (SELECTOR_DSL_PREFIXES.test(input)) return input;
|
|
9686
9717
|
if (/[>\s+,~]/.test(input)) return input;
|
|
9687
9718
|
const tagName = input.match(/^[a-zA-Z][a-zA-Z0-9]*/)?.[0];
|
|
9688
9719
|
if (tagName && HTML_TAGS.has(tagName.toLowerCase())) return input;
|
|
@@ -9857,11 +9888,23 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9857
9888
|
output: options.output || options.o || args[0]
|
|
9858
9889
|
};
|
|
9859
9890
|
break;
|
|
9860
|
-
case "eval":
|
|
9891
|
+
case "eval": {
|
|
9861
9892
|
cmdName = "eval";
|
|
9862
|
-
|
|
9863
|
-
|
|
9893
|
+
const frameVal = options.frame || void 0;
|
|
9894
|
+
const exprParts = [];
|
|
9895
|
+
for (let i = 0; i < args.length; i++) {
|
|
9896
|
+
if (args[i] === "--frame") {
|
|
9897
|
+
i++;
|
|
9898
|
+
continue;
|
|
9899
|
+
}
|
|
9900
|
+
if (args[i].startsWith("--frame=")) continue;
|
|
9901
|
+
exprParts.push(args[i]);
|
|
9902
|
+
}
|
|
9903
|
+
params = { expression: exprParts.join(" ") || options.expression || "" };
|
|
9904
|
+
if (frameVal) params.frame = frameVal;
|
|
9905
|
+
if (!params.expression) outputError("Usage: xbrowser eval <expression> [--frame <url-substring>]");
|
|
9864
9906
|
break;
|
|
9907
|
+
}
|
|
9865
9908
|
case "scroll": {
|
|
9866
9909
|
let direction = "down";
|
|
9867
9910
|
let distance;
|
|
@@ -10564,7 +10607,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10564
10607
|
} catch {
|
|
10565
10608
|
}
|
|
10566
10609
|
try {
|
|
10567
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10610
|
+
const { daemonPing } = await import("./daemon-client-Y2YSZCGK.js");
|
|
10568
10611
|
if (await daemonPing()) {
|
|
10569
10612
|
await fetch("http://localhost:9224/rpc", {
|
|
10570
10613
|
method: "POST",
|
|
@@ -11130,7 +11173,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11130
11173
|
}
|
|
11131
11174
|
}
|
|
11132
11175
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11133
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11176
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-SLDBENVF.js");
|
|
11134
11177
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11135
11178
|
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
11136
11179
|
const { join: join13 } = await import("path");
|
|
@@ -12436,6 +12479,9 @@ var HTTPServer = class {
|
|
|
12436
12479
|
this.server = null;
|
|
12437
12480
|
reject(err);
|
|
12438
12481
|
});
|
|
12482
|
+
server.headersTimeout = 20 * 6e4;
|
|
12483
|
+
server.requestTimeout = 20 * 6e4;
|
|
12484
|
+
server.keepAliveTimeout = 20 * 6e4;
|
|
12439
12485
|
server.listen(this.port, this.host, () => {
|
|
12440
12486
|
const addr = server.address();
|
|
12441
12487
|
if (addr && typeof addr === "object") {
|
|
@@ -13071,7 +13117,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13071
13117
|
}
|
|
13072
13118
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13073
13119
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13074
|
-
const { forwardPluginExec } = await import("./daemon-client-
|
|
13120
|
+
const { forwardPluginExec } = await import("./daemon-client-Y2YSZCGK.js");
|
|
13075
13121
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13076
13122
|
const result = await forwardPluginExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13077
13123
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13111,7 +13157,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13111
13157
|
const targetPage = pages[cmdTabIndex];
|
|
13112
13158
|
await targetPage.bringToFront().catch(() => {
|
|
13113
13159
|
});
|
|
13114
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13160
|
+
const { setActivePage: setActivePage2 } = await import("./browser-Q5APBNF6.js");
|
|
13115
13161
|
setActivePage2(session, targetPage);
|
|
13116
13162
|
}
|
|
13117
13163
|
}
|
|
@@ -13387,7 +13433,7 @@ async function main() {
|
|
|
13387
13433
|
const command = process.argv[2];
|
|
13388
13434
|
const isLongRunning = command === "preview" || command === "serve";
|
|
13389
13435
|
if (!isLongRunning) {
|
|
13390
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
13436
|
+
const { ensureProcessCanExit } = await import("./browser-Q5APBNF6.js");
|
|
13391
13437
|
await ensureProcessCanExit().catch(() => {
|
|
13392
13438
|
});
|
|
13393
13439
|
process.exit(process.exitCode || exitCode);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import "./chunk-KFQGP6VL.js";
|
|
12
12
|
|
|
13
13
|
// src/client/daemon-client.ts
|
|
14
|
+
import { request as httpRequest } from "http";
|
|
14
15
|
var DAEMON_PORT = 9224;
|
|
15
16
|
var DAEMON_BASE = `http://localhost:${DAEMON_PORT}`;
|
|
16
17
|
var _ensurePromise = null;
|
|
@@ -205,7 +206,40 @@ async function forwardRecordSummary(session) {
|
|
|
205
206
|
return rpcCall("record:summary", { session }, 5e3);
|
|
206
207
|
}
|
|
207
208
|
async function forwardReplay(file, session, slowMo) {
|
|
208
|
-
return
|
|
209
|
+
return longRpcCall("replay", { file, session, slowMo }, 20 * 6e4);
|
|
210
|
+
}
|
|
211
|
+
function longRpcCall(method, params, timeoutMs) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const body = JSON.stringify({ method, params });
|
|
214
|
+
const req = httpRequest(
|
|
215
|
+
{
|
|
216
|
+
hostname: "127.0.0.1",
|
|
217
|
+
port: DAEMON_PORT,
|
|
218
|
+
path: "/rpc",
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
|
|
221
|
+
},
|
|
222
|
+
(res) => {
|
|
223
|
+
const chunks = [];
|
|
224
|
+
res.on("data", (c) => chunks.push(c));
|
|
225
|
+
res.on("end", () => {
|
|
226
|
+
try {
|
|
227
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
228
|
+
if (parsed && typeof parsed.error === "string" && Object.keys(parsed).length === 1) {
|
|
229
|
+
reject(new Error(`Daemon error: ${parsed.error}`));
|
|
230
|
+
} else {
|
|
231
|
+
resolve(parsed);
|
|
232
|
+
}
|
|
233
|
+
} catch (e) {
|
|
234
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
req.setTimeout(timeoutMs, () => req.destroy(new Error(`replay RPC timeout after ${timeoutMs}ms`)));
|
|
240
|
+
req.on("error", reject);
|
|
241
|
+
req.end(body);
|
|
242
|
+
});
|
|
209
243
|
}
|
|
210
244
|
async function forwardRecordCheckpoint(session, type, hint, selector) {
|
|
211
245
|
return rpcCall("record:checkpoint", { session, type, hint, selector }, 1e4);
|
package/dist/daemon-main.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-VEDJ5XSQ.js";
|
|
16
16
|
import {
|
|
17
17
|
SessionRecorder
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-HBHOKZPN.js";
|
|
19
19
|
import {
|
|
20
20
|
closeEphemeralContext,
|
|
21
21
|
closeSessionByName,
|
|
@@ -31,8 +31,11 @@ import {
|
|
|
31
31
|
resolveLaunchOpts,
|
|
32
32
|
saveSessionDiskMeta,
|
|
33
33
|
setActivePage
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
34
|
+
} from "./chunk-HVPUVVSA.js";
|
|
35
|
+
import "./chunk-XQ4HRPDJ.js";
|
|
36
|
+
import {
|
|
37
|
+
queryJS
|
|
38
|
+
} from "./chunk-3FWLW7FS.js";
|
|
36
39
|
import "./chunk-TNEN6VQ2.js";
|
|
37
40
|
import {
|
|
38
41
|
errMsg
|
|
@@ -40,7 +43,7 @@ import {
|
|
|
40
43
|
import {
|
|
41
44
|
detectAntiBot,
|
|
42
45
|
formatDetectionMessage
|
|
43
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-JKVUFP3G.js";
|
|
44
47
|
import {
|
|
45
48
|
ScreencastCapturer
|
|
46
49
|
} from "./chunk-BNO7OKO4.js";
|
|
@@ -416,14 +419,23 @@ var CAPTCHA_SELECTORS = {
|
|
|
416
419
|
generic: '#captcha, .captcha-container, [class*="captcha"]'
|
|
417
420
|
};
|
|
418
421
|
async function detectCaptcha(page) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
422
|
+
const hit = await page.evaluate(`
|
|
423
|
+
(function() {
|
|
424
|
+
var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
|
|
425
|
+
for (var type in S) {
|
|
426
|
+
var sel = S[type];
|
|
427
|
+
var el;
|
|
428
|
+
try { el = document.querySelector(sel); } catch (e) { continue; }
|
|
429
|
+
if (el) {
|
|
430
|
+
var url = el.getAttribute && el.getAttribute('src');
|
|
431
|
+
return { type: type, selector: sel, iframeUrl: url || undefined };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return null;
|
|
435
|
+
})()
|
|
436
|
+
`).catch(() => null);
|
|
437
|
+
if (!hit) return null;
|
|
438
|
+
return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
|
|
427
439
|
}
|
|
428
440
|
async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
|
|
429
441
|
const startTime = Date.now();
|
|
@@ -474,12 +486,24 @@ var clickCommand = registerCommand({
|
|
|
474
486
|
let detectedNewPage;
|
|
475
487
|
let cleanup;
|
|
476
488
|
if (ctx.browserContext?.on) {
|
|
489
|
+
const targetIdOf = (p2) => p2._targetId;
|
|
490
|
+
let targetIdsBefore;
|
|
491
|
+
try {
|
|
492
|
+
targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
|
|
493
|
+
} catch {
|
|
494
|
+
targetIdsBefore = /* @__PURE__ */ new Set();
|
|
495
|
+
}
|
|
477
496
|
const pagePromise = new Promise((resolve) => {
|
|
478
497
|
const timer = setTimeout(() => {
|
|
479
498
|
ctx.browserContext.off("page", handler);
|
|
480
499
|
resolve(void 0);
|
|
481
500
|
}, 3e3);
|
|
482
501
|
const handler = (page2) => {
|
|
502
|
+
if (targetIdsBefore.has(targetIdOf(page2))) return;
|
|
503
|
+
try {
|
|
504
|
+
if ((page2.url?.() ?? "") === "about:blank") return;
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
483
507
|
clearTimeout(timer);
|
|
484
508
|
ctx.browserContext.off("page", handler);
|
|
485
509
|
resolve(page2);
|
|
@@ -880,9 +904,11 @@ var mouseCommand = registerCommand({
|
|
|
880
904
|
await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
|
|
881
905
|
break;
|
|
882
906
|
case "down":
|
|
907
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
883
908
|
await ctx.page.mouse.down({ button });
|
|
884
909
|
break;
|
|
885
910
|
case "up":
|
|
911
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
886
912
|
await ctx.page.mouse.up({ button });
|
|
887
913
|
break;
|
|
888
914
|
case "click":
|
|
@@ -901,10 +927,11 @@ import { z as z7 } from "zod";
|
|
|
901
927
|
import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
902
928
|
var evaluateCommand = registerCommand({
|
|
903
929
|
name: "eval",
|
|
904
|
-
description: "Evaluate JavaScript expression in the browser",
|
|
930
|
+
description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
|
|
905
931
|
scope: "page",
|
|
906
932
|
parameters: z7.object({
|
|
907
|
-
expression: z7.string()
|
|
933
|
+
expression: z7.string(),
|
|
934
|
+
frame: z7.string().optional().describe("\u5728 URL \u542B\u6B64\u5B50\u4E32\u7684 iframe \u91CC\u6267\u884C\uFF08\u540C\u8FDB\u7A0B\u7528 contextId\uFF0C\u8DE8\u57DF OOPIF \u7528 Target auto-attach\uFF09")
|
|
908
935
|
}),
|
|
909
936
|
handler: async (p, ctx) => {
|
|
910
937
|
const engine = createRuleEngine();
|
|
@@ -916,7 +943,7 @@ var evaluateCommand = registerCommand({
|
|
|
916
943
|
direction: "client\u2192browser"
|
|
917
944
|
});
|
|
918
945
|
engine.stop();
|
|
919
|
-
const result = await ctx.page.evaluate(p.expression);
|
|
946
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, p.expression) : await ctx.page.evaluate(p.expression);
|
|
920
947
|
const response = ok7({ result });
|
|
921
948
|
if (decision && decision.severity === "danger") {
|
|
922
949
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
@@ -5404,6 +5431,11 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5404
5431
|
const targetPage = pages[p.index];
|
|
5405
5432
|
await targetPage.bringToFront().catch(() => {
|
|
5406
5433
|
});
|
|
5434
|
+
for (let i = 0; i < 5; i++) {
|
|
5435
|
+
const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
|
|
5436
|
+
if (okProbe) break;
|
|
5437
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5438
|
+
}
|
|
5407
5439
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5408
5440
|
if (session) {
|
|
5409
5441
|
setActivePage(session, targetPage);
|
|
@@ -5827,7 +5859,7 @@ function describeSelector(strategy, value, name) {
|
|
|
5827
5859
|
// src/context.ts
|
|
5828
5860
|
function attachDetectAntiBot(ctx) {
|
|
5829
5861
|
ctx.detectAntiBot = async (page, config) => {
|
|
5830
|
-
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-
|
|
5862
|
+
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
|
|
5831
5863
|
return detectAntiBot2(page, config);
|
|
5832
5864
|
};
|
|
5833
5865
|
}
|
|
@@ -6565,7 +6597,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6565
6597
|
}
|
|
6566
6598
|
let targetPageOverride = null;
|
|
6567
6599
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6568
|
-
const { findTargetPage } = await import("./browser-
|
|
6600
|
+
const { findTargetPage } = await import("./browser-XNU7JQYC.js");
|
|
6569
6601
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6570
6602
|
if (!targetPageOverride) {
|
|
6571
6603
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6582,7 +6614,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6582
6614
|
params = result.data;
|
|
6583
6615
|
}
|
|
6584
6616
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6585
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6617
|
+
const { forwardExec } = await import("./daemon-client-MMDRYCF5.js");
|
|
6586
6618
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6587
6619
|
if (result) return result;
|
|
6588
6620
|
}
|
|
@@ -7941,15 +7973,26 @@ function createRPCHandler() {
|
|
|
7941
7973
|
const needsPause = INTERACTION_COMMANDS2.has(command) && !!previewWS;
|
|
7942
7974
|
if (needsPause) await previewWS.pauseScreencast(sessionName);
|
|
7943
7975
|
let urlBeforeCommand;
|
|
7976
|
+
let preElement;
|
|
7944
7977
|
try {
|
|
7945
7978
|
const session = findSession(sessionName);
|
|
7946
7979
|
urlBeforeCommand = session?.page?.url();
|
|
7980
|
+
const sel = cmdParams.selector || cmdParams.css;
|
|
7981
|
+
if (session?.page && sel && INTERACTION_COMMANDS2.has(command)) {
|
|
7982
|
+
preElement = await session.page.evaluate(`
|
|
7983
|
+
(function() {
|
|
7984
|
+
const el = ${queryJS(sel)};
|
|
7985
|
+
if (!el || typeof window.__xb_describe !== 'function') return null;
|
|
7986
|
+
return window.__xb_describe(el);
|
|
7987
|
+
})()
|
|
7988
|
+
`);
|
|
7989
|
+
}
|
|
7947
7990
|
} catch {
|
|
7948
7991
|
}
|
|
7949
7992
|
try {
|
|
7950
7993
|
const result = await executeCommand(command, cmdParams, sessionName, { cdpEndpoint: endpoint });
|
|
7951
7994
|
registerSessionIfNew(sessionName);
|
|
7952
|
-
await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand);
|
|
7995
|
+
await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand, preElement);
|
|
7953
7996
|
return result;
|
|
7954
7997
|
} finally {
|
|
7955
7998
|
if (needsPause) await previewWS.resumeScreencast(sessionName).catch(() => {
|
|
@@ -8104,7 +8147,7 @@ function createRPCHandler() {
|
|
|
8104
8147
|
const limit = params.limit || 50;
|
|
8105
8148
|
return { session: sessionName, commands: commandLogStore.list(sessionName, { limit }) };
|
|
8106
8149
|
}
|
|
8107
|
-
async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand) {
|
|
8150
|
+
async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand, preElement) {
|
|
8108
8151
|
const recorder = activeRecorders.get(sessionName);
|
|
8109
8152
|
if (!recorder) return;
|
|
8110
8153
|
const COMMAND_ACTION_MAP = {
|
|
@@ -8112,33 +8155,48 @@ function createRPCHandler() {
|
|
|
8112
8155
|
fill: "cdp-fill",
|
|
8113
8156
|
click: "cdp-click",
|
|
8114
8157
|
type: "input",
|
|
8115
|
-
select: "change"
|
|
8158
|
+
select: "change",
|
|
8159
|
+
scroll: "scroll"
|
|
8116
8160
|
};
|
|
8117
8161
|
const actionType = COMMAND_ACTION_MAP[command];
|
|
8118
8162
|
if (!actionType) return;
|
|
8119
8163
|
const selector = params.selector || params.css;
|
|
8120
|
-
|
|
8164
|
+
let value = params.value || params.expression;
|
|
8165
|
+
if (value === void 0 || value === "") {
|
|
8166
|
+
value = params.text;
|
|
8167
|
+
}
|
|
8168
|
+
if (command === "scroll") {
|
|
8169
|
+
const dir = params.direction || "down";
|
|
8170
|
+
const dist = params.distance ?? 300;
|
|
8171
|
+
value = `${dir}:${dist}`;
|
|
8172
|
+
}
|
|
8121
8173
|
let currentUrl = params.url || urlBeforeCommand;
|
|
8122
8174
|
if (command !== "goto" && urlBeforeCommand && urlBeforeCommand !== "about:blank") {
|
|
8123
8175
|
currentUrl = urlBeforeCommand;
|
|
8124
8176
|
}
|
|
8125
|
-
let element;
|
|
8126
|
-
if (selector) {
|
|
8177
|
+
let element = preElement;
|
|
8178
|
+
if (!element && selector) {
|
|
8127
8179
|
try {
|
|
8128
8180
|
const session = findSession(sessionName);
|
|
8129
8181
|
if (session?.page) {
|
|
8130
|
-
element = await session.page.evaluate(
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8182
|
+
element = await session.page.evaluate(`
|
|
8183
|
+
(function() {
|
|
8184
|
+
const el = ${queryJS(selector)};
|
|
8185
|
+
if (!el || typeof window.__xb_describe !== 'function') return null;
|
|
8186
|
+
return window.__xb_describe(el);
|
|
8187
|
+
})()
|
|
8188
|
+
`);
|
|
8135
8189
|
}
|
|
8136
8190
|
} catch {
|
|
8137
8191
|
}
|
|
8138
8192
|
}
|
|
8139
8193
|
await recorder.recordCommandAction({
|
|
8140
8194
|
type: actionType,
|
|
8141
|
-
selector
|
|
8195
|
+
// Prefer the described unique selector so the dedup key matches the
|
|
8196
|
+
// real action signal's key (which uses element.selector). The raw
|
|
8197
|
+
// command selector (e.g. "button") never equals the generated unique
|
|
8198
|
+
// selector, so dedup silently failed before (rec-duel d02/d03).
|
|
8199
|
+
selector: element?.selector || selector,
|
|
8142
8200
|
value,
|
|
8143
8201
|
url: currentUrl,
|
|
8144
8202
|
element
|
|
@@ -8348,7 +8406,7 @@ function createRPCHandler() {
|
|
|
8348
8406
|
if (isNewFormat) {
|
|
8349
8407
|
try {
|
|
8350
8408
|
const replayErrors = [];
|
|
8351
|
-
const { SessionReplayer } = await import("./session-replayer-
|
|
8409
|
+
const { SessionReplayer } = await import("./session-replayer-WIUYVN5J.js");
|
|
8352
8410
|
const replayer = new SessionReplayer({
|
|
8353
8411
|
page: session.page,
|
|
8354
8412
|
stepDelay: slowMo * 500,
|
package/dist/index.d.ts
CHANGED
|
@@ -173,6 +173,12 @@ interface XBPage {
|
|
|
173
173
|
}): Promise<void>;
|
|
174
174
|
addInitScript(script: string): Promise<void>;
|
|
175
175
|
discoverFrames(): Promise<XBFrame[]>;
|
|
176
|
+
/**
|
|
177
|
+
* 在 URL 含 urlIncludes 的 iframe 上下文中执行表达式。
|
|
178
|
+
* 同进程 iframe 走 contextId;跨域 OOPIF 走 Target auto-attach。
|
|
179
|
+
* 绕过页面同源策略(CDP 是调试通道)——跨域嵌入内容读写的唯一路径。
|
|
180
|
+
*/
|
|
181
|
+
evaluateInFrame<R = unknown>(urlIncludes: string, expression: string): Promise<R>;
|
|
176
182
|
bringToFront(): Promise<void>;
|
|
177
183
|
setExtraHTTPHeaders(headers: Record<string, string>): Promise<void>;
|
|
178
184
|
setOfflineMode(offline: boolean): Promise<void>;
|
|
@@ -288,6 +294,7 @@ interface XBMouse {
|
|
|
288
294
|
button?: 'left' | 'right' | 'middle';
|
|
289
295
|
clickCount?: number;
|
|
290
296
|
delay?: number;
|
|
297
|
+
stealth?: boolean;
|
|
291
298
|
}): Promise<void>;
|
|
292
299
|
dblclick(x: number, y: number, opts?: {
|
|
293
300
|
button?: 'left' | 'right' | 'middle';
|
|
@@ -311,6 +318,7 @@ interface XBKeyboard {
|
|
|
311
318
|
up(key: string): Promise<void>;
|
|
312
319
|
type(text: string, opts?: {
|
|
313
320
|
delay?: number;
|
|
321
|
+
stealth?: boolean;
|
|
314
322
|
}): Promise<void>;
|
|
315
323
|
insertText(text: string): Promise<void>;
|
|
316
324
|
}
|