@xbrowser/cli 1.9.9 → 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-FFYPFTBV.js → browser-Q5APBNF6.js} +1 -1
- package/dist/{browser-YCO4GJMZ.js → browser-V3JIWSTR.js} +2 -2
- package/dist/{browser-RHWUAMTB.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-6OZWKXSW.js → chunk-HVPUVVSA.js} +72 -15
- package/dist/{chunk-GUTBIYFW.js → chunk-JEDP4PJW.js} +507 -70
- 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-RZM5CNIY.js → chunk-XKQVUFUS.js} +72 -15
- package/dist/{chunk-RRBXV7KE.js → chunk-XQ4HRPDJ.js} +384 -102
- package/dist/cli.js +79 -29
- 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 +103 -32
- package/dist/index.d.ts +8 -0
- package/dist/index.js +94 -31
- 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
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
queryAllJS,
|
|
3
|
+
queryJS
|
|
4
|
+
} from "./chunk-3FWLW7FS.js";
|
|
1
5
|
import {
|
|
2
6
|
connectToCDP,
|
|
3
7
|
launchChrome
|
|
@@ -18,6 +22,161 @@ import { EventEmitter as EventEmitter2 } from "events";
|
|
|
18
22
|
// src/cdp-driver/page.ts
|
|
19
23
|
import { EventEmitter } from "events";
|
|
20
24
|
|
|
25
|
+
// src/cdp-driver/stealth.ts
|
|
26
|
+
var DEFAULT_STEALTH_CONFIG = {
|
|
27
|
+
bezierCurvature: [0.35, 0.6],
|
|
28
|
+
noiseAmplitude: 5.5,
|
|
29
|
+
overshootRange: [6, 14],
|
|
30
|
+
aimPause: [150, 400],
|
|
31
|
+
pressDuration: [60, 140],
|
|
32
|
+
releaseDrift: [0.8, 2.5],
|
|
33
|
+
landingOffsetSmall: [0.3, 2.5],
|
|
34
|
+
landingOffsetLarge: [1.5, 7],
|
|
35
|
+
smallElementThreshold: 30,
|
|
36
|
+
typingRhythm: {
|
|
37
|
+
fastProb: 0.22,
|
|
38
|
+
fastRange: [25, 60],
|
|
39
|
+
normalRange: [50, 350],
|
|
40
|
+
pauseProb: 0.18,
|
|
41
|
+
pauseRange: [400, 1200]
|
|
42
|
+
},
|
|
43
|
+
keyPressDuration: [50, 110],
|
|
44
|
+
typoProbability: 0.06,
|
|
45
|
+
wheelPeak: 180,
|
|
46
|
+
wheelDecayRate: 0.4
|
|
47
|
+
};
|
|
48
|
+
function rand(min, max) {
|
|
49
|
+
return min + Math.random() * (max - min);
|
|
50
|
+
}
|
|
51
|
+
function cosineEase(t) {
|
|
52
|
+
return 0.5 - 0.5 * Math.cos(Math.PI * t);
|
|
53
|
+
}
|
|
54
|
+
function bezierTrajectory(x0, y0, x1, y1, config = DEFAULT_STEALTH_CONFIG) {
|
|
55
|
+
const dist = Math.hypot(x1 - x0, y1 - y0);
|
|
56
|
+
const n = Math.max(10, Math.min(28, Math.round(dist / 15)));
|
|
57
|
+
const shortMove = dist < 120;
|
|
58
|
+
const curvature = shortMove ? rand(2, 6) : Math.max(dist * rand(...config.bezierCurvature), rand(18, 35));
|
|
59
|
+
const dir = Math.random() < 0.5 ? 1 : -1;
|
|
60
|
+
const d = dist || 1;
|
|
61
|
+
const dx = x1 - x0, dy = y1 - y0;
|
|
62
|
+
const c1x = x0 + dx * 0.3 - dy / d * curvature * 0.5 * dir;
|
|
63
|
+
const c1y = y0 + dy * 0.3 + dx / d * curvature * 0.5 * dir;
|
|
64
|
+
const c2x = x0 + dx * 0.7 - dy / d * curvature * 0.8 * dir;
|
|
65
|
+
const c2y = y0 + dy * 0.7 + dx / d * curvature * 0.8 * dir;
|
|
66
|
+
const points = [];
|
|
67
|
+
for (let i = 1; i <= n; i++) {
|
|
68
|
+
const t = cosineEase(i / n);
|
|
69
|
+
const mt = 1 - t;
|
|
70
|
+
let px = mt ** 3 * x0 + 3 * mt ** 2 * t * c1x + 3 * mt * t ** 2 * c2x + t ** 3 * x1;
|
|
71
|
+
let py = mt ** 3 * y0 + 3 * mt ** 2 * t * c1y + 3 * mt * t ** 2 * c2y + t ** 3 * y1;
|
|
72
|
+
const amp = shortMove ? Math.min(2, config.noiseAmplitude) : config.noiseAmplitude;
|
|
73
|
+
px += rand(-amp, amp);
|
|
74
|
+
py += rand(-amp, amp);
|
|
75
|
+
points.push({ x: px, y: py, delay: rand(9, 16) });
|
|
76
|
+
}
|
|
77
|
+
if (!shortMove) {
|
|
78
|
+
const over = rand(...config.overshootRange);
|
|
79
|
+
const ox = x1 + dx / d * over + rand(-2, 2);
|
|
80
|
+
const oy = y1 + dy / d * over + rand(-2, 2);
|
|
81
|
+
points.push({ x: ox, y: oy, delay: rand(14, 30) });
|
|
82
|
+
points.push({
|
|
83
|
+
x: x1 + dx / d * over * 0.4,
|
|
84
|
+
y: y1 + dy / d * over * 0.4,
|
|
85
|
+
delay: rand(14, 30)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
points.push({ x: x1 + rand(-1, 1), y: y1 + rand(-1, 1), delay: rand(14, 30) });
|
|
89
|
+
return points;
|
|
90
|
+
}
|
|
91
|
+
function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
|
|
92
|
+
const isSmall = Math.min(width, height) < config.smallElementThreshold;
|
|
93
|
+
const range = isSmall ? config.landingOffsetSmall : config.landingOffsetLarge;
|
|
94
|
+
const dx = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
|
|
95
|
+
const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
|
|
96
|
+
return { dx, dy };
|
|
97
|
+
}
|
|
98
|
+
var KEY_MAP = {};
|
|
99
|
+
for (let i = 97; i <= 122; i++) {
|
|
100
|
+
const ch = String.fromCharCode(i);
|
|
101
|
+
KEY_MAP[ch] = { key: ch, code: "Key" + ch.toUpperCase(), vk: i - 32 };
|
|
102
|
+
}
|
|
103
|
+
for (let i = 65; i <= 90; i++) {
|
|
104
|
+
const ch = String.fromCharCode(i);
|
|
105
|
+
KEY_MAP[ch] = { key: ch, code: "Key" + ch, vk: i, shift: true };
|
|
106
|
+
}
|
|
107
|
+
for (let i = 48; i <= 57; i++) {
|
|
108
|
+
const ch = String.fromCharCode(i);
|
|
109
|
+
KEY_MAP[ch] = { key: ch, code: "Digit" + ch, vk: i };
|
|
110
|
+
}
|
|
111
|
+
Object.assign(KEY_MAP, {
|
|
112
|
+
" ": { key: " ", code: "Space", vk: 32 },
|
|
113
|
+
".": { key: ".", code: "Period", vk: 190 },
|
|
114
|
+
"-": { key: "-", code: "Minus", vk: 189 },
|
|
115
|
+
"@": { key: "@", code: "Digit2", vk: 50, shift: true },
|
|
116
|
+
"_": { key: "_", code: "Minus", vk: 189, shift: true }
|
|
117
|
+
});
|
|
118
|
+
function buildStealthInitScript() {
|
|
119
|
+
return [
|
|
120
|
+
"(function(){",
|
|
121
|
+
// 1. AEL event proxy
|
|
122
|
+
" var o=EventTarget.prototype.addEventListener;",
|
|
123
|
+
" var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
|
|
124
|
+
" var _ael=function(t,f){",
|
|
125
|
+
" var op=arguments[2];",
|
|
126
|
+
' if(typeof f!=="function")return o.call(this,t,f,op);',
|
|
127
|
+
" var w=function(e){",
|
|
128
|
+
" if(!e||e.constructor===FocusEvent||e.constructor===KeyboardEvent)return f.call(this,e);",
|
|
129
|
+
" return f.call(this,new Proxy(e,{get:function(k,p){",
|
|
130
|
+
' if(p==="sourceCapabilities")return fc;',
|
|
131
|
+
' if(p==="isTrusted")return true;',
|
|
132
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
|
|
133
|
+
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
|
|
134
|
+
" return k[p]+_f;",
|
|
135
|
+
" }",
|
|
136
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
137
|
+
" }}));",
|
|
138
|
+
" };",
|
|
139
|
+
" return o.call(this,t,w,op);",
|
|
140
|
+
" };",
|
|
141
|
+
" EventTarget.prototype.addEventListener=_ael;",
|
|
142
|
+
// 2. Screen override (prototype-level, not instance-level)
|
|
143
|
+
" var _gw=function(){return 1728};",
|
|
144
|
+
" var _gh=function(){return 1117};",
|
|
145
|
+
" var _gah=function(){return 1092};",
|
|
146
|
+
' Object.defineProperty(Screen.prototype,"width",{get:_gw,configurable:true});',
|
|
147
|
+
' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
|
|
148
|
+
' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
|
|
149
|
+
' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
|
|
150
|
+
" document.hasFocus=function(){return true};",
|
|
151
|
+
// 3. toString disguise (name-list based)
|
|
152
|
+
" var _ts=Function.prototype.toString;",
|
|
153
|
+
" var _hf=document.hasFocus;",
|
|
154
|
+
" Function.prototype.toString=function(){",
|
|
155
|
+
' if(this===_ael)return"function addEventListener(type, callback) { [native code] }";',
|
|
156
|
+
' if(this===_hf)return"function hasFocus() { [native code] }";',
|
|
157
|
+
' if(this===_gw)return"function get width() { [native code] }";',
|
|
158
|
+
' if(this===_gh)return"function get height() { [native code] }";',
|
|
159
|
+
' if(this===_gah)return"function get availHeight() { [native code] }";',
|
|
160
|
+
" return _ts.call(this);",
|
|
161
|
+
" };",
|
|
162
|
+
// 4. onclick prototype hijack (dual-stream consistency)
|
|
163
|
+
" var _ba=function(k,p){",
|
|
164
|
+
' if(p==="isTrusted")return true;',
|
|
165
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
|
|
166
|
+
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
|
|
167
|
+
" }",
|
|
168
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
169
|
+
" };",
|
|
170
|
+
' Object.defineProperty(Document.prototype,"onclick",{',
|
|
171
|
+
" configurable:true,",
|
|
172
|
+
" get:function(){var raw=this.__ocRaw||null;if(!raw)return null;var self=this;",
|
|
173
|
+
" return function(e){return raw.call(self,new Proxy(e,{get:function(k,p){return _ba(k,p)}}))}},",
|
|
174
|
+
" set:function(fn){this.__ocRaw=fn}",
|
|
175
|
+
" });",
|
|
176
|
+
"})()"
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
21
180
|
// src/cdp-driver/mouse.ts
|
|
22
181
|
var XBMouseImpl = class {
|
|
23
182
|
conn;
|
|
@@ -39,18 +198,35 @@ var XBMouseImpl = class {
|
|
|
39
198
|
}
|
|
40
199
|
async click(x, y, opts = {}) {
|
|
41
200
|
const button = opts.button ?? "left";
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
201
|
+
const stealth = opts.stealth ?? true;
|
|
202
|
+
let tx = x, ty = y;
|
|
203
|
+
if (stealth && opts.elementWidth !== void 0 && opts.elementHeight !== void 0) {
|
|
204
|
+
const off = landingOffset(opts.elementWidth, opts.elementHeight);
|
|
205
|
+
tx += off.dx;
|
|
206
|
+
ty += off.dy;
|
|
207
|
+
}
|
|
208
|
+
if (stealth) {
|
|
209
|
+
const traj = bezierTrajectory(this._x, this._y, tx, ty);
|
|
210
|
+
for (const p of traj) {
|
|
211
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
|
|
212
|
+
this._x = p.x;
|
|
213
|
+
this._y = p.y;
|
|
214
|
+
await sleep(p.delay);
|
|
215
|
+
}
|
|
216
|
+
await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
|
|
217
|
+
} else {
|
|
218
|
+
await this.move(tx, ty);
|
|
48
219
|
}
|
|
49
|
-
await this.
|
|
50
|
-
|
|
51
|
-
|
|
220
|
+
await this.down({ button });
|
|
221
|
+
await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
|
|
222
|
+
const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
|
|
223
|
+
const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
|
|
224
|
+
this._x = rx;
|
|
225
|
+
this._y = ry;
|
|
226
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
|
|
227
|
+
for (let i = 1; i < (opts.clickCount ?? 1); i++) {
|
|
228
|
+
if (opts.delay) await sleep(opts.delay);
|
|
52
229
|
await this.down({ button });
|
|
53
|
-
if (delay > 0) await sleep(delay);
|
|
54
230
|
await this.up({ button });
|
|
55
231
|
}
|
|
56
232
|
}
|
|
@@ -229,7 +405,7 @@ var XBKeyboardImpl = class {
|
|
|
229
405
|
}
|
|
230
406
|
};
|
|
231
407
|
function resolveKeyMapping(key) {
|
|
232
|
-
if (
|
|
408
|
+
if (KEY_MAP2[key]) return KEY_MAP2[key];
|
|
233
409
|
if (key.length === 1) {
|
|
234
410
|
const lower = key.toLowerCase();
|
|
235
411
|
if (lower >= "a" && lower <= "z") {
|
|
@@ -246,7 +422,7 @@ function resolveKeyMapping(key) {
|
|
|
246
422
|
}
|
|
247
423
|
return { key, code: key };
|
|
248
424
|
}
|
|
249
|
-
var
|
|
425
|
+
var KEY_MAP2 = {
|
|
250
426
|
Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
|
|
251
427
|
Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
|
|
252
428
|
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
@@ -282,96 +458,57 @@ function sleep2(ms) {
|
|
|
282
458
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
283
459
|
}
|
|
284
460
|
|
|
285
|
-
// src/cdp-driver/selector-utils.ts
|
|
286
|
-
function queryJS(selector) {
|
|
287
|
-
if (selector.startsWith("xpath=")) {
|
|
288
|
-
const xpath = JSON.stringify(selector.slice(6));
|
|
289
|
-
return `document.evaluate(${xpath}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue`;
|
|
290
|
-
}
|
|
291
|
-
if (selector.startsWith("text=")) {
|
|
292
|
-
const raw = selector.slice(5);
|
|
293
|
-
const exact = raw.startsWith('"') && raw.endsWith('"');
|
|
294
|
-
const text = exact ? raw.slice(1, -1) : raw;
|
|
295
|
-
return `(() => {
|
|
296
|
-
const target = ${JSON.stringify(text)};
|
|
297
|
-
const exact = ${exact};
|
|
298
|
-
const els = [...document.querySelectorAll('*')].filter(e => {
|
|
299
|
-
if (e.children.length > 0) return false;
|
|
300
|
-
if (e.offsetParent === null) return false;
|
|
301
|
-
const t = (e.textContent || '').trim();
|
|
302
|
-
if (!t) return false;
|
|
303
|
-
return exact ? t === target : t.toLowerCase().includes(target.toLowerCase());
|
|
304
|
-
});
|
|
305
|
-
return els[0] || null;
|
|
306
|
-
})()`;
|
|
307
|
-
}
|
|
308
|
-
if (selector.startsWith("popup-text=")) {
|
|
309
|
-
const text = selector.slice("popup-text=".length);
|
|
310
|
-
return `(() => {
|
|
311
|
-
const target = ${JSON.stringify(text)};
|
|
312
|
-
const els = [...document.querySelectorAll('*')].filter(e => {
|
|
313
|
-
if (e.children.length > 0) return false;
|
|
314
|
-
if (e.offsetParent === null) return false;
|
|
315
|
-
if ((e.textContent || '').trim() !== target) return false;
|
|
316
|
-
return true;
|
|
317
|
-
});
|
|
318
|
-
return els[0] || null;
|
|
319
|
-
})()`;
|
|
320
|
-
}
|
|
321
|
-
return `document.querySelector(${JSON.stringify(selector)})`;
|
|
322
|
-
}
|
|
323
|
-
function queryAllJS(selector) {
|
|
324
|
-
if (selector.startsWith("xpath=")) {
|
|
325
|
-
const xpath = JSON.stringify(selector.slice(6));
|
|
326
|
-
return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
|
|
327
|
-
}
|
|
328
|
-
if (selector.startsWith("text=") || selector.startsWith("popup-text=")) {
|
|
329
|
-
return `(() => { const el = ${queryJS(selector)}; return el ? [el] : []; })()`;
|
|
330
|
-
}
|
|
331
|
-
return `document.querySelectorAll(${JSON.stringify(selector)})`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
461
|
// src/cdp-driver/actionability.ts
|
|
335
462
|
async function waitForActionable(page, selector, opts = {}) {
|
|
336
463
|
const timeout = opts.timeout ?? 3e4;
|
|
337
464
|
if (opts.force) {
|
|
338
|
-
|
|
339
|
-
|
|
465
|
+
const deadline2 = Date.now() + timeout;
|
|
466
|
+
let lastError;
|
|
467
|
+
while (Date.now() < deadline2) {
|
|
468
|
+
const rect = await page.evaluate(`
|
|
340
469
|
(function() {
|
|
341
470
|
const el = ${queryJS(selector)};
|
|
342
471
|
if (!el) return null;
|
|
343
472
|
const r = el.getBoundingClientRect();
|
|
344
|
-
|
|
473
|
+
let x = r.x, y = r.y;
|
|
474
|
+
let doc = el.ownerDocument;
|
|
475
|
+
while (doc !== document) {
|
|
476
|
+
let host = null;
|
|
477
|
+
const scan = (d) => {
|
|
478
|
+
let frames;
|
|
479
|
+
try { frames = d.querySelectorAll('iframe'); } catch (e) { return null; }
|
|
480
|
+
for (const f of frames) {
|
|
481
|
+
let inner = null;
|
|
482
|
+
try { inner = f.contentDocument; } catch (e) { continue; }
|
|
483
|
+
if (!inner) continue;
|
|
484
|
+
if (inner === doc) return f;
|
|
485
|
+
const rr = scan(inner);
|
|
486
|
+
if (rr) return rr;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
};
|
|
490
|
+
host = scan(document);
|
|
491
|
+
if (!host) break;
|
|
492
|
+
const hr = host.getBoundingClientRect();
|
|
493
|
+
x += hr.x; y += hr.y;
|
|
494
|
+
doc = host.ownerDocument;
|
|
495
|
+
}
|
|
496
|
+
return { x, y, width: r.width, height: r.height };
|
|
345
497
|
})()
|
|
346
|
-
`);
|
|
347
|
-
if (
|
|
348
|
-
|
|
498
|
+
`).catch(() => null);
|
|
499
|
+
if (rect && rect.width > 0 && rect.height > 0) return { nodeId: 0, rect };
|
|
500
|
+
lastError = `Element not visible (zero size): ${selector}`;
|
|
501
|
+
lastError = `Element not found: ${selector}`;
|
|
502
|
+
await page.waitForTimeout(200);
|
|
349
503
|
}
|
|
350
|
-
|
|
351
|
-
let lastError;
|
|
352
|
-
let nodeId = 0;
|
|
353
|
-
let rect = null;
|
|
354
|
-
while (Date.now() < deadline2) {
|
|
355
|
-
nodeId = await page.querySelector(selector);
|
|
356
|
-
if (!nodeId) {
|
|
357
|
-
lastError = `Element not found: ${selector}`;
|
|
358
|
-
await page.waitForTimeout(200);
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
rect = await page.getBoxModel(nodeId);
|
|
362
|
-
if (rect) break;
|
|
363
|
-
lastError = `Element has no box: ${selector}`;
|
|
364
|
-
await page.waitForTimeout(500);
|
|
365
|
-
}
|
|
366
|
-
if (!rect) throw new Error(lastError || `Element has no box: ${selector}`);
|
|
367
|
-
return { nodeId, rect };
|
|
504
|
+
throw new Error(lastError || `Element not found: ${selector}`);
|
|
368
505
|
}
|
|
369
506
|
const deadline = Date.now() + timeout;
|
|
370
507
|
while (Date.now() < deadline) {
|
|
371
508
|
const result = await checkActionable(page, selector);
|
|
372
509
|
if (result.ok && result.rect) {
|
|
373
|
-
const nodeId = await page.querySelector(selector);
|
|
374
|
-
|
|
510
|
+
const nodeId = await page.querySelector(selector).catch(() => 0) ?? 0;
|
|
511
|
+
return { nodeId, rect: result.rect };
|
|
375
512
|
}
|
|
376
513
|
await page.waitForTimeout(50);
|
|
377
514
|
}
|
|
@@ -404,12 +541,25 @@ async function checkActionable(page, selector) {
|
|
|
404
541
|
return { ok: false, reason: 'parent_disabled' };
|
|
405
542
|
}
|
|
406
543
|
|
|
407
|
-
// Check not covered by another element at center
|
|
544
|
+
// Check not covered by another element at center.
|
|
545
|
+
// elementFromPoint must run in the element's OWN document: for iframe-
|
|
546
|
+
// internal elements the main-document hit-test returns the <iframe>
|
|
547
|
+
// host itself, which falsely reports "covered" (rec-duel d01).
|
|
548
|
+
// For shadow-internal elements the hit-test retargets to the shadow
|
|
549
|
+
// HOST \u2014 walk the host chain before declaring coverage (rec-duel d04).
|
|
408
550
|
const cx = rect.x + rect.width / 2;
|
|
409
551
|
const cy = rect.y + rect.height / 2;
|
|
410
|
-
const topEl =
|
|
552
|
+
const topEl = el.ownerDocument.elementFromPoint(cx, cy);
|
|
411
553
|
if (topEl && topEl !== el && !el.contains(topEl) && !topEl.contains(el)) {
|
|
412
|
-
|
|
554
|
+
let hostChain = [];
|
|
555
|
+
let rootNode = el.getRootNode();
|
|
556
|
+
while (rootNode && rootNode.host) {
|
|
557
|
+
hostChain.push(rootNode.host);
|
|
558
|
+
rootNode = rootNode.host.getRootNode();
|
|
559
|
+
}
|
|
560
|
+
if (!hostChain.includes(topEl)) {
|
|
561
|
+
return { ok: false, reason: 'covered', rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
|
|
562
|
+
}
|
|
413
563
|
}
|
|
414
564
|
|
|
415
565
|
return {
|
|
@@ -454,21 +604,48 @@ var XBLocatorImpl = class _XBLocatorImpl {
|
|
|
454
604
|
const el = ${this._q(this.selector)};
|
|
455
605
|
if (!el) return null;
|
|
456
606
|
const rect = el.getBoundingClientRect();
|
|
457
|
-
|
|
607
|
+
let x = rect.x, y = rect.y;
|
|
608
|
+
let doc = el.ownerDocument;
|
|
609
|
+
while (doc !== document) {
|
|
610
|
+
let host = null;
|
|
611
|
+
const scan = (d) => {
|
|
612
|
+
let frames;
|
|
613
|
+
try { frames = d.querySelectorAll('iframe'); } catch (e) { return null; }
|
|
614
|
+
for (const f of frames) {
|
|
615
|
+
let inner = null;
|
|
616
|
+
try { inner = f.contentDocument; } catch (e) { continue; }
|
|
617
|
+
if (!inner) continue;
|
|
618
|
+
if (inner === doc) return f;
|
|
619
|
+
const r = scan(inner);
|
|
620
|
+
if (r) return r;
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
};
|
|
624
|
+
host = scan(document);
|
|
625
|
+
if (!host) break;
|
|
626
|
+
const hr = host.getBoundingClientRect();
|
|
627
|
+
x += hr.x; y += hr.y;
|
|
628
|
+
doc = host.ownerDocument;
|
|
629
|
+
}
|
|
630
|
+
return { x, y, width: rect.width, height: rect.height };
|
|
458
631
|
})()
|
|
459
632
|
`);
|
|
460
633
|
const finalRect = updatedRect ?? rect;
|
|
461
634
|
const cx = finalRect.x + finalRect.width / 2;
|
|
462
635
|
const cy = finalRect.y + finalRect.height / 2;
|
|
463
636
|
await this.page.mouse.click(cx, cy, {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
637
|
+
stealth: true,
|
|
638
|
+
elementWidth: finalRect.width,
|
|
639
|
+
elementHeight: finalRect.height,
|
|
640
|
+
...{ button: opts.button ?? "left", clickCount: opts.clickCount ?? 1, delay: opts.delay }
|
|
467
641
|
});
|
|
468
642
|
}
|
|
469
643
|
async fill(value, opts = {}) {
|
|
470
644
|
await waitForActionable(this.page, this.selector, opts);
|
|
471
645
|
await scrollIntoView(this.page, this.selector);
|
|
646
|
+
await this.click({ ...opts });
|
|
647
|
+
await this.page.keyboard.type(value, { stealth: true });
|
|
648
|
+
return;
|
|
472
649
|
await this.page.evaluate(`
|
|
473
650
|
(function() {
|
|
474
651
|
const el = ${this._q(this.selector)};
|
|
@@ -1178,6 +1355,8 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1178
1355
|
await this.conn.send("Page.enable", void 0, this.sessionId);
|
|
1179
1356
|
await this.conn.send("Runtime.enable", void 0, this.sessionId);
|
|
1180
1357
|
await this.conn.send("Network.enable", void 0, this.sessionId);
|
|
1358
|
+
await this.conn.send("DOM.enable", void 0, this.sessionId).catch(() => {
|
|
1359
|
+
});
|
|
1181
1360
|
this.setupPageEvents();
|
|
1182
1361
|
this.setupNetworkEvents();
|
|
1183
1362
|
this.setupConsoleEvents();
|
|
@@ -1190,6 +1369,9 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1190
1369
|
);
|
|
1191
1370
|
this._url = info.url;
|
|
1192
1371
|
this._title = info.title;
|
|
1372
|
+
if (info.url && info.url !== "about:blank" && info.url !== "") {
|
|
1373
|
+
this._loadState = { loadFired: true, domContentFired: true, networkIdle: true };
|
|
1374
|
+
}
|
|
1193
1375
|
} catch {
|
|
1194
1376
|
}
|
|
1195
1377
|
}
|
|
@@ -1202,6 +1384,16 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1202
1384
|
const waitUntil = opts.waitUntil ?? "load";
|
|
1203
1385
|
const timeout = opts.timeout ?? 3e4;
|
|
1204
1386
|
this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
|
|
1387
|
+
if (process.env.XBROWSER_STEALTH !== "off") {
|
|
1388
|
+
try {
|
|
1389
|
+
await this.conn.send(
|
|
1390
|
+
"Page.addScriptToEvaluateOnNewDocument",
|
|
1391
|
+
{ source: buildStealthInitScript() },
|
|
1392
|
+
this.sessionId
|
|
1393
|
+
);
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1205
1397
|
const result = await this.conn.send(
|
|
1206
1398
|
"Page.navigate",
|
|
1207
1399
|
{ url, referrer: opts.referer },
|
|
@@ -1409,6 +1601,91 @@ Last error: ${lastError.message}` : "";
|
|
|
1409
1601
|
}
|
|
1410
1602
|
return result.result?.value;
|
|
1411
1603
|
}
|
|
1604
|
+
/**
|
|
1605
|
+
* 在指定 iframe 上下文中执行表达式(攻防 D16 能力建设,2026-08-19)。
|
|
1606
|
+
*
|
|
1607
|
+
* 双路径:
|
|
1608
|
+
* 1. 同进程 iframe —— Runtime.enable 收集 executionContextCreated,
|
|
1609
|
+
* 找到目标 frameId 的 contextId,用 contextId 定向执行;
|
|
1610
|
+
* 2. 跨域 OOPIF(独立 target)—— Target.setAutoAttach(flatten) 监听
|
|
1611
|
+
* attachedToTarget 中 type==='iframe' 的会话,用其 sessionId 执行。
|
|
1612
|
+
*
|
|
1613
|
+
* 这绕过了页面同源策略(那是页面 JS 的约束,CDP 是调试通道)——
|
|
1614
|
+
* 支付窗/验证码/第三方嵌入内容的读写都靠它。
|
|
1615
|
+
*/
|
|
1616
|
+
async evaluateInFrame(urlIncludes, expression) {
|
|
1617
|
+
if (this._closed) throw new Error("Page is closed");
|
|
1618
|
+
const evalIn = async (sessionId, contextId) => {
|
|
1619
|
+
const params = { expression, returnByValue: true, awaitPromise: true };
|
|
1620
|
+
if (contextId !== void 0) params.contextId = contextId;
|
|
1621
|
+
const result = await this.conn.send("Runtime.evaluate", params, sessionId);
|
|
1622
|
+
if (result.exceptionDetails) {
|
|
1623
|
+
const detail = result.exceptionDetails.exception?.description ?? result.exceptionDetails.exception?.value ?? result.exceptionDetails.text;
|
|
1624
|
+
throw new Error(`[frame ${urlIncludes}] ${detail}`);
|
|
1625
|
+
}
|
|
1626
|
+
return result.result?.value;
|
|
1627
|
+
};
|
|
1628
|
+
try {
|
|
1629
|
+
const tg = await this.conn.send("Target.getTargets", void 0);
|
|
1630
|
+
const hit2 = (tg.targetInfos || []).find((t) => t.type === "iframe" && (t.url || "").includes(urlIncludes));
|
|
1631
|
+
if (hit2) {
|
|
1632
|
+
const att = await this.conn.send("Target.attachToTarget", { targetId: hit2.targetId, flatten: true });
|
|
1633
|
+
return evalIn(att.sessionId);
|
|
1634
|
+
}
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
const tree = await this.conn.send("Page.getFrameTree", void 0, this.sessionId);
|
|
1638
|
+
const all = [];
|
|
1639
|
+
const walk = (node) => {
|
|
1640
|
+
all.push({ id: node.frame.id, url: node.frame.url });
|
|
1641
|
+
for (const child of node.childFrames || []) walk(child);
|
|
1642
|
+
};
|
|
1643
|
+
walk(tree.frameTree);
|
|
1644
|
+
const mainId = tree.frameTree?.frame?.id;
|
|
1645
|
+
const target = all.find((f) => f.id !== mainId && f.url.includes(urlIncludes));
|
|
1646
|
+
if (target) {
|
|
1647
|
+
const contexts = [];
|
|
1648
|
+
const onCtx = (raw) => {
|
|
1649
|
+
const c = raw?.context;
|
|
1650
|
+
if (c?.id && c?.auxData?.frameId) contexts.push({ id: c.id, frameId: c.auxData.frameId });
|
|
1651
|
+
};
|
|
1652
|
+
this.conn.on("Runtime.executionContextCreated", onCtx);
|
|
1653
|
+
try {
|
|
1654
|
+
await this.conn.send("Runtime.enable", void 0, this.sessionId).catch(() => {
|
|
1655
|
+
});
|
|
1656
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
1657
|
+
} finally {
|
|
1658
|
+
this.conn.off("Runtime.executionContextCreated", onCtx);
|
|
1659
|
+
}
|
|
1660
|
+
const ctx = contexts.find((c) => c.frameId === target.id);
|
|
1661
|
+
if (ctx) return evalIn(this.sessionId, ctx.id);
|
|
1662
|
+
}
|
|
1663
|
+
const attached = [];
|
|
1664
|
+
const onAttach = (raw) => {
|
|
1665
|
+
const ev = raw;
|
|
1666
|
+
if (ev?.sessionId && ev.targetInfo?.type === "iframe") {
|
|
1667
|
+
attached.push({ sessionId: ev.sessionId, url: ev.targetInfo.url || "" });
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
this.conn.on("Target.attachedToTarget", onAttach);
|
|
1671
|
+
try {
|
|
1672
|
+
await this.conn.send("Target.setAutoAttach", {
|
|
1673
|
+
autoAttach: true,
|
|
1674
|
+
waitForDebuggerOnStart: false,
|
|
1675
|
+
flatten: true
|
|
1676
|
+
}, this.sessionId);
|
|
1677
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
1678
|
+
} finally {
|
|
1679
|
+
this.conn.off("Target.attachedToTarget", onAttach);
|
|
1680
|
+
this.conn.send("Target.setAutoAttach", { autoAttach: false, waitForDebuggerOnStart: false, flatten: true }, this.sessionId).catch(() => {
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
const hit = attached.find((a) => a.url.includes(urlIncludes));
|
|
1684
|
+
if (!hit) {
|
|
1685
|
+
throw new Error(`frame not found for "${urlIncludes}"\uFF08OOPIF target\u3001frame \u6811\u3001auto-attach \u4E09\u8DEF\u5747\u672A\u547D\u4E2D\uFF1B\u53EF\u80FD\u4ECD\u5728\u52A0\u8F7D\uFF09`);
|
|
1686
|
+
}
|
|
1687
|
+
return evalIn(hit.sessionId);
|
|
1688
|
+
}
|
|
1412
1689
|
/** evaluateHandle — evaluates fn and returns a handle for element bounding box */
|
|
1413
1690
|
async evaluateHandle(fn, ...args) {
|
|
1414
1691
|
let expression;
|
|
@@ -1779,16 +2056,21 @@ Last error: ${lastError.message}` : "";
|
|
|
1779
2056
|
}
|
|
1780
2057
|
return 1;
|
|
1781
2058
|
}
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
{ depth: 0 },
|
|
1785
|
-
|
|
2059
|
+
const withTimeout = (p, ms) => Promise.race([p, new Promise((r) => setTimeout(() => r(null), ms))]);
|
|
2060
|
+
const doc = await withTimeout(
|
|
2061
|
+
this.conn.send("DOM.getDocument", { depth: 0 }, this.sessionId),
|
|
2062
|
+
8e3
|
|
1786
2063
|
);
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2064
|
+
if (!doc) return 0;
|
|
2065
|
+
const result = await withTimeout(
|
|
2066
|
+
this.conn.send(
|
|
2067
|
+
"DOM.querySelector",
|
|
2068
|
+
{ nodeId: doc.root.nodeId, selector },
|
|
2069
|
+
this.sessionId
|
|
2070
|
+
),
|
|
2071
|
+
8e3
|
|
1791
2072
|
);
|
|
2073
|
+
if (!result) return 0;
|
|
1792
2074
|
return result.nodeId;
|
|
1793
2075
|
}
|
|
1794
2076
|
/** Query all matching elements, returns array of CDP nodeIds */
|