@xbrowser/cli 1.21.0 → 1.23.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.
Files changed (43) hide show
  1. package/README.md +21 -3
  2. package/dist/{browser-CNBSH53B.js → browser-BVRFSUXU.js} +3 -2
  3. package/dist/{browser-C2VEOJC2.js → browser-DSYUAE7H.js} +5 -4
  4. package/dist/{browser-VZOOFLAH.js → browser-Q67BJTI2.js} +4 -3
  5. package/dist/{cdp-driver-KFX4A6WR.js → cdp-driver-DC7EIUG5.js} +4 -3
  6. package/dist/{cdp-driver-7Z6ZAT5E.js → cdp-driver-T7D6H2RW.js} +335 -546
  7. package/dist/{cdp-driver-26LGCHXV.js → cdp-driver-WWO2UZDR.js} +3 -2
  8. package/dist/{chunk-Z5RTK4GW.js → chunk-2Q3GQRUP.js} +2 -2
  9. package/dist/{chunk-CLYZPSDR.js → chunk-4OU37CXP.js} +354 -576
  10. package/dist/{chunk-HWWG2ISJ.js → chunk-7OP2ISEY.js} +353 -575
  11. package/dist/{chunk-PIWNMC36.js → chunk-A3MORKYR.js} +1 -1
  12. package/dist/{chunk-3FWLW7FS.js → chunk-A7NEA4PA.js} +39 -1
  13. package/dist/{chunk-OMU63E6J.js → chunk-FBQZ3PVD.js} +5 -8
  14. package/dist/chunk-GYB5BOSP.js +602 -0
  15. package/dist/{chunk-ESJYANQO.js → chunk-IFKJO2UR.js} +360 -582
  16. package/dist/{chunk-NODRQGOK.js → chunk-IR6C24P7.js} +97 -0
  17. package/dist/{chunk-TEXCXIBW.js → chunk-LPW6QS4M.js} +5 -8
  18. package/dist/{chunk-3KZ34AUC.js → chunk-MYH6FIBZ.js} +2 -1
  19. package/dist/{chunk-AVSTNODR.js → chunk-T4WTKBWI.js} +2 -2
  20. package/dist/{chunk-HBHOKZPN.js → chunk-V6KGBU5J.js} +97 -0
  21. package/dist/{chunk-IWVG4XYZ.js → chunk-VCVDILH6.js} +2 -1
  22. package/dist/{chunk-NKW4A74J.js → chunk-XTUAUFL3.js} +5 -8
  23. package/dist/cli.js +497 -252
  24. package/dist/daemon-main.js +25 -19
  25. package/dist/{human-interaction-Y5IDH6LD.js → human-interaction-57VYIR56.js} +1 -1
  26. package/dist/{human-interaction-ZUTR5AC2.js → human-interaction-67K43B4O.js} +1 -1
  27. package/dist/{human-interaction-4YR2N6R6.js → human-interaction-MXST5ZQM.js} +1 -1
  28. package/dist/index.d.ts +52 -4
  29. package/dist/index.js +500 -256
  30. package/dist/keep-awake-M4KJS4B4.js +29 -0
  31. package/dist/{launcher-44STYRJC.js → launcher-IK2FKW6T.js} +1 -1
  32. package/dist/{launcher-EXJHHAUL.js → launcher-LUOMMGZC.js} +1 -1
  33. package/dist/{plugin-singleton-FCNDN3FX.js → plugin-singleton-GJO7EZXB.js} +1 -1
  34. package/dist/{recovery-NXC35EQN.js → recovery-6RI2FE3E.js} +1 -1
  35. package/dist/{recovery-MDWAVXE4.js → recovery-LGY25LIM.js} +1 -1
  36. package/dist/{recovery-L5GLDX4O.js → recovery-LMJW537X.js} +1 -1
  37. package/dist/{session-recorder-SLDBENVF.js → session-recorder-563ISSJK.js} +1 -1
  38. package/dist/{session-recorder-3BEVWHOK.js → session-recorder-GEZVQBKW.js} +1 -1
  39. package/dist/session-replayer-N665T3G5.js +932 -0
  40. package/dist/stealth-2AA2FSNN.js +29 -0
  41. package/dist/stealth-JOXGV34D.js +29 -0
  42. package/package.json +11 -7
  43. package/dist/session-replayer-EVD4HZBB.js +0 -413
@@ -1,11 +1,19 @@
1
1
  import {
2
2
  queryAllJS,
3
3
  queryJS
4
- } from "./chunk-3FWLW7FS.js";
4
+ } from "./chunk-A7NEA4PA.js";
5
+ import {
6
+ DEFAULT_STEALTH_CONFIG,
7
+ bezierTrajectory,
8
+ buildStealthInitScript,
9
+ landingOffset,
10
+ rand,
11
+ typingDelay
12
+ } from "./chunk-GYB5BOSP.js";
5
13
  import {
6
14
  connectToCDP,
7
15
  launchChrome
8
- } from "./chunk-3KZ34AUC.js";
16
+ } from "./chunk-MYH6FIBZ.js";
9
17
  import {
10
18
  errMsg
11
19
  } from "./chunk-GDKLH7ZY.js";
@@ -15,6 +23,9 @@ import {
15
23
 
16
24
  // src/cdp-driver/browser.ts
17
25
  import { EventEmitter as EventEmitter3 } from "events";
26
+ import { mkdirSync } from "fs";
27
+ import { join } from "path";
28
+ import { tmpdir } from "os";
18
29
 
19
30
  // src/cdp-driver/context.ts
20
31
  import { EventEmitter as EventEmitter2 } from "events";
@@ -22,541 +33,6 @@ import { EventEmitter as EventEmitter2 } from "events";
22
33
  // src/cdp-driver/page.ts
23
34
  import { EventEmitter } from "events";
24
35
 
25
- // src/cdp-driver/stealth.ts
26
- var DEFAULT_STEALTH_CONFIG = {
27
- // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
28
- uaChProfile: {
29
- brands: [
30
- { brand: "Not=A?Brand", version: "99" },
31
- { brand: "Google Chrome", version: "151" },
32
- { brand: "Chromium", version: "151" }
33
- ],
34
- platform: "macOS",
35
- platformVersion: "26.2.0",
36
- architecture: "arm",
37
- bitness: "64",
38
- model: "",
39
- uaFullVersion: "151.0.7922.175",
40
- fullVersionList: [
41
- { brand: "Not=A?Brand", version: "99.0.0.0" },
42
- { brand: "Google Chrome", version: "151.0.7922.175" },
43
- { brand: "Chromium", version: "151.0.7922.175" }
44
- ]
45
- },
46
- bezierCurvature: [0.35, 0.6],
47
- noiseAmplitude: 5.5,
48
- overshootRange: [6, 14],
49
- aimPause: [80, 280],
50
- pressDuration: [60, 140],
51
- releaseDrift: [0.8, 2.5],
52
- landingOffsetSmall: [0.3, 2.5],
53
- landingOffsetLarge: [1.5, 7],
54
- smallElementThreshold: 30,
55
- typingRhythm: {
56
- fastProb: 0.22,
57
- fastRange: [25, 60],
58
- normalRange: [50, 350],
59
- pauseProb: 0.18,
60
- pauseRange: [400, 1200]
61
- },
62
- keyPressDuration: [50, 110],
63
- typoProbability: 0.06,
64
- wheelPeak: 180,
65
- wheelDecayRate: 0.4
66
- };
67
- function rand(min, max) {
68
- return min + Math.random() * (max - min);
69
- }
70
- function sleep(ms) {
71
- return new Promise((resolve) => setTimeout(resolve, ms));
72
- }
73
- function cosineEase(t) {
74
- return 0.5 - 0.5 * Math.cos(Math.PI * t);
75
- }
76
- function bezierTrajectory(x0, y0, x1, y1, config = DEFAULT_STEALTH_CONFIG) {
77
- const dist = Math.hypot(x1 - x0, y1 - y0);
78
- const n = Math.max(10, Math.min(28, Math.round(dist / 15)));
79
- const shortMove = dist < 120;
80
- const curvature = shortMove ? rand(2, 6) : Math.max(dist * rand(...config.bezierCurvature), rand(18, 35));
81
- const dir = Math.random() < 0.5 ? 1 : -1;
82
- const d = dist || 1;
83
- const dx = x1 - x0, dy = y1 - y0;
84
- const c1x = x0 + dx * 0.3 - dy / d * curvature * 0.5 * dir;
85
- const c1y = y0 + dy * 0.3 + dx / d * curvature * 0.5 * dir;
86
- const c2x = x0 + dx * 0.7 - dy / d * curvature * 0.8 * dir;
87
- const c2y = y0 + dy * 0.7 + dx / d * curvature * 0.8 * dir;
88
- const points = [];
89
- for (let i = 1; i <= n; i++) {
90
- const t = cosineEase(i / n);
91
- const mt = 1 - t;
92
- let px = mt ** 3 * x0 + 3 * mt ** 2 * t * c1x + 3 * mt * t ** 2 * c2x + t ** 3 * x1;
93
- let py = mt ** 3 * y0 + 3 * mt ** 2 * t * c1y + 3 * mt * t ** 2 * c2y + t ** 3 * y1;
94
- const amp = shortMove ? Math.min(2, config.noiseAmplitude) : config.noiseAmplitude;
95
- px += rand(-amp, amp);
96
- py += rand(-amp, amp);
97
- points.push({ x: px, y: py, delay: rand(9, 16) });
98
- }
99
- if (!shortMove) {
100
- const over = rand(...config.overshootRange);
101
- const ox = x1 + dx / d * over + rand(-2, 2);
102
- const oy = y1 + dy / d * over + rand(-2, 2);
103
- points.push({ x: ox, y: oy, delay: rand(14, 30) });
104
- points.push({
105
- x: x1 + dx / d * over * 0.4,
106
- y: y1 + dy / d * over * 0.4,
107
- delay: rand(14, 30)
108
- });
109
- }
110
- points.push({ x: x1 + rand(-1, 1), y: y1 + rand(-1, 1), delay: rand(14, 30) });
111
- return points;
112
- }
113
- function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
114
- const isSmall = Math.min(width, height) < config.smallElementThreshold;
115
- const range = isSmall ? config.landingOffsetSmall : config.landingOffsetLarge;
116
- const dx = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
117
- const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
118
- return { dx, dy };
119
- }
120
- function typingDelay(config = DEFAULT_STEALTH_CONFIG) {
121
- const roll = Math.random();
122
- const r = config.typingRhythm;
123
- if (roll < r.pauseProb) return rand(...r.pauseRange);
124
- if (roll < r.pauseProb + r.fastProb) return rand(...r.fastRange);
125
- return rand(...r.normalRange);
126
- }
127
- function wheelDelta(step, config = DEFAULT_STEALTH_CONFIG) {
128
- return Math.round(
129
- config.wheelPeak * Math.exp(-step * config.wheelDecayRate) * rand(0.85, 1.15)
130
- );
131
- }
132
- var KEY_MAP = {};
133
- for (let i = 97; i <= 122; i++) {
134
- const ch = String.fromCharCode(i);
135
- KEY_MAP[ch] = { key: ch, code: "Key" + ch.toUpperCase(), vk: i - 32 };
136
- }
137
- for (let i = 65; i <= 90; i++) {
138
- const ch = String.fromCharCode(i);
139
- KEY_MAP[ch] = { key: ch, code: "Key" + ch, vk: i, shift: true };
140
- }
141
- for (let i = 48; i <= 57; i++) {
142
- const ch = String.fromCharCode(i);
143
- KEY_MAP[ch] = { key: ch, code: "Digit" + ch, vk: i };
144
- }
145
- Object.assign(KEY_MAP, {
146
- " ": { key: " ", code: "Space", vk: 32 },
147
- ".": { key: ".", code: "Period", vk: 190 },
148
- "-": { key: "-", code: "Minus", vk: 189 },
149
- "@": { key: "@", code: "Digit2", vk: 50, shift: true },
150
- "_": { key: "_", code: "Minus", vk: 189, shift: true }
151
- });
152
- function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
153
- const prof = config.uaChProfile;
154
- const profJson = prof ? JSON.stringify(prof) : "null";
155
- return [
156
- "(function(){",
157
- ' window.__xbStealthVer="57";',
158
- " window.__xbUaChProf=" + profJson + ";",
159
- // 1. AEL event proxy
160
- " var o=EventTarget.prototype.addEventListener;",
161
- " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
162
- " var _ael=function(t,f){",
163
- " var op=arguments[2];",
164
- ' if(typeof f!=="function")return o.call(this,t,f,op);',
165
- " var w=function(e){",
166
- " if(!e)return f.call(this,e);",
167
- " return f.call(this,new Proxy(e,{get:function(k,p){",
168
- ' if(p==="sourceCapabilities")return fc;',
169
- // S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
170
- // 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
171
- // 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
172
- // + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
173
- ' if(p==="isTrusted"){',
174
- ' var orig=Reflect.get(k,"isTrusted");',
175
- " if(orig===true)return true;",
176
- ' if(k.type==="paste")return true;',
177
- " return orig;",
178
- " }",
179
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
180
- " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
181
- " return k[p]+_f;",
182
- " }",
183
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
184
- " }}));",
185
- " };",
186
- " return o.call(this,t,w,op);",
187
- " };",
188
- " EventTarget.prototype.addEventListener=_ael;",
189
- // window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
190
- // (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
191
- " try{",
192
- ' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
193
- " if(_weDesc&&_weDesc.get){",
194
- " var _origWeGet=_weDesc.get;",
195
- ' Object.defineProperty(Window.prototype,"event",{',
196
- " configurable:true,",
197
- " get:function(){",
198
- " var ev=_origWeGet.call(this);",
199
- " if(!ev)return ev;",
200
- " return new Proxy(ev,{get:function(k,p){",
201
- ' if(p==="isTrusted"){',
202
- ' var orig=Reflect.get(k,"isTrusted");',
203
- " if(orig===true)return true;",
204
- ' if(k.type==="paste")return true;',
205
- " return orig;",
206
- " }",
207
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
208
- " }});",
209
- " }",
210
- " });",
211
- " }",
212
- " }catch(e){}",
213
- // 2. Screen override (prototype-level, not instance-level)
214
- " var _gw=function(){return 1728};",
215
- " var _gh=function(){return 1117};",
216
- " var _gah=function(){return 1092};",
217
- ' Object.defineProperty(Screen.prototype,"width",{get:_gw,configurable:true});',
218
- ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
219
- ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
220
- ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
221
- // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
222
- // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
223
- // 原型+实例双层覆写(S172 模式)。
224
- " try{",
225
- ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
226
- ' var fixBrands=function(list){return (list||[]).map(function(b){return (b.brand&&b.brand.indexOf("Headless")!==-1)?Object.assign({},b,{brand:b.brand.replace("HeadlessChrome","Chrome")}):b;});};',
227
- ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
228
- " var wrapUAD=function(uad){",
229
- " if(!uad||uad.__xbUA)return uad;",
230
- " try{",
231
- ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
232
- " var _ghev=uad.getHighEntropyValues.bind(uad);",
233
- " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
234
- ' var o={};for(var k in v){o[k]=(typeof v[k]==="string")?fixUA(v[k]):(Array.isArray(v[k])?fixBrands(v[k]):v[k]);}',
235
- " return o;});};",
236
- " uad.__xbUA=true;",
237
- " }catch(e){}",
238
- " return uad;",
239
- " };",
240
- ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
241
- " if(_nua&&_nua.get){",
242
- " var _nuag=function(){return fixUA(_nua.get.call(this));};",
243
- ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
244
- ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
245
- " }",
246
- ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
247
- " if(_nuad&&_nuad.get){",
248
- " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
249
- ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
250
- ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
251
- " }",
252
- // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
253
- // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
254
- " if(!navigator.userAgentData && window.__xbUaChProf){",
255
- " var prof = window.__xbUaChProf;",
256
- " var uad={",
257
- ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
258
- " mobile:false,",
259
- " platform:prof.platform,",
260
- " getHighEntropyValues:function(hints){",
261
- " return Promise.resolve().then(function(){",
262
- " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
263
- " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
264
- " uaFullVersion:prof.uaFullVersion,",
265
- " fullVersionList:prof.fullVersionList};",
266
- " var out={};",
267
- " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
268
- " return out;",
269
- " });",
270
- " },",
271
- " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
272
- " };",
273
- ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
274
- " }",
275
- " }catch(e){}",
276
- // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
277
- // —— 真实浏览器失焦/后台时 hasFocus=false
278
- // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
279
- ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
280
- // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
281
- // Headless software raster differs subtly from Chrome GPU raster —
282
- // toDataURL hashes fingerprint the rasterizer. Inject a stable
283
- // (per-page-load) subpixel shift into fillText so hashes look like a
284
- // distinct-but-consistent real GPU, and hide the HEADLESS tell in
285
- // WebGL renderer strings.
286
- " var _seed=Math.floor(Math.random()*2147483647);",
287
- " var _prng=function(){_seed=(_seed*48271)%2147483647;return _seed/2147483647;};",
288
- " var _dx=(_prng()*0.4-0.2).toFixed(3)*1, _dy=(_prng()*0.4-0.2).toFixed(3)*1;",
289
- " var _fillText=CanvasRenderingContext2D.prototype.fillText;",
290
- " CanvasRenderingContext2D.prototype.fillText=function(t,x,y,m){",
291
- " return _fillText.call(this,t,x+_dx,y+_dy,m);",
292
- " };",
293
- " var _toDataURL=HTMLCanvasElement.prototype.toDataURL;",
294
- " HTMLCanvasElement.prototype.toDataURL=function(){",
295
- ' var ctx=this.getContext("2d");',
296
- " if(ctx){var d=ctx.getImageData(0,0,Math.min(this.width,2),Math.min(this.height,2));",
297
- " for(var i=0;i<d.data.length;i+=4){if(d.data[i+3]>0){d.data[i]^=1;break;}}",
298
- " ctx.putImageData(d,0,0);}",
299
- " return _toDataURL.apply(this,arguments);",
300
- " };",
301
- " try{",
302
- " var _gl=HTMLCanvasElement.prototype.getContext;",
303
- " HTMLCanvasElement.prototype.getContext=function(t,o){",
304
- " var c=_gl.call(this,t,o);",
305
- ' if(c&&(t==="webgl"||t==="experimental-webgl")&&c.getParameter){',
306
- " var _gp=c.getParameter.bind(c);",
307
- " c.getParameter=function(p){",
308
- " var v=_gp(p);",
309
- ' if(typeof v==="string"&&/SwiftShader|Software|Rasterizer|Headless/i.test(v))',
310
- ' return "ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Max, Unspecified Version)";',
311
- " return v;};}",
312
- " return c;};",
313
- " }catch(e){}",
314
- // 5. AudioContext fingerprint: per-load stable micro-noise on channel
315
- // data (d21). DSP sum differences between headless software audio and
316
- // real hardware audio are a classic fingerprint — add ±1e-7 level
317
- // noise (inaudible, changes the sum hash).
318
- " try{",
319
- " var _gcd=AudioBuffer.prototype.getChannelData;",
320
- " AudioBuffer.prototype.getChannelData=function(ch){",
321
- " var d=_gcd.call(this,ch);",
322
- ' var key="__xb_audio_"+ch;',
323
- " if(!this[key]){",
324
- " this[key]=true;",
325
- " for(var i=0;i<d.length;i+=997){d[i]=d[i]+(_prng()-0.5)*2e-7;}",
326
- " }",
327
- " return d;",
328
- " };",
329
- " var _gffd=AnalyserNode.prototype.getFloatFrequencyData;",
330
- " AnalyserNode.prototype.getFloatFrequencyData=function(arr){",
331
- " _gffd.call(this,arr);",
332
- " for(var i=0;i<arr.length;i+=31){arr[i]=arr[i]+(_prng()-0.5)*0.01;}",
333
- " };",
334
- " var _gfbd=AnalyserNode.prototype.getByteFrequencyData;",
335
- " AnalyserNode.prototype.getByteFrequencyData=function(arr){",
336
- " _gfbd.call(this,arr);",
337
- " for(var i=0;i<arr.length;i+=31){arr[i]=(arr[i]+((_prng()*3)|0))&255;}",
338
- " };",
339
- " }catch(e){}",
340
- // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
341
- " try{",
342
- ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
343
- " if(_tmw&&_tmw.get){",
344
- " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
345
- ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
346
- " }",
347
- " }catch(e){}",
348
- " try{",
349
- ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
350
- // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
351
- " var _sv=SpeechSynthesis.prototype.getVoices;",
352
- " SpeechSynthesis.prototype.getVoices=function(){",
353
- " var real=_sv.call(this);",
354
- " if(real&&real.length>50)return real;",
355
- // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
356
- // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
357
- ' return _fakeVoices.map(function(n,i){return {name:n,lang:i<2?"en-US":i<6?"en-GB":"zh-TW",localService:true,default:i===0,voiceURI:n};});',
358
- " };",
359
- // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
360
- // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
361
- " try{",
362
- " var _piv=function(){",
363
- ' var frs=document.querySelectorAll("iframe");',
364
- " for(var i=0;i<frs.length;i++){try{",
365
- " var cw=frs[i].contentWindow;",
366
- " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
367
- " cw.speechSynthesis.__xbP=true;",
368
- " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
369
- " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
370
- ' return _fakeVoices.map(function(n,j){return{name:n,lang:j<2?"en-US":j<6?"en-GB":"zh-TW",localService:true,default:j===0,voiceURI:n};});};',
371
- " }catch(e2){}}",
372
- " };",
373
- " setInterval(_piv,2000);",
374
- " var _mo=new MutationObserver(function(muts){",
375
- " for(var k=0;k<muts.length;k++){var ns=muts[k].addedNodes;",
376
- ' for(var q=0;q<ns.length;q++){if(ns[q].tagName==="IFRAME")setTimeout(_piv,20);}}',
377
- " });",
378
- " _mo.observe(document.documentElement,{childList:true,subtree:true});",
379
- " }catch(e){}",
380
- " }catch(e){}",
381
- " try{",
382
- " var _gb=navigator.getBattery.bind(navigator);",
383
- " navigator.getBattery=function(){",
384
- " return _gb().then(function(b){",
385
- ' Object.defineProperty(b,"charging",{get:function(){return true;},configurable:true});',
386
- " return b;});",
387
- " };",
388
- " }catch(e){}",
389
- // 3c. WebRTC local IP leak guard (d23): headless STUN candidates can
390
- // expose host IPs; mDNS-only candidates are the modern Chrome default.
391
- " try{",
392
- " var _oc=RTCPeerConnection.prototype.createOffer;",
393
- " RTCPeerConnection.prototype.createOffer=function(){",
394
- " var p=_oc.apply(this,arguments);",
395
- " var self=this;",
396
- " return p.then(function(offer){",
397
- ' offer.sdp=offer.sdp.split(String.fromCharCode(10)).filter(function(l){return l.indexOf("typ host")<0}).join(String.fromCharCode(10));',
398
- " return offer;});",
399
- " };",
400
- " }catch(e){}",
401
- // 3d-2. performance.now precision clamp (d34): full-precision timestamps
402
- // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
403
- " try{",
404
- " var _pn=Performance.prototype.now;",
405
- " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
406
- " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
407
- ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
408
- " if(_pto&&_pto.get){",
409
- " // S173: timeOrigin \u771F\u4F2A\u88C5\u2014\u2014\u5360\u4F4D\u5C42\uFF08\u8FD4\u56DE\u771F\u503C\uFF09\u5347\u7EA7\u4E3A\u4F1A\u8BDD\u7EA7\u968F\u673A\u504F\u79FB\u3002",
410
- " // \u9632 cross-page \u5BFC\u822A\u65F6\u523B\u5173\u8054\uFF1A\u4E0D\u540C\u7AD9\u70B9\u7528 timeOrigin \u7CBE\u786E\u5BF9\u9F50\u7528\u6237\u884C\u4E3A\u6863\u6848\u3002",
411
- " // \u504F\u79FB\u7531\u672C\u9875 _seed \u6D3E\u751F\uFF08\u9875\u5185\u81EA\u6D3D\uFF0Cnow()+\u504F\u79FB\u4E00\u81F4\uFF09\uFF0C\u8DE8\u9875\u968F\u673A\uFF08\u4E0D\u53EF\u5173\u8054\uFF09\u3002",
412
- " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
413
- " var _tog=function(){return _pto.get.call(performance)+_toff;};",
414
- ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
415
- " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
416
- ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
417
- ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
418
- " }",
419
- " }catch(e){}",
420
- // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
421
- // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
422
- // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
423
- // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
424
- // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
425
- // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
426
- " try{",
427
- " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
428
- " var _gce=PointerEvent.prototype.getCoalescedEvents;",
429
- " var _lm=null;",
430
- " var _gceH=function(){",
431
- " var list=_gce.call(this);",
432
- ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
433
- " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
434
- " var out=null;",
435
- " if((!list||list.length<2)&&_lm){",
436
- " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
437
- // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
438
- // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
439
- // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
440
- " var expN=Math.floor(dt/8)-1;",
441
- // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
442
- // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
443
- " if(expN>0&&dt>=12&&Math.random()>0.15){",
444
- " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
445
- " var span=Math.min(dt,n*8.3);",
446
- " var frac=Math.min(1,span/Math.max(dt,1));",
447
- " out=[];",
448
- " for(var i=1;i<=n;i++){",
449
- // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
450
- " var k=n+1-i;",
451
- ' var ev=new PointerEvent("pointermove",{',
452
- ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
453
- " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
454
- " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
455
- " screenX:this.screenX,screenY:this.screenY,",
456
- " buttons:this.buttons,button:this.button,",
457
- " bubbles:true,cancelable:true,composed:true,",
458
- " width:this.width,height:this.height,pressure:this.pressure,",
459
- " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
460
- " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
461
- // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
462
- // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
463
- // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
464
- // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
465
- // 合成事件同时间戳、群内间隔塌到 ~2ms)。
466
- " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
467
- ' if(p==="isTrusted")return true;',
468
- ' if(p==="timeStamp")return t2;',
469
- ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
470
- " }});})(ev,tsV));",
471
- " }",
472
- " out.push(this);",
473
- " }",
474
- " }",
475
- " _lm=cur;",
476
- " return out||list;",
477
- " };",
478
- " PointerEvent.prototype.getCoalescedEvents=_gceH;",
479
- // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
480
- // "getCoalescedEvents")——toString 白名单管不到 name 属性。
481
- ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
482
- " }",
483
- " }catch(e){}",
484
- // 3d. Chrome object depth (d24): automation fakes usually only set
485
- // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
486
- " try{",
487
- " if(window.chrome){",
488
- ' if(!window.chrome.app||!window.chrome.app.run)window.chrome.app={run:function(){},load:function(){},getDetails:function(){return null},InstallState:{DISABLED:"disabled",INSTALLED:"installed",NOT_INSTALLED:"not_installed"},RunningState:{CANNOT_RUN:"cannot_run",READY_TO_RUN:"ready_to_run",RUNNING:"running"}};',
489
- ' if(!window.chrome.runtime)window.chrome.runtime={OnInstalledReason:{CHROME_UPDATE:"chrome_update",INSTALL:"install",UPDATE:"update"},PlatformOs:{ANDROID:"android",CROS:"cros",LINUX:"linux",MAC:"mac",OPENBSD:"openbsd",WIN:"win"},connect:function(){},sendMessage:function(){},id:undefined};',
490
- " if(!window.chrome.csi)window.chrome.csi=function(){return{};}",
491
- " if(!window.chrome.loadTimes)window.chrome.loadTimes=function(){return{};}",
492
- " }",
493
- " }catch(e){}",
494
- // 3e. Font availability patch (d25): headless Chromium misses some system
495
- // fonts (e.g. Menlo on macOS) that real Chrome has. Register a FontFace
496
- // aliasing the missing font to a local() equivalent so offsetWidth-based
497
- // font probing sees the same availability as the faked environment.
498
- " try{",
499
- ' var _fontAliases=[["Menlo","Courier New"],["SF Mono","Menlo"],["Segoe UI","Helvetica"]];',
500
- " _fontAliases.forEach(function(pa){",
501
- ' try{var ff=new FontFace(pa[0],"local("+JSON.stringify(pa[1])+")");',
502
- " document.fonts.add(ff);ff.load();}catch(e2){}",
503
- " });",
504
- " }catch(e){}",
505
- // 3g. Notification.requestPermission 延迟(d62):headless 无原生横幅,
506
- // request 瞬回 denied —— 真机 request 会 pending 在横幅上数秒到分钟。
507
- // 延迟 2-6s 再 resolve,模拟横幅期(用户"看了下然后拒绝")。
508
- " try{",
509
- " if(window.Notification&&Notification.requestPermission){",
510
- " var _nrp=Notification.requestPermission.bind(Notification);",
511
- " Notification.requestPermission=function(cb){",
512
- " return new Promise(function(res){",
513
- " setTimeout(function(){_nrp().then(function(r){if(cb)try{cb(r)}catch(e){}res(r);});},2000+Math.random()*4000);",
514
- " });",
515
- " };",
516
- " }",
517
- " }catch(e){}",
518
- // 3. toString disguise (name-list based)
519
- " var _ts=Function.prototype.toString;",
520
- " var _hf=document.hasFocus;",
521
- " Function.prototype.toString=function(){",
522
- ' if(this===_ael)return"function addEventListener(type, callback) { [native code] }";',
523
- ' if(this===_hf)return"function hasFocus() { [native code] }";',
524
- ' if(this===_gw)return"function get width() { [native code] }";',
525
- ' if(this===_gh)return"function get height() { [native code] }";',
526
- ' if(this===_gah)return"function get availHeight() { [native code] }";',
527
- ' if(this===CanvasRenderingContext2D.prototype.fillText)return"function fillText() { [native code] }";',
528
- ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
529
- ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
530
- ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
531
- ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
532
- ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
533
- ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
534
- ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
535
- " return _ts.call(this);",
536
- " };",
537
- // 4. onclick prototype hijack (dual-stream consistency)
538
- " var _ba=function(k,p){",
539
- ' if(p==="isTrusted"){',
540
- ' var orig=Reflect.get(k,"isTrusted");',
541
- " if(orig===true)return true;",
542
- ' if(k.type==="paste")return true;',
543
- " return orig;",
544
- " }",
545
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
546
- " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
547
- " }",
548
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
549
- " };",
550
- ' Object.defineProperty(Document.prototype,"onclick",{',
551
- " configurable:true,",
552
- " get:function(){var raw=this.__ocRaw||null;if(!raw)return null;var self=this;",
553
- " return function(e){return raw.call(self,new Proxy(e,{get:function(k,p){return _ba(k,p)}}))}},",
554
- " set:function(fn){this.__ocRaw=fn}",
555
- " });",
556
- "})()"
557
- ].join("\n");
558
- }
559
-
560
36
  // src/cdp-driver/mouse.ts
561
37
  var XBMouseImpl = class {
562
38
  conn;
@@ -597,26 +73,26 @@ var XBMouseImpl = class {
597
73
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
598
74
  this._x = p.x;
599
75
  this._y = p.y;
600
- await sleep2(p.delay);
76
+ await sleep(p.delay);
601
77
  }
602
78
  this._x = tx;
603
79
  this._y = ty;
604
80
  if (_truncated) {
605
81
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: tx, y: ty, button: this._button });
606
82
  }
607
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
83
+ await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
608
84
  } else {
609
85
  await this.move(tx, ty);
610
86
  }
611
- await this.down({ button });
612
- await sleep2(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
87
+ await this.down({ button, clickCount: opts.clickCount ?? 1 });
88
+ await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
613
89
  const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
614
90
  const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
615
91
  this._x = rx;
616
92
  this._y = ry;
617
93
  await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
618
94
  for (let i = 1; i < (opts.clickCount ?? 1); i++) {
619
- if (opts.delay) await sleep2(opts.delay);
95
+ if (opts.delay) await sleep(opts.delay);
620
96
  await this.down({ button });
621
97
  await this.up({ button });
622
98
  }
@@ -632,7 +108,9 @@ var XBMouseImpl = class {
632
108
  x: this._x,
633
109
  y: this._y,
634
110
  button,
635
- clickCount: 1
111
+ // r23: press 必须与 release 声明同一 clickCount——Chrome 按序列计数
112
+ // 合成 click/dblclick,press 缺声明会把计数器重置(dblclick 永不合成)
113
+ clickCount: opts.clickCount ?? 1
636
114
  });
637
115
  }
638
116
  async up(opts = {}) {
@@ -643,7 +121,7 @@ var XBMouseImpl = class {
643
121
  x: this._x,
644
122
  y: this._y,
645
123
  button,
646
- clickCount: 1
124
+ clickCount: opts.clickCount ?? 1
647
125
  });
648
126
  }
649
127
  async move(x, y, opts = {}) {
@@ -654,7 +132,7 @@ var XBMouseImpl = class {
654
132
  const dx = x - fromX;
655
133
  const dy = y - fromY;
656
134
  for (let i = 1; i <= steps; i++) {
657
- if (stealth) await sleep2(rand(16, 28));
135
+ if (stealth) await sleep(rand(16, 28));
658
136
  const t = i / steps;
659
137
  this._x = fromX + dx * t;
660
138
  this._y = fromY + dy * t;
@@ -688,7 +166,7 @@ var XBMouseImpl = class {
688
166
  const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
689
167
  const fx = this._x, fy = this._y;
690
168
  for (let i = 1; i <= steps; i++) {
691
- if (stealth) await sleep2(rand(16, 28));
169
+ if (stealth) await sleep(rand(16, 28));
692
170
  const t = i / steps;
693
171
  this._x = fx + (x - fx) * t;
694
172
  this._y = fy + (y - fy) * t;
@@ -701,7 +179,7 @@ var XBMouseImpl = class {
701
179
  }
702
180
  this._x = x;
703
181
  this._y = y;
704
- await sleep2(rand(60, 140));
182
+ await sleep(rand(60, 140));
705
183
  await this.send("Input.dispatchMouseEvent", {
706
184
  type: "mouseReleased",
707
185
  x,
@@ -724,7 +202,7 @@ var XBMouseImpl = class {
724
202
  await this.conn.send(method, params, this.sessionId);
725
203
  }
726
204
  };
727
- function sleep2(ms) {
205
+ function sleep(ms) {
728
206
  return new Promise((resolve) => setTimeout(resolve, ms));
729
207
  }
730
208
 
@@ -754,17 +232,17 @@ var XBKeyboardImpl = class {
754
232
  await this.dispatchKeyEvent(downParams);
755
233
  if (mapping.text) {
756
234
  if (process.env.XBROWSER_STEALTH !== "off") {
757
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
235
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
758
236
  }
759
237
  await this.dispatchKeyEvent({
760
238
  type: "char",
761
239
  text: mapping.text
762
240
  });
763
241
  }
764
- if (delay > 0) await sleep3(delay);
242
+ if (delay > 0) await sleep2(delay);
765
243
  else {
766
244
  if (process.env.XBROWSER_STEALTH !== "off") {
767
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
245
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
768
246
  }
769
247
  }
770
248
  const upParams = {
@@ -810,16 +288,16 @@ var XBKeyboardImpl = class {
810
288
  const fixedDelay = opts.delay ?? 0;
811
289
  for (const char of text) {
812
290
  if (fixedDelay > 0) {
813
- await sleep3(fixedDelay);
291
+ await sleep2(fixedDelay);
814
292
  } else if (stealth) {
815
- await sleep3(typingDelay());
293
+ await sleep2(typingDelay());
816
294
  }
817
295
  if (stealth && char.length === 1 && Math.random() < DEFAULT_STEALTH_CONFIG.typoProbability) {
818
296
  const wrong = NEIGHBOR_KEYS[char];
819
297
  if (wrong && wrong !== char) {
820
298
  await this.dispatchKeySequence(resolveKeyMapping(wrong), stealth);
821
- await sleep3(rand(120, 420));
822
- await this.dispatchKeySequence(KEY_MAP2.Backspace, stealth);
299
+ await sleep2(rand(120, 420));
300
+ await this.dispatchKeySequence(KEY_MAP.Backspace, stealth);
823
301
  }
824
302
  }
825
303
  await this.dispatchKeySequence(resolveKeyMapping(char), stealth);
@@ -849,7 +327,7 @@ var XBKeyboardImpl = class {
849
327
  await this.dispatchKeyEvent({ type: "char", text: mapping.text });
850
328
  }
851
329
  if (stealth) {
852
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
330
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
853
331
  }
854
332
  const upParams = {
855
333
  type: "keyUp",
@@ -888,7 +366,7 @@ var XBKeyboardImpl = class {
888
366
  }
889
367
  await this.dispatchKeyEvent(downParams);
890
368
  if (process.env.XBROWSER_STEALTH !== "off") {
891
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
369
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
892
370
  }
893
371
  await this.dispatchKeyEvent({
894
372
  type: "keyUp",
@@ -912,7 +390,7 @@ var XBKeyboardImpl = class {
912
390
  await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
913
391
  await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
914
392
  if (process.env.XBROWSER_STEALTH !== "off") {
915
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
393
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
916
394
  }
917
395
  await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
918
396
  await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
@@ -922,7 +400,7 @@ var XBKeyboardImpl = class {
922
400
  }
923
401
  };
924
402
  function resolveKeyMapping(key) {
925
- if (KEY_MAP2[key]) return KEY_MAP2[key];
403
+ if (KEY_MAP[key]) return KEY_MAP[key];
926
404
  if (key.length === 1) {
927
405
  const lower = key.toLowerCase();
928
406
  if (lower >= "a" && lower <= "z") {
@@ -939,7 +417,7 @@ function resolveKeyMapping(key) {
939
417
  }
940
418
  return { key, code: key };
941
419
  }
942
- var KEY_MAP2 = {
420
+ var KEY_MAP = {
943
421
  Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
944
422
  Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
945
423
  Escape: { key: "Escape", code: "Escape", keyCode: 27 },
@@ -1008,7 +486,7 @@ var NEIGHBOR_KEYS = {
1008
486
  "8": "9",
1009
487
  "9": "0"
1010
488
  };
1011
- function sleep3(ms) {
489
+ function sleep2(ms) {
1012
490
  return new Promise((resolve) => setTimeout(resolve, ms));
1013
491
  }
1014
492
 
@@ -1198,6 +676,14 @@ var XBLocatorImpl = class _XBLocatorImpl {
1198
676
  await waitForActionable(this.page, this.selector, opts);
1199
677
  await scrollIntoView(this.page, this.selector);
1200
678
  await this.click({ ...opts });
679
+ await this.page.evaluate(`
680
+ (function() {
681
+ const el = ${this._q(this.selector)};
682
+ if (!el) return;
683
+ if (document.activeElement !== el) el.focus();
684
+ if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
685
+ })()
686
+ `);
1201
687
  if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1202
688
  try {
1203
689
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
@@ -1953,6 +1439,189 @@ var XBPageImpl = class _XBPageImpl {
1953
1439
  await this.conn.send("Network.enable", void 0, this.sessionId);
1954
1440
  await this.conn.send("DOM.enable", void 0, this.sessionId).catch(() => {
1955
1441
  });
1442
+ if (this._contextImpl.ignoreHTTPSErrors) {
1443
+ await this.conn.send("Security.enable", void 0, this.sessionId).catch(() => {
1444
+ });
1445
+ await this.conn.send("Security.setIgnoreCertificateErrors", { ignore: true }, this.sessionId).catch(() => {
1446
+ });
1447
+ }
1448
+ if (this._contextImpl.httpCredentials) {
1449
+ await this.conn.send("Fetch.enable", {
1450
+ patterns: [{ urlPattern: "*", requestStage: "Request" }],
1451
+ handleAuthRequests: true
1452
+ }, this.sessionId).catch(() => {
1453
+ });
1454
+ this._subscriptions.push(
1455
+ this.conn.subscribe("Fetch.requestPaused", this.sessionId, (params) => {
1456
+ if (this._interceptionEnabled) return;
1457
+ const p = params;
1458
+ this.conn.send("Fetch.continueRequest", { requestId: p.requestId }, this.sessionId).catch(() => {
1459
+ });
1460
+ })
1461
+ );
1462
+ this._subscriptions.push(
1463
+ this.conn.subscribe("Fetch.authRequired", this.sessionId, (params) => {
1464
+ const p = params;
1465
+ const creds = this._contextImpl.httpCredentials;
1466
+ this.conn.send("Fetch.continueWithAuth", {
1467
+ requestId: p.requestId,
1468
+ authChallengeResponse: creds ? { response: "ProvideCredentials", username: creds.username, password: creds.password } : { response: "CancelAuth" }
1469
+ }, this.sessionId).catch(() => {
1470
+ });
1471
+ })
1472
+ );
1473
+ }
1474
+ await this.conn.send("Page.addScriptToEvaluateOnNewDocument", {
1475
+ source: [
1476
+ "try { window.print = function() {}; window.__xb_print_suppressed = true; } catch (e) {}",
1477
+ "try {",
1478
+ " if (window.showOpenFilePicker) {",
1479
+ " var __xb_mkHandle = function(name, content) {",
1480
+ ' var file = new File([content], name, { type: "text/plain" });',
1481
+ " return {",
1482
+ ' kind: "file", name: name,',
1483
+ " getFile: function() { return Promise.resolve(file); },",
1484
+ " createWritable: function() { return Promise.resolve({ write: function() { return Promise.resolve(); }, close: function() { return Promise.resolve(); } }); },",
1485
+ " isSameEntry: function() { return Promise.resolve(false); },",
1486
+ ' queryPermission: function() { return Promise.resolve("granted"); },',
1487
+ ' requestPermission: function() { return Promise.resolve("granted"); },',
1488
+ " };",
1489
+ " };",
1490
+ " window.showOpenFilePicker = function() {",
1491
+ ' return Promise.resolve([__xb_mkHandle("xbrowser-stub.txt", "xbrowser fs access stub")]);',
1492
+ " };",
1493
+ " window.showSaveFilePicker = function() {",
1494
+ ' return Promise.resolve(__xb_mkHandle("xbrowser-stub-save.txt", ""));',
1495
+ " };",
1496
+ " window.showDirectoryPicker = function() {",
1497
+ " var emptyIter = function() { return [][Symbol.iterator](); };",
1498
+ " return Promise.resolve({",
1499
+ ' kind: "directory", name: "xbrowser-stub-dir",',
1500
+ " values: emptyIter, entries: emptyIter, keys: emptyIter,",
1501
+ ' getDirectoryHandle: function() { return Promise.reject(new Error("stub empty dir")); },',
1502
+ ' getFileHandle: function() { return Promise.reject(new Error("stub empty dir")); },',
1503
+ " isSameEntry: function() { return Promise.resolve(false); },",
1504
+ ' queryPermission: function() { return Promise.resolve("granted"); },',
1505
+ ' requestPermission: function() { return Promise.resolve("granted"); },',
1506
+ " });",
1507
+ " };",
1508
+ " window.__xb_fs_stub_installed = true;",
1509
+ " }",
1510
+ "} catch (e) {}",
1511
+ // sup-s7: 右键菜单压制——headful 下 CDP 右键弹浏览器原生菜单(CDP
1512
+ // 无法关闭浏览器 UI)。document 捕获阶段 preventDefault 抑制原生
1513
+ // 菜单;不阻断传播,录制 contextmenu 事件与页面自定义菜单不受影响。
1514
+ "try {",
1515
+ ' document.addEventListener("contextmenu", function(e) { e.preventDefault(); }, true);',
1516
+ " window.__xb_ctxmenu_suppressed = true;",
1517
+ "} catch (e) {}",
1518
+ // sup-s10: Notification 守卫——new Notification() 弹 OS 级系统横幅
1519
+ // (headful 污染屏幕,等待通知交互的流程挂起)。no-op 包装但保
1520
+ // API 形状:permission/granted、requestPermission→granted、实例
1521
+ // title/close 可用——页面通知流程无感继续。
1522
+ "try {",
1523
+ " var XBN = function Notification(title, options) {",
1524
+ " this.title = title;",
1525
+ ' this.body = (options && options.body) || "";',
1526
+ ' this.tag = (options && options.tag) || "";',
1527
+ " this.onclick = null; this.onshow = null; this.onerror = null; this.onclose = null;",
1528
+ " };",
1529
+ " XBN.prototype.close = function() {};",
1530
+ ' XBN.permission = "granted";',
1531
+ ' XBN.requestPermission = function() { return Promise.resolve("granted"); };',
1532
+ " XBN.maxActions = (window.Notification && window.Notification.maxActions) || 2;",
1533
+ " window.Notification = XBN;",
1534
+ " window.__xb_notification_suppressed = true;",
1535
+ "} catch (e) {}",
1536
+ // sup-W1: WebAPI 选择器/配对框家族 stub(1/7 Serial)——
1537
+ // navigator.serial.requestPort() 弹浏览器原生串口选择器(CDP 无法
1538
+ // 拦截),headless 下 promise 挂起。stub 返回 rejected Promise
1539
+ // (NotFoundError),页面 catch 分支接管、流程继续。
1540
+ "try {",
1541
+ " if (navigator.serial) {",
1542
+ " navigator.serial.requestPort = function() {",
1543
+ ' return Promise.reject(new DOMException("xbrowser serial stub", "NotFoundError"));',
1544
+ " };",
1545
+ " window.__xb_serial_stubbed = true;",
1546
+ " }",
1547
+ "} catch (e) {}",
1548
+ // sup-W2: WebAPI stub(2/7 USB)——requestDevice 弹原生设备配对框,
1549
+ // 同 Serial pattern:rejected Promise(NotFoundError),catch 接管。
1550
+ "try {",
1551
+ " if (navigator.usb) {",
1552
+ " navigator.usb.requestDevice = function() {",
1553
+ ' return Promise.reject(new DOMException("xbrowser usb stub", "NotFoundError"));',
1554
+ " };",
1555
+ " window.__xb_usb_stubbed = true;",
1556
+ " }",
1557
+ "} catch (e) {}",
1558
+ // sup-W3: WebAPI stub(3/7 Bluetooth)——requestDevice 弹原生蓝牙
1559
+ // 配对框,同 Serial/USB pattern:rejected Promise(NotFoundError)。
1560
+ // CI linux headless 下 navigator.bluetooth 原生不存在——缺失时合成
1561
+ // 对象再包装,保证跨平台语义一致(requestDevice 恒 rejected)。
1562
+ "try {",
1563
+ " if (!navigator.bluetooth) {",
1564
+ ' try { Object.defineProperty(navigator, "bluetooth", { value: {}, configurable: true }); } catch (e) {}',
1565
+ " }",
1566
+ " if (navigator.bluetooth) {",
1567
+ " navigator.bluetooth.requestDevice = function() {",
1568
+ ' return Promise.reject(new DOMException("xbrowser ble stub", "NotFoundError"));',
1569
+ " };",
1570
+ " window.__xb_ble_stubbed = true;",
1571
+ " }",
1572
+ "} catch (e) {}",
1573
+ // sup-W4: WebAPI stub(4/7 HID)——requestDevices 弹原生 HID 设备
1574
+ // 选择器,同家族 pattern:rejected Promise(NotFoundError)。
1575
+ "try {",
1576
+ " if (navigator.hid) {",
1577
+ " navigator.hid.requestDevices = function() {",
1578
+ ' return Promise.reject(new DOMException("xbrowser hid stub", "NotFoundError"));',
1579
+ " };",
1580
+ " window.__xb_hid_stubbed = true;",
1581
+ " }",
1582
+ "} catch (e) {}",
1583
+ // sup-W5: WebAPI stub(5/7 WakeLock)——request('screen') 在无用户
1584
+ // 激活/权限时挂起或拒绝,等待 wake 事件的流程死锁。stub 返回合成
1585
+ // sentinel(release 可用,不真实持锁)。
1586
+ "try {",
1587
+ " if (navigator.wakeLock) {",
1588
+ " navigator.wakeLock.request = function() {",
1589
+ " return Promise.resolve({",
1590
+ " released: false,",
1591
+ ' type: "screen",',
1592
+ " release: function() { this.released = true; return Promise.resolve(); },",
1593
+ " addEventListener: function() {},",
1594
+ " removeEventListener: function() {},",
1595
+ " });",
1596
+ " };",
1597
+ " window.__xb_wakelock_stubbed = true;",
1598
+ " }",
1599
+ "} catch (e) {}",
1600
+ // sup-W6: WebAPI stub(6/7 IdleDetector)——start() 需权限气泡授权
1601
+ // 才能继续,headless 下挂起。stub 返回 rejected(NotAllowedError),
1602
+ // 页面 catch 分支接管。
1603
+ "try {",
1604
+ " if (window.IdleDetector) {",
1605
+ " IdleDetector.start = function() {",
1606
+ ' return Promise.reject(new DOMException("xbrowser idle stub", "NotAllowedError"));',
1607
+ " };",
1608
+ " window.__xb_idle_stubbed = true;",
1609
+ " }",
1610
+ "} catch (e) {}",
1611
+ // sup-W7: WebAPI stub(7/7 PaymentRequest)——show() 弹浏览器支付
1612
+ // 面板(无用户交互不关闭),headless 下 promise 挂起。stub 返回
1613
+ // rejected(NotAllowedError),页面 catch 分支接管。
1614
+ "try {",
1615
+ " if (window.PaymentRequest) {",
1616
+ " PaymentRequest.prototype.show = function() {",
1617
+ ' return Promise.reject(new DOMException("xbrowser pay stub", "NotAllowedError"));',
1618
+ " };",
1619
+ " window.__xb_pay_stubbed = true;",
1620
+ " }",
1621
+ "} catch (e) {}"
1622
+ ].join("\n")
1623
+ }, this.sessionId).catch(() => {
1624
+ });
1956
1625
  this.setupPageEvents();
1957
1626
  this.setupNetworkEvents();
1958
1627
  this.setupConsoleEvents();
@@ -2393,7 +2062,9 @@ Last error: ${lastError.message}` : "";
2393
2062
  await this.locator(selector).click(opts);
2394
2063
  }
2395
2064
  async dblclick(selector, opts = {}) {
2396
- await this.locator(selector).click({ ...opts, clickCount: 2 });
2065
+ const locator = this.locator(selector);
2066
+ await locator.click({ ...opts, clickCount: 1 });
2067
+ await locator.click({ ...opts, clickCount: 2 });
2397
2068
  }
2398
2069
  async fill(selector, value, opts = {}) {
2399
2070
  await this.locator(selector).fill(value, opts);
@@ -2510,6 +2181,26 @@ Last error: ${lastError.message}` : "";
2510
2181
  );
2511
2182
  this._viewportSize = size;
2512
2183
  }
2184
+ /**
2185
+ * env-E2: 缩放变异——deviceScaleFactor 切换(CSS 像素坐标空间不变,
2186
+ * 物理像素翻倍)。保留当前 CSS 视口尺寸,只换 DSF。
2187
+ */
2188
+ async setDeviceScaleFactor(dsf) {
2189
+ const size = this._viewportSize ?? await this.evaluate(
2190
+ "({ width: window.innerWidth, height: window.innerHeight })"
2191
+ );
2192
+ await this.conn.send(
2193
+ "Emulation.setDeviceMetricsOverride",
2194
+ {
2195
+ width: size.width,
2196
+ height: size.height,
2197
+ deviceScaleFactor: dsf,
2198
+ mobile: false
2199
+ },
2200
+ this.sessionId
2201
+ );
2202
+ this._viewportSize = size;
2203
+ }
2513
2204
  // ── Scripts ─────────────────────────────────────────────────
2514
2205
  async addInitScript(script) {
2515
2206
  await this.conn.send(
@@ -2733,6 +2424,12 @@ Last error: ${lastError.message}` : "";
2733
2424
  async _cdpSend(method, params) {
2734
2425
  return this.conn.send(method, params, this.sessionId);
2735
2426
  }
2427
+ /** sup-s11: beforeunload 自动应答语义——'accept'(默认,离开页面,与录制
2428
+ * 意图一致)或 'dismiss'(取消导航留守原页,尽力而为:此构建上 CDP
2429
+ * accept:false 对浏览器侧导航的取消语义不稳定,不保证)。 */
2430
+ setBeforeUnloadBehavior(mode) {
2431
+ this._beforeUnloadAccept = mode === "accept";
2432
+ }
2736
2433
  /**
2737
2434
  * 开启/关闭原生文件选择框拦截(CDP stateful 开关)。
2738
2435
  *
@@ -2791,8 +2488,9 @@ Last error: ${lastError.message}` : "";
2791
2488
  }
2792
2489
  };
2793
2490
  this._emit("dialog", dialog);
2491
+ const autoAccept = p.type === "beforeunload" ? this._beforeUnloadAccept : false;
2794
2492
  setTimeout(() => {
2795
- this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
2493
+ this.conn.send("Page.handleJavaScriptDialog", { accept: autoAccept }, this.sessionId).catch(() => {
2796
2494
  });
2797
2495
  }, 0);
2798
2496
  })
@@ -2935,6 +2633,7 @@ Last error: ${lastError.message}` : "";
2935
2633
  _networkRequests = /* @__PURE__ */ new Map();
2936
2634
  _routeHandlers = [];
2937
2635
  _interceptionEnabled = false;
2636
+ _beforeUnloadAccept = true;
2938
2637
  /** Store network data — called by browser.ts installNetworkCapture or internal event handlers */
2939
2638
  _storeNetworkRequest(requestId, data) {
2940
2639
  this._networkRequests.set(requestId, { requestId, ...data });
@@ -3046,7 +2745,10 @@ Last error: ${lastError.message}` : "";
3046
2745
  if (!this._interceptionEnabled) {
3047
2746
  this._interceptionEnabled = true;
3048
2747
  await this.conn.send("Fetch.enable", {
3049
- patterns: [{ urlPattern: "*", requestStage: "Request" }]
2748
+ patterns: [{ urlPattern: "*", requestStage: "Request" }],
2749
+ // sup-s9: 保留认证自动响应(Fetch.enable 为替换语义,不带此参数
2750
+ // 会顶掉 _init 的 auth 配置)
2751
+ handleAuthRequests: true
3050
2752
  }, this.sessionId);
3051
2753
  this._subscriptions.push(
3052
2754
  this.conn.subscribe("Fetch.requestPaused", this.sessionId, (params) => {
@@ -3099,6 +2801,36 @@ Last error: ${lastError.message}` : "";
3099
2801
  });
3100
2802
  }
3101
2803
  // ── setInputFiles ───────────────────────────────────────────
2804
+ /**
2805
+ * sup-s4: Dropzone 拖拽上传模拟——Dropzone/Uppy/自绘上传区只监听
2806
+ * dragover/drop(无 input[type=file] 可点),真实用户靠 OS 文件拖入
2807
+ * 触发,自动化无法产生 OS 拖拽。页内构造 DataTransfer+File,按拖放
2808
+ * 协议派发 dragenter/dragover/drop(bubbles+cancelable,坐标取目标
2809
+ * 中心)。
2810
+ */
2811
+ async dropFiles(selector, payload) {
2812
+ await this.evaluate(`
2813
+ (function() {
2814
+ var el = ${queryJS(selector)};
2815
+ if (!el) throw new Error('drop target not found: ' + ${JSON.stringify(selector)});
2816
+ var b64 = ${JSON.stringify(payload.buffer.toString("base64"))};
2817
+ var bin = atob(b64);
2818
+ var buf = new Uint8Array(bin.length);
2819
+ for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
2820
+ var file = new File([buf], ${JSON.stringify(payload.name)}, { type: ${JSON.stringify(payload.mimeType)} });
2821
+ var dt = new DataTransfer();
2822
+ dt.items.add(file);
2823
+ var rect = el.getBoundingClientRect();
2824
+ var opts = {
2825
+ bubbles: true, cancelable: true,
2826
+ clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2,
2827
+ };
2828
+ el.dispatchEvent(new DragEvent('dragenter', Object.assign({ dataTransfer: dt }, opts)));
2829
+ el.dispatchEvent(new DragEvent('dragover', Object.assign({ dataTransfer: dt }, opts)));
2830
+ el.dispatchEvent(new DragEvent('drop', Object.assign({ dataTransfer: dt }, opts)));
2831
+ })()
2832
+ `);
2833
+ }
3102
2834
  async setInputFiles(selector, files) {
3103
2835
  const fileArr = Array.isArray(files) ? files : [files];
3104
2836
  const fileList = fileArr.map((f) => ({
@@ -3245,6 +2977,14 @@ var XBContextImpl = class {
3245
2977
  get stealthConfig() {
3246
2978
  return this.options.stealthConfig;
3247
2979
  }
2980
+ /** sup-s9: HTTP 认证凭证经 context 传给 page 层(authRequired 自动响应) */
2981
+ get httpCredentials() {
2982
+ return this.options.httpCredentials;
2983
+ }
2984
+ /** sup-s12: 自签/无效证书放行——经 context 传给 page 层(原为死字段) */
2985
+ get ignoreHTTPSErrors() {
2986
+ return this.options.ignoreHTTPSErrors;
2987
+ }
3248
2988
  async newPage() {
3249
2989
  if (this.closed) throw new Error("Context is closed");
3250
2990
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3474,7 +3214,7 @@ var XBBrowserImpl = class {
3474
3214
  this._exitHandler = null;
3475
3215
  }
3476
3216
  if (this.childProcess) {
3477
- const { killChrome: killChrome2 } = await import("./launcher-44STYRJC.js");
3217
+ const { killChrome: killChrome2 } = await import("./launcher-IK2FKW6T.js");
3478
3218
  await killChrome2(this.childProcess, this.tmpDir);
3479
3219
  }
3480
3220
  await this.conn.close();
@@ -3500,6 +3240,38 @@ var XBBrowserImpl = class {
3500
3240
  } catch {
3501
3241
  }
3502
3242
  const context = new XBContextImpl(this.conn, contextId, this, opts);
3243
+ if (!this.cdpEndpoint && contextId !== "default") {
3244
+ const downloadDir = join(tmpdir(), "xbrowser-downloads");
3245
+ try {
3246
+ mkdirSync(downloadDir, { recursive: true });
3247
+ } catch {
3248
+ }
3249
+ await this.conn.send("Browser.setDownloadBehavior", {
3250
+ behavior: "allowAndName",
3251
+ downloadPath: downloadDir,
3252
+ eventsEnabled: false,
3253
+ browserContextId: contextId
3254
+ }, void 0, 1e4).catch(() => {
3255
+ });
3256
+ await this.conn.send("Browser.grantPermissions", {
3257
+ permissions: [
3258
+ "notifications",
3259
+ "geolocation",
3260
+ "clipboardReadWrite",
3261
+ "clipboardSanitizedWrite",
3262
+ "videoCapture",
3263
+ "audioCapture",
3264
+ "midi"
3265
+ ],
3266
+ browserContextId: contextId
3267
+ }, void 0, 1e4).catch(() => {
3268
+ this.conn.send("Browser.grantPermissions", {
3269
+ permissions: ["notifications", "geolocation"],
3270
+ browserContextId: contextId
3271
+ }, void 0, 1e4).catch(() => {
3272
+ });
3273
+ });
3274
+ }
3503
3275
  context.on("page", (page) => {
3504
3276
  this._emitter.emit("page", page);
3505
3277
  });
@@ -3612,7 +3384,7 @@ var XBBrowserImpl = class {
3612
3384
  if (pageTargets.length === 0 && httpFallbackUrl) {
3613
3385
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3614
3386
  try {
3615
- const { getCDPTargets: getCDPTargets2 } = await import("./launcher-44STYRJC.js");
3387
+ const { getCDPTargets: getCDPTargets2 } = await import("./launcher-IK2FKW6T.js");
3616
3388
  const httpPages = await getCDPTargets2(httpFallbackUrl);
3617
3389
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3618
3390
  for (const p of httpPages) {
@@ -4988,21 +4760,27 @@ async function launch(options = {}) {
4988
4760
  }
4989
4761
  const conn = new CDPConnection(wsEndpoint);
4990
4762
  await conn.ready();
4763
+ let stealthOpts;
4764
+ try {
4765
+ const bv = await conn.send("Browser.getVersion");
4766
+ const m = bv.product.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
4767
+ if (m && !options.stealthConfig?.uaChProfile) {
4768
+ const { deriveUaChProfileForBinary } = await import("./stealth-JOXGV34D.js");
4769
+ stealthOpts = { stealthConfig: { uaChProfile: deriveUaChProfileForBinary(m[0], m[1]) } };
4770
+ }
4771
+ } catch {
4772
+ }
4773
+ if (options.stealthConfig) {
4774
+ stealthOpts = {
4775
+ stealthConfig: { ...stealthOpts?.stealthConfig ?? {}, ...options.stealthConfig }
4776
+ };
4777
+ }
4991
4778
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4992
- const browser = new XBBrowserImpl(
4993
- conn,
4994
- childProcess,
4995
- tmpDir,
4996
- httpEndpoint,
4997
- options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
4998
- );
4779
+ const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint, stealthOpts);
4999
4780
  return { browser, wsEndpoint };
5000
4781
  }
5001
4782
 
5002
4783
  export {
5003
- rand,
5004
- sleep,
5005
- wheelDelta,
5006
4784
  XBMouseImpl,
5007
4785
  XBKeyboardImpl,
5008
4786
  waitForActionable,