@xbrowser/cli 1.20.0 → 1.22.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 (35) hide show
  1. package/README.md +1 -0
  2. package/dist/{browser-Y6B3NS4Q.js → browser-BVRFSUXU.js} +3 -2
  3. package/dist/{browser-NVC7MTML.js → browser-DSYUAE7H.js} +5 -5
  4. package/dist/{browser-PBBC7RAV.js → browser-Q67BJTI2.js} +6 -4
  5. package/dist/{cdp-driver-QYFA4QTP.js → cdp-driver-DC7EIUG5.js} +4 -3
  6. package/dist/{cdp-driver-Z2PLAO42.js → cdp-driver-T7D6H2RW.js} +368 -510
  7. package/dist/{cdp-driver-73F5CVH2.js → cdp-driver-WWO2UZDR.js} +4 -3
  8. package/dist/{chunk-76EKW7YJ.js → chunk-2Q3GQRUP.js} +3 -3
  9. package/dist/{chunk-UYEJ66VX.js → chunk-4OU37CXP.js} +387 -540
  10. package/dist/{chunk-H2A5JUK5.js → chunk-75DNUI6M.js} +9 -0
  11. package/dist/{chunk-FDU5BCPV.js → chunk-7OP2ISEY.js} +378 -540
  12. package/dist/{chunk-3FWLW7FS.js → chunk-A7NEA4PA.js} +39 -1
  13. package/dist/chunk-GYB5BOSP.js +602 -0
  14. package/dist/{chunk-T2KUVO4H.js → chunk-IFKJO2UR.js} +393 -546
  15. package/dist/{chunk-NODRQGOK.js → chunk-IR6C24P7.js} +97 -0
  16. package/dist/{chunk-3KZ34AUC.js → chunk-MYH6FIBZ.js} +2 -1
  17. package/dist/{chunk-75TLPVVZ.js → chunk-T4WTKBWI.js} +2 -2
  18. package/dist/{chunk-HBHOKZPN.js → chunk-V6KGBU5J.js} +97 -0
  19. package/dist/{chunk-IWVG4XYZ.js → chunk-VCVDILH6.js} +2 -1
  20. package/dist/{chunk-A6LPGFAL.js → chunk-WYETQ5C7.js} +1 -1
  21. package/dist/cli.js +25 -13
  22. package/dist/daemon-main.js +20 -14
  23. package/dist/index.d.ts +136 -1
  24. package/dist/index.js +29 -18
  25. package/dist/keep-awake-M4KJS4B4.js +29 -0
  26. package/dist/{launcher-44STYRJC.js → launcher-IK2FKW6T.js} +1 -1
  27. package/dist/{launcher-EXJHHAUL.js → launcher-LUOMMGZC.js} +1 -1
  28. package/dist/{proxy-LUR4U5YF.js → proxy-NKCYE76M.js} +2 -2
  29. package/dist/{session-recorder-SLDBENVF.js → session-recorder-563ISSJK.js} +1 -1
  30. package/dist/{session-recorder-3BEVWHOK.js → session-recorder-GEZVQBKW.js} +1 -1
  31. package/dist/session-replayer-XM5L7LFD.js +927 -0
  32. package/dist/stealth-2AA2FSNN.js +29 -0
  33. package/dist/stealth-JOXGV34D.js +29 -0
  34. package/package.json +2 -1
  35. package/dist/session-replayer-SBYJXQZZ.js +0 -413
@@ -1,7 +1,15 @@
1
+ import {
2
+ DEFAULT_STEALTH_CONFIG,
3
+ bezierTrajectory,
4
+ buildStealthInitScript,
5
+ landingOffset,
6
+ rand,
7
+ typingDelay
8
+ } from "./chunk-GYB5BOSP.js";
1
9
  import {
2
10
  connectToCDP,
3
11
  launchChrome
4
- } from "./chunk-3KZ34AUC.js";
12
+ } from "./chunk-MYH6FIBZ.js";
5
13
  import {
6
14
  errMsg
7
15
  } from "./chunk-GDKLH7ZY.js";
@@ -11,12 +19,15 @@ import {
11
19
 
12
20
  // src/browser.ts
13
21
  import { randomUUID } from "crypto";
14
- import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
15
- import { join } from "path";
22
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
23
+ import { join as join2 } from "path";
16
24
  import { homedir } from "os";
17
25
 
18
26
  // src/cdp-driver/browser.ts
19
27
  import { EventEmitter as EventEmitter3 } from "events";
28
+ import { mkdirSync } from "fs";
29
+ import { join } from "path";
30
+ import { tmpdir } from "os";
20
31
 
21
32
  // src/cdp-driver/context.ts
22
33
  import { EventEmitter as EventEmitter2 } from "events";
@@ -24,496 +35,6 @@ import { EventEmitter as EventEmitter2 } from "events";
24
35
  // src/cdp-driver/page.ts
25
36
  import { EventEmitter } from "events";
26
37
 
27
- // src/cdp-driver/stealth.ts
28
- var DEFAULT_STEALTH_CONFIG = {
29
- bezierCurvature: [0.35, 0.6],
30
- noiseAmplitude: 5.5,
31
- overshootRange: [6, 14],
32
- aimPause: [80, 280],
33
- pressDuration: [60, 140],
34
- releaseDrift: [0.8, 2.5],
35
- landingOffsetSmall: [0.3, 2.5],
36
- landingOffsetLarge: [1.5, 7],
37
- smallElementThreshold: 30,
38
- typingRhythm: {
39
- fastProb: 0.22,
40
- fastRange: [25, 60],
41
- normalRange: [50, 350],
42
- pauseProb: 0.18,
43
- pauseRange: [400, 1200]
44
- },
45
- keyPressDuration: [50, 110],
46
- typoProbability: 0.06,
47
- wheelPeak: 180,
48
- wheelDecayRate: 0.4
49
- };
50
- function rand(min, max) {
51
- return min + Math.random() * (max - min);
52
- }
53
- function sleep(ms) {
54
- return new Promise((resolve) => setTimeout(resolve, ms));
55
- }
56
- function cosineEase(t) {
57
- return 0.5 - 0.5 * Math.cos(Math.PI * t);
58
- }
59
- function bezierTrajectory(x0, y0, x1, y1, config = DEFAULT_STEALTH_CONFIG) {
60
- const dist = Math.hypot(x1 - x0, y1 - y0);
61
- const n = Math.max(10, Math.min(28, Math.round(dist / 15)));
62
- const shortMove = dist < 120;
63
- const curvature = shortMove ? rand(2, 6) : Math.max(dist * rand(...config.bezierCurvature), rand(18, 35));
64
- const dir = Math.random() < 0.5 ? 1 : -1;
65
- const d = dist || 1;
66
- const dx = x1 - x0, dy = y1 - y0;
67
- const c1x = x0 + dx * 0.3 - dy / d * curvature * 0.5 * dir;
68
- const c1y = y0 + dy * 0.3 + dx / d * curvature * 0.5 * dir;
69
- const c2x = x0 + dx * 0.7 - dy / d * curvature * 0.8 * dir;
70
- const c2y = y0 + dy * 0.7 + dx / d * curvature * 0.8 * dir;
71
- const points = [];
72
- for (let i = 1; i <= n; i++) {
73
- const t = cosineEase(i / n);
74
- const mt = 1 - t;
75
- let px = mt ** 3 * x0 + 3 * mt ** 2 * t * c1x + 3 * mt * t ** 2 * c2x + t ** 3 * x1;
76
- let py = mt ** 3 * y0 + 3 * mt ** 2 * t * c1y + 3 * mt * t ** 2 * c2y + t ** 3 * y1;
77
- const amp = shortMove ? Math.min(2, config.noiseAmplitude) : config.noiseAmplitude;
78
- px += rand(-amp, amp);
79
- py += rand(-amp, amp);
80
- points.push({ x: px, y: py, delay: rand(9, 16) });
81
- }
82
- if (!shortMove) {
83
- const over = rand(...config.overshootRange);
84
- const ox = x1 + dx / d * over + rand(-2, 2);
85
- const oy = y1 + dy / d * over + rand(-2, 2);
86
- points.push({ x: ox, y: oy, delay: rand(14, 30) });
87
- points.push({
88
- x: x1 + dx / d * over * 0.4,
89
- y: y1 + dy / d * over * 0.4,
90
- delay: rand(14, 30)
91
- });
92
- }
93
- points.push({ x: x1 + rand(-1, 1), y: y1 + rand(-1, 1), delay: rand(14, 30) });
94
- return points;
95
- }
96
- function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
97
- const isSmall = Math.min(width, height) < config.smallElementThreshold;
98
- const range = isSmall ? config.landingOffsetSmall : config.landingOffsetLarge;
99
- const dx = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
100
- const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
101
- return { dx, dy };
102
- }
103
- function typingDelay(config = DEFAULT_STEALTH_CONFIG) {
104
- const roll = Math.random();
105
- const r = config.typingRhythm;
106
- if (roll < r.pauseProb) return rand(...r.pauseRange);
107
- if (roll < r.pauseProb + r.fastProb) return rand(...r.fastRange);
108
- return rand(...r.normalRange);
109
- }
110
- function wheelDelta(step, config = DEFAULT_STEALTH_CONFIG) {
111
- return Math.round(
112
- config.wheelPeak * Math.exp(-step * config.wheelDecayRate) * rand(0.85, 1.15)
113
- );
114
- }
115
- var KEY_MAP = {};
116
- for (let i = 97; i <= 122; i++) {
117
- const ch = String.fromCharCode(i);
118
- KEY_MAP[ch] = { key: ch, code: "Key" + ch.toUpperCase(), vk: i - 32 };
119
- }
120
- for (let i = 65; i <= 90; i++) {
121
- const ch = String.fromCharCode(i);
122
- KEY_MAP[ch] = { key: ch, code: "Key" + ch, vk: i, shift: true };
123
- }
124
- for (let i = 48; i <= 57; i++) {
125
- const ch = String.fromCharCode(i);
126
- KEY_MAP[ch] = { key: ch, code: "Digit" + ch, vk: i };
127
- }
128
- Object.assign(KEY_MAP, {
129
- " ": { key: " ", code: "Space", vk: 32 },
130
- ".": { key: ".", code: "Period", vk: 190 },
131
- "-": { key: "-", code: "Minus", vk: 189 },
132
- "@": { key: "@", code: "Digit2", vk: 50, shift: true },
133
- "_": { key: "_", code: "Minus", vk: 189, shift: true }
134
- });
135
- function buildStealthInitScript() {
136
- return [
137
- "(function(){",
138
- ' window.__xbStealthVer="57";',
139
- // 1. AEL event proxy
140
- " var o=EventTarget.prototype.addEventListener;",
141
- " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
142
- " var _ael=function(t,f){",
143
- " var op=arguments[2];",
144
- ' if(typeof f!=="function")return o.call(this,t,f,op);',
145
- " var w=function(e){",
146
- " if(!e)return f.call(this,e);",
147
- " return f.call(this,new Proxy(e,{get:function(k,p){",
148
- ' if(p==="sourceCapabilities")return fc;',
149
- // S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
150
- // 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
151
- // 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
152
- // + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
153
- ' if(p==="isTrusted"){',
154
- ' var orig=Reflect.get(k,"isTrusted");',
155
- " if(orig===true)return true;",
156
- ' if(k.type==="paste")return true;',
157
- " return orig;",
158
- " }",
159
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
160
- " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
161
- " return k[p]+_f;",
162
- " }",
163
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
164
- " }}));",
165
- " };",
166
- " return o.call(this,t,w,op);",
167
- " };",
168
- " EventTarget.prototype.addEventListener=_ael;",
169
- // window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
170
- // (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
171
- " try{",
172
- ' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
173
- " if(_weDesc&&_weDesc.get){",
174
- " var _origWeGet=_weDesc.get;",
175
- ' Object.defineProperty(Window.prototype,"event",{',
176
- " configurable:true,",
177
- " get:function(){",
178
- " var ev=_origWeGet.call(this);",
179
- " if(!ev)return ev;",
180
- " return new Proxy(ev,{get:function(k,p){",
181
- ' if(p==="isTrusted"){',
182
- ' var orig=Reflect.get(k,"isTrusted");',
183
- " if(orig===true)return true;",
184
- ' if(k.type==="paste")return true;',
185
- " return orig;",
186
- " }",
187
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
188
- " }});",
189
- " }",
190
- " });",
191
- " }",
192
- " }catch(e){}",
193
- // 2. Screen override (prototype-level, not instance-level)
194
- " var _gw=function(){return 1728};",
195
- " var _gh=function(){return 1117};",
196
- " var _gah=function(){return 1092};",
197
- ' Object.defineProperty(Screen.prototype,"width",{get:_gw,configurable:true});',
198
- ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
199
- ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
200
- ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
201
- // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
202
- // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
203
- // 原型+实例双层覆写(S172 模式)。
204
- " try{",
205
- ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
206
- ' 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;});};',
207
- ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
208
- " var wrapUAD=function(uad){",
209
- " if(!uad||uad.__xbUA)return uad;",
210
- " try{",
211
- ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
212
- " var _ghev=uad.getHighEntropyValues.bind(uad);",
213
- " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
214
- ' 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]);}',
215
- " return o;});};",
216
- " uad.__xbUA=true;",
217
- " }catch(e){}",
218
- " return uad;",
219
- " };",
220
- ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
221
- " if(_nua&&_nua.get){",
222
- " var _nuag=function(){return fixUA(_nua.get.call(this));};",
223
- ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
224
- ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
225
- " }",
226
- ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
227
- " if(_nuad&&_nuad.get){",
228
- " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
229
- ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
230
- ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
231
- " }",
232
- " }catch(e){}",
233
- // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
234
- // —— 真实浏览器失焦/后台时 hasFocus=false
235
- // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
236
- ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
237
- // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
238
- // Headless software raster differs subtly from Chrome GPU raster —
239
- // toDataURL hashes fingerprint the rasterizer. Inject a stable
240
- // (per-page-load) subpixel shift into fillText so hashes look like a
241
- // distinct-but-consistent real GPU, and hide the HEADLESS tell in
242
- // WebGL renderer strings.
243
- " var _seed=Math.floor(Math.random()*2147483647);",
244
- " var _prng=function(){_seed=(_seed*48271)%2147483647;return _seed/2147483647;};",
245
- " var _dx=(_prng()*0.4-0.2).toFixed(3)*1, _dy=(_prng()*0.4-0.2).toFixed(3)*1;",
246
- " var _fillText=CanvasRenderingContext2D.prototype.fillText;",
247
- " CanvasRenderingContext2D.prototype.fillText=function(t,x,y,m){",
248
- " return _fillText.call(this,t,x+_dx,y+_dy,m);",
249
- " };",
250
- " var _toDataURL=HTMLCanvasElement.prototype.toDataURL;",
251
- " HTMLCanvasElement.prototype.toDataURL=function(){",
252
- ' var ctx=this.getContext("2d");',
253
- " if(ctx){var d=ctx.getImageData(0,0,Math.min(this.width,2),Math.min(this.height,2));",
254
- " for(var i=0;i<d.data.length;i+=4){if(d.data[i+3]>0){d.data[i]^=1;break;}}",
255
- " ctx.putImageData(d,0,0);}",
256
- " return _toDataURL.apply(this,arguments);",
257
- " };",
258
- " try{",
259
- " var _gl=HTMLCanvasElement.prototype.getContext;",
260
- " HTMLCanvasElement.prototype.getContext=function(t,o){",
261
- " var c=_gl.call(this,t,o);",
262
- ' if(c&&(t==="webgl"||t==="experimental-webgl")&&c.getParameter){',
263
- " var _gp=c.getParameter.bind(c);",
264
- " c.getParameter=function(p){",
265
- " var v=_gp(p);",
266
- ' if(typeof v==="string"&&/SwiftShader|Software|Rasterizer|Headless/i.test(v))',
267
- ' return "ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Max, Unspecified Version)";',
268
- " return v;};}",
269
- " return c;};",
270
- " }catch(e){}",
271
- // 5. AudioContext fingerprint: per-load stable micro-noise on channel
272
- // data (d21). DSP sum differences between headless software audio and
273
- // real hardware audio are a classic fingerprint — add ±1e-7 level
274
- // noise (inaudible, changes the sum hash).
275
- " try{",
276
- " var _gcd=AudioBuffer.prototype.getChannelData;",
277
- " AudioBuffer.prototype.getChannelData=function(ch){",
278
- " var d=_gcd.call(this,ch);",
279
- ' var key="__xb_audio_"+ch;',
280
- " if(!this[key]){",
281
- " this[key]=true;",
282
- " for(var i=0;i<d.length;i+=997){d[i]=d[i]+(_prng()-0.5)*2e-7;}",
283
- " }",
284
- " return d;",
285
- " };",
286
- " var _gffd=AnalyserNode.prototype.getFloatFrequencyData;",
287
- " AnalyserNode.prototype.getFloatFrequencyData=function(arr){",
288
- " _gffd.call(this,arr);",
289
- " for(var i=0;i<arr.length;i+=31){arr[i]=arr[i]+(_prng()-0.5)*0.01;}",
290
- " };",
291
- " var _gfbd=AnalyserNode.prototype.getByteFrequencyData;",
292
- " AnalyserNode.prototype.getByteFrequencyData=function(arr){",
293
- " _gfbd.call(this,arr);",
294
- " for(var i=0;i<arr.length;i+=31){arr[i]=(arr[i]+((_prng()*3)|0))&255;}",
295
- " };",
296
- " }catch(e){}",
297
- // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
298
- " try{",
299
- ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
300
- " if(_tmw&&_tmw.get){",
301
- " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
302
- ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
303
- " }",
304
- " }catch(e){}",
305
- " try{",
306
- ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
307
- // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
308
- " var _sv=SpeechSynthesis.prototype.getVoices;",
309
- " SpeechSynthesis.prototype.getVoices=function(){",
310
- " var real=_sv.call(this);",
311
- " if(real&&real.length>50)return real;",
312
- // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
313
- // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
314
- ' 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};});',
315
- " };",
316
- // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
317
- // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
318
- " try{",
319
- " var _piv=function(){",
320
- ' var frs=document.querySelectorAll("iframe");',
321
- " for(var i=0;i<frs.length;i++){try{",
322
- " var cw=frs[i].contentWindow;",
323
- " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
324
- " cw.speechSynthesis.__xbP=true;",
325
- " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
326
- " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
327
- ' 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};});};',
328
- " }catch(e2){}}",
329
- " };",
330
- " setInterval(_piv,2000);",
331
- " var _mo=new MutationObserver(function(muts){",
332
- " for(var k=0;k<muts.length;k++){var ns=muts[k].addedNodes;",
333
- ' for(var q=0;q<ns.length;q++){if(ns[q].tagName==="IFRAME")setTimeout(_piv,20);}}',
334
- " });",
335
- " _mo.observe(document.documentElement,{childList:true,subtree:true});",
336
- " }catch(e){}",
337
- " }catch(e){}",
338
- " try{",
339
- " var _gb=navigator.getBattery.bind(navigator);",
340
- " navigator.getBattery=function(){",
341
- " return _gb().then(function(b){",
342
- ' Object.defineProperty(b,"charging",{get:function(){return true;},configurable:true});',
343
- " return b;});",
344
- " };",
345
- " }catch(e){}",
346
- // 3c. WebRTC local IP leak guard (d23): headless STUN candidates can
347
- // expose host IPs; mDNS-only candidates are the modern Chrome default.
348
- " try{",
349
- " var _oc=RTCPeerConnection.prototype.createOffer;",
350
- " RTCPeerConnection.prototype.createOffer=function(){",
351
- " var p=_oc.apply(this,arguments);",
352
- " var self=this;",
353
- " return p.then(function(offer){",
354
- ' offer.sdp=offer.sdp.split(String.fromCharCode(10)).filter(function(l){return l.indexOf("typ host")<0}).join(String.fromCharCode(10));',
355
- " return offer;});",
356
- " };",
357
- " }catch(e){}",
358
- // 3d-2. performance.now precision clamp (d34): full-precision timestamps
359
- // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
360
- " try{",
361
- " var _pn=Performance.prototype.now;",
362
- " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
363
- " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
364
- ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
365
- " if(_pto&&_pto.get){",
366
- " // 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",
367
- " // \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",
368
- " // \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",
369
- " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
370
- " var _tog=function(){return _pto.get.call(performance)+_toff;};",
371
- ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
372
- " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
373
- ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
374
- ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
375
- " }",
376
- " }catch(e){}",
377
- // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
378
- // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
379
- // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
380
- // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
381
- // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
382
- // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
383
- " try{",
384
- " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
385
- " var _gce=PointerEvent.prototype.getCoalescedEvents;",
386
- " var _lm=null;",
387
- " var _gceH=function(){",
388
- " var list=_gce.call(this);",
389
- ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
390
- " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
391
- " var out=null;",
392
- " if((!list||list.length<2)&&_lm){",
393
- " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
394
- // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
395
- // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
396
- // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
397
- " var expN=Math.floor(dt/8)-1;",
398
- // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
399
- // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
400
- " if(expN>0&&dt>=12&&Math.random()>0.15){",
401
- " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
402
- " var span=Math.min(dt,n*8.3);",
403
- " var frac=Math.min(1,span/Math.max(dt,1));",
404
- " out=[];",
405
- " for(var i=1;i<=n;i++){",
406
- // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
407
- " var k=n+1-i;",
408
- ' var ev=new PointerEvent("pointermove",{',
409
- ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
410
- " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
411
- " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
412
- " screenX:this.screenX,screenY:this.screenY,",
413
- " buttons:this.buttons,button:this.button,",
414
- " bubbles:true,cancelable:true,composed:true,",
415
- " width:this.width,height:this.height,pressure:this.pressure,",
416
- " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
417
- " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
418
- // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
419
- // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
420
- // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
421
- // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
422
- // 合成事件同时间戳、群内间隔塌到 ~2ms)。
423
- " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
424
- ' if(p==="isTrusted")return true;',
425
- ' if(p==="timeStamp")return t2;',
426
- ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
427
- " }});})(ev,tsV));",
428
- " }",
429
- " out.push(this);",
430
- " }",
431
- " }",
432
- " _lm=cur;",
433
- " return out||list;",
434
- " };",
435
- " PointerEvent.prototype.getCoalescedEvents=_gceH;",
436
- // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
437
- // "getCoalescedEvents")——toString 白名单管不到 name 属性。
438
- ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
439
- " }",
440
- " }catch(e){}",
441
- // 3d. Chrome object depth (d24): automation fakes usually only set
442
- // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
443
- " try{",
444
- " if(window.chrome){",
445
- ' 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"}};',
446
- ' 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};',
447
- " if(!window.chrome.csi)window.chrome.csi=function(){return{};}",
448
- " if(!window.chrome.loadTimes)window.chrome.loadTimes=function(){return{};}",
449
- " }",
450
- " }catch(e){}",
451
- // 3e. Font availability patch (d25): headless Chromium misses some system
452
- // fonts (e.g. Menlo on macOS) that real Chrome has. Register a FontFace
453
- // aliasing the missing font to a local() equivalent so offsetWidth-based
454
- // font probing sees the same availability as the faked environment.
455
- " try{",
456
- ' var _fontAliases=[["Menlo","Courier New"],["SF Mono","Menlo"],["Segoe UI","Helvetica"]];',
457
- " _fontAliases.forEach(function(pa){",
458
- ' try{var ff=new FontFace(pa[0],"local("+JSON.stringify(pa[1])+")");',
459
- " document.fonts.add(ff);ff.load();}catch(e2){}",
460
- " });",
461
- " }catch(e){}",
462
- // 3g. Notification.requestPermission 延迟(d62):headless 无原生横幅,
463
- // request 瞬回 denied —— 真机 request 会 pending 在横幅上数秒到分钟。
464
- // 延迟 2-6s 再 resolve,模拟横幅期(用户"看了下然后拒绝")。
465
- " try{",
466
- " if(window.Notification&&Notification.requestPermission){",
467
- " var _nrp=Notification.requestPermission.bind(Notification);",
468
- " Notification.requestPermission=function(cb){",
469
- " return new Promise(function(res){",
470
- " setTimeout(function(){_nrp().then(function(r){if(cb)try{cb(r)}catch(e){}res(r);});},2000+Math.random()*4000);",
471
- " });",
472
- " };",
473
- " }",
474
- " }catch(e){}",
475
- // 3. toString disguise (name-list based)
476
- " var _ts=Function.prototype.toString;",
477
- " var _hf=document.hasFocus;",
478
- " Function.prototype.toString=function(){",
479
- ' if(this===_ael)return"function addEventListener(type, callback) { [native code] }";',
480
- ' if(this===_hf)return"function hasFocus() { [native code] }";',
481
- ' if(this===_gw)return"function get width() { [native code] }";',
482
- ' if(this===_gh)return"function get height() { [native code] }";',
483
- ' if(this===_gah)return"function get availHeight() { [native code] }";',
484
- ' if(this===CanvasRenderingContext2D.prototype.fillText)return"function fillText() { [native code] }";',
485
- ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
486
- ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
487
- ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
488
- ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
489
- ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
490
- ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
491
- ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
492
- " return _ts.call(this);",
493
- " };",
494
- // 4. onclick prototype hijack (dual-stream consistency)
495
- " var _ba=function(k,p){",
496
- ' if(p==="isTrusted"){',
497
- ' var orig=Reflect.get(k,"isTrusted");',
498
- " if(orig===true)return true;",
499
- ' if(k.type==="paste")return true;',
500
- " return orig;",
501
- " }",
502
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
503
- " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
504
- " }",
505
- ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
506
- " };",
507
- ' Object.defineProperty(Document.prototype,"onclick",{',
508
- " configurable:true,",
509
- " get:function(){var raw=this.__ocRaw||null;if(!raw)return null;var self=this;",
510
- " return function(e){return raw.call(self,new Proxy(e,{get:function(k,p){return _ba(k,p)}}))}},",
511
- " set:function(fn){this.__ocRaw=fn}",
512
- " });",
513
- "})()"
514
- ].join("\n");
515
- }
516
-
517
38
  // src/cdp-driver/mouse.ts
518
39
  var XBMouseImpl = class {
519
40
  conn;
@@ -554,26 +75,26 @@ var XBMouseImpl = class {
554
75
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
555
76
  this._x = p.x;
556
77
  this._y = p.y;
557
- await sleep2(p.delay);
78
+ await sleep(p.delay);
558
79
  }
559
80
  this._x = tx;
560
81
  this._y = ty;
561
82
  if (_truncated) {
562
83
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: tx, y: ty, button: this._button });
563
84
  }
564
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
85
+ await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
565
86
  } else {
566
87
  await this.move(tx, ty);
567
88
  }
568
- await this.down({ button });
569
- await sleep2(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
89
+ await this.down({ button, clickCount: opts.clickCount ?? 1 });
90
+ await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
570
91
  const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
571
92
  const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
572
93
  this._x = rx;
573
94
  this._y = ry;
574
95
  await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
575
96
  for (let i = 1; i < (opts.clickCount ?? 1); i++) {
576
- if (opts.delay) await sleep2(opts.delay);
97
+ if (opts.delay) await sleep(opts.delay);
577
98
  await this.down({ button });
578
99
  await this.up({ button });
579
100
  }
@@ -589,7 +110,9 @@ var XBMouseImpl = class {
589
110
  x: this._x,
590
111
  y: this._y,
591
112
  button,
592
- clickCount: 1
113
+ // r23: press 必须与 release 声明同一 clickCount——Chrome 按序列计数
114
+ // 合成 click/dblclick,press 缺声明会把计数器重置(dblclick 永不合成)
115
+ clickCount: opts.clickCount ?? 1
593
116
  });
594
117
  }
595
118
  async up(opts = {}) {
@@ -600,7 +123,7 @@ var XBMouseImpl = class {
600
123
  x: this._x,
601
124
  y: this._y,
602
125
  button,
603
- clickCount: 1
126
+ clickCount: opts.clickCount ?? 1
604
127
  });
605
128
  }
606
129
  async move(x, y, opts = {}) {
@@ -611,7 +134,7 @@ var XBMouseImpl = class {
611
134
  const dx = x - fromX;
612
135
  const dy = y - fromY;
613
136
  for (let i = 1; i <= steps; i++) {
614
- if (stealth) await sleep2(rand(16, 28));
137
+ if (stealth) await sleep(rand(16, 28));
615
138
  const t = i / steps;
616
139
  this._x = fromX + dx * t;
617
140
  this._y = fromY + dy * t;
@@ -645,7 +168,7 @@ var XBMouseImpl = class {
645
168
  const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
646
169
  const fx = this._x, fy = this._y;
647
170
  for (let i = 1; i <= steps; i++) {
648
- if (stealth) await sleep2(rand(16, 28));
171
+ if (stealth) await sleep(rand(16, 28));
649
172
  const t = i / steps;
650
173
  this._x = fx + (x - fx) * t;
651
174
  this._y = fy + (y - fy) * t;
@@ -658,7 +181,7 @@ var XBMouseImpl = class {
658
181
  }
659
182
  this._x = x;
660
183
  this._y = y;
661
- await sleep2(rand(60, 140));
184
+ await sleep(rand(60, 140));
662
185
  await this.send("Input.dispatchMouseEvent", {
663
186
  type: "mouseReleased",
664
187
  x,
@@ -681,7 +204,7 @@ var XBMouseImpl = class {
681
204
  await this.conn.send(method, params, this.sessionId);
682
205
  }
683
206
  };
684
- function sleep2(ms) {
207
+ function sleep(ms) {
685
208
  return new Promise((resolve) => setTimeout(resolve, ms));
686
209
  }
687
210
 
@@ -711,17 +234,17 @@ var XBKeyboardImpl = class {
711
234
  await this.dispatchKeyEvent(downParams);
712
235
  if (mapping.text) {
713
236
  if (process.env.XBROWSER_STEALTH !== "off") {
714
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
237
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
715
238
  }
716
239
  await this.dispatchKeyEvent({
717
240
  type: "char",
718
241
  text: mapping.text
719
242
  });
720
243
  }
721
- if (delay > 0) await sleep3(delay);
244
+ if (delay > 0) await sleep2(delay);
722
245
  else {
723
246
  if (process.env.XBROWSER_STEALTH !== "off") {
724
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
247
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
725
248
  }
726
249
  }
727
250
  const upParams = {
@@ -767,16 +290,16 @@ var XBKeyboardImpl = class {
767
290
  const fixedDelay = opts.delay ?? 0;
768
291
  for (const char of text) {
769
292
  if (fixedDelay > 0) {
770
- await sleep3(fixedDelay);
293
+ await sleep2(fixedDelay);
771
294
  } else if (stealth) {
772
- await sleep3(typingDelay());
295
+ await sleep2(typingDelay());
773
296
  }
774
297
  if (stealth && char.length === 1 && Math.random() < DEFAULT_STEALTH_CONFIG.typoProbability) {
775
298
  const wrong = NEIGHBOR_KEYS[char];
776
299
  if (wrong && wrong !== char) {
777
300
  await this.dispatchKeySequence(resolveKeyMapping(wrong), stealth);
778
- await sleep3(rand(120, 420));
779
- await this.dispatchKeySequence(KEY_MAP2.Backspace, stealth);
301
+ await sleep2(rand(120, 420));
302
+ await this.dispatchKeySequence(KEY_MAP.Backspace, stealth);
780
303
  }
781
304
  }
782
305
  await this.dispatchKeySequence(resolveKeyMapping(char), stealth);
@@ -806,7 +329,7 @@ var XBKeyboardImpl = class {
806
329
  await this.dispatchKeyEvent({ type: "char", text: mapping.text });
807
330
  }
808
331
  if (stealth) {
809
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
332
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
810
333
  }
811
334
  const upParams = {
812
335
  type: "keyUp",
@@ -845,7 +368,7 @@ var XBKeyboardImpl = class {
845
368
  }
846
369
  await this.dispatchKeyEvent(downParams);
847
370
  if (process.env.XBROWSER_STEALTH !== "off") {
848
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
371
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
849
372
  }
850
373
  await this.dispatchKeyEvent({
851
374
  type: "keyUp",
@@ -869,7 +392,7 @@ var XBKeyboardImpl = class {
869
392
  await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
870
393
  await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
871
394
  if (process.env.XBROWSER_STEALTH !== "off") {
872
- await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
395
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
873
396
  }
874
397
  await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
875
398
  await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
@@ -879,7 +402,7 @@ var XBKeyboardImpl = class {
879
402
  }
880
403
  };
881
404
  function resolveKeyMapping(key) {
882
- if (KEY_MAP2[key]) return KEY_MAP2[key];
405
+ if (KEY_MAP[key]) return KEY_MAP[key];
883
406
  if (key.length === 1) {
884
407
  const lower = key.toLowerCase();
885
408
  if (lower >= "a" && lower <= "z") {
@@ -896,7 +419,7 @@ function resolveKeyMapping(key) {
896
419
  }
897
420
  return { key, code: key };
898
421
  }
899
- var KEY_MAP2 = {
422
+ var KEY_MAP = {
900
423
  Enter: { key: "Enter", code: "Enter", text: "\r", keyCode: 13 },
901
424
  Tab: { key: "Tab", code: "Tab", text: " ", keyCode: 9 },
902
425
  Escape: { key: "Escape", code: "Escape", keyCode: 27 },
@@ -965,7 +488,7 @@ var NEIGHBOR_KEYS = {
965
488
  "8": "9",
966
489
  "9": "0"
967
490
  };
968
- function sleep3(ms) {
491
+ function sleep2(ms) {
969
492
  return new Promise((resolve) => setTimeout(resolve, ms));
970
493
  }
971
494
 
@@ -1257,6 +780,14 @@ var XBLocatorImpl = class _XBLocatorImpl {
1257
780
  await waitForActionable(this.page, this.selector, opts);
1258
781
  await scrollIntoView(this.page, this.selector);
1259
782
  await this.click({ ...opts });
783
+ await this.page.evaluate(`
784
+ (function() {
785
+ const el = ${this._q(this.selector)};
786
+ if (!el) return;
787
+ if (document.activeElement !== el) el.focus();
788
+ if ((el.value || el.textContent || '') !== '') document.execCommand('selectAll');
789
+ })()
790
+ `);
1260
791
  if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1261
792
  try {
1262
793
  const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
@@ -2012,6 +1543,189 @@ var XBPageImpl = class _XBPageImpl {
2012
1543
  await this.conn.send("Network.enable", void 0, this.sessionId);
2013
1544
  await this.conn.send("DOM.enable", void 0, this.sessionId).catch(() => {
2014
1545
  });
1546
+ if (this._contextImpl.ignoreHTTPSErrors) {
1547
+ await this.conn.send("Security.enable", void 0, this.sessionId).catch(() => {
1548
+ });
1549
+ await this.conn.send("Security.setIgnoreCertificateErrors", { ignore: true }, this.sessionId).catch(() => {
1550
+ });
1551
+ }
1552
+ if (this._contextImpl.httpCredentials) {
1553
+ await this.conn.send("Fetch.enable", {
1554
+ patterns: [{ urlPattern: "*", requestStage: "Request" }],
1555
+ handleAuthRequests: true
1556
+ }, this.sessionId).catch(() => {
1557
+ });
1558
+ this._subscriptions.push(
1559
+ this.conn.subscribe("Fetch.requestPaused", this.sessionId, (params) => {
1560
+ if (this._interceptionEnabled) return;
1561
+ const p = params;
1562
+ this.conn.send("Fetch.continueRequest", { requestId: p.requestId }, this.sessionId).catch(() => {
1563
+ });
1564
+ })
1565
+ );
1566
+ this._subscriptions.push(
1567
+ this.conn.subscribe("Fetch.authRequired", this.sessionId, (params) => {
1568
+ const p = params;
1569
+ const creds = this._contextImpl.httpCredentials;
1570
+ this.conn.send("Fetch.continueWithAuth", {
1571
+ requestId: p.requestId,
1572
+ authChallengeResponse: creds ? { response: "ProvideCredentials", username: creds.username, password: creds.password } : { response: "CancelAuth" }
1573
+ }, this.sessionId).catch(() => {
1574
+ });
1575
+ })
1576
+ );
1577
+ }
1578
+ await this.conn.send("Page.addScriptToEvaluateOnNewDocument", {
1579
+ source: [
1580
+ "try { window.print = function() {}; window.__xb_print_suppressed = true; } catch (e) {}",
1581
+ "try {",
1582
+ " if (window.showOpenFilePicker) {",
1583
+ " var __xb_mkHandle = function(name, content) {",
1584
+ ' var file = new File([content], name, { type: "text/plain" });',
1585
+ " return {",
1586
+ ' kind: "file", name: name,',
1587
+ " getFile: function() { return Promise.resolve(file); },",
1588
+ " createWritable: function() { return Promise.resolve({ write: function() { return Promise.resolve(); }, close: function() { return Promise.resolve(); } }); },",
1589
+ " isSameEntry: function() { return Promise.resolve(false); },",
1590
+ ' queryPermission: function() { return Promise.resolve("granted"); },',
1591
+ ' requestPermission: function() { return Promise.resolve("granted"); },',
1592
+ " };",
1593
+ " };",
1594
+ " window.showOpenFilePicker = function() {",
1595
+ ' return Promise.resolve([__xb_mkHandle("xbrowser-stub.txt", "xbrowser fs access stub")]);',
1596
+ " };",
1597
+ " window.showSaveFilePicker = function() {",
1598
+ ' return Promise.resolve(__xb_mkHandle("xbrowser-stub-save.txt", ""));',
1599
+ " };",
1600
+ " window.showDirectoryPicker = function() {",
1601
+ " var emptyIter = function() { return [][Symbol.iterator](); };",
1602
+ " return Promise.resolve({",
1603
+ ' kind: "directory", name: "xbrowser-stub-dir",',
1604
+ " values: emptyIter, entries: emptyIter, keys: emptyIter,",
1605
+ ' getDirectoryHandle: function() { return Promise.reject(new Error("stub empty dir")); },',
1606
+ ' getFileHandle: function() { return Promise.reject(new Error("stub empty dir")); },',
1607
+ " isSameEntry: function() { return Promise.resolve(false); },",
1608
+ ' queryPermission: function() { return Promise.resolve("granted"); },',
1609
+ ' requestPermission: function() { return Promise.resolve("granted"); },',
1610
+ " });",
1611
+ " };",
1612
+ " window.__xb_fs_stub_installed = true;",
1613
+ " }",
1614
+ "} catch (e) {}",
1615
+ // sup-s7: 右键菜单压制——headful 下 CDP 右键弹浏览器原生菜单(CDP
1616
+ // 无法关闭浏览器 UI)。document 捕获阶段 preventDefault 抑制原生
1617
+ // 菜单;不阻断传播,录制 contextmenu 事件与页面自定义菜单不受影响。
1618
+ "try {",
1619
+ ' document.addEventListener("contextmenu", function(e) { e.preventDefault(); }, true);',
1620
+ " window.__xb_ctxmenu_suppressed = true;",
1621
+ "} catch (e) {}",
1622
+ // sup-s10: Notification 守卫——new Notification() 弹 OS 级系统横幅
1623
+ // (headful 污染屏幕,等待通知交互的流程挂起)。no-op 包装但保
1624
+ // API 形状:permission/granted、requestPermission→granted、实例
1625
+ // title/close 可用——页面通知流程无感继续。
1626
+ "try {",
1627
+ " var XBN = function Notification(title, options) {",
1628
+ " this.title = title;",
1629
+ ' this.body = (options && options.body) || "";',
1630
+ ' this.tag = (options && options.tag) || "";',
1631
+ " this.onclick = null; this.onshow = null; this.onerror = null; this.onclose = null;",
1632
+ " };",
1633
+ " XBN.prototype.close = function() {};",
1634
+ ' XBN.permission = "granted";',
1635
+ ' XBN.requestPermission = function() { return Promise.resolve("granted"); };',
1636
+ " XBN.maxActions = (window.Notification && window.Notification.maxActions) || 2;",
1637
+ " window.Notification = XBN;",
1638
+ " window.__xb_notification_suppressed = true;",
1639
+ "} catch (e) {}",
1640
+ // sup-W1: WebAPI 选择器/配对框家族 stub(1/7 Serial)——
1641
+ // navigator.serial.requestPort() 弹浏览器原生串口选择器(CDP 无法
1642
+ // 拦截),headless 下 promise 挂起。stub 返回 rejected Promise
1643
+ // (NotFoundError),页面 catch 分支接管、流程继续。
1644
+ "try {",
1645
+ " if (navigator.serial) {",
1646
+ " navigator.serial.requestPort = function() {",
1647
+ ' return Promise.reject(new DOMException("xbrowser serial stub", "NotFoundError"));',
1648
+ " };",
1649
+ " window.__xb_serial_stubbed = true;",
1650
+ " }",
1651
+ "} catch (e) {}",
1652
+ // sup-W2: WebAPI stub(2/7 USB)——requestDevice 弹原生设备配对框,
1653
+ // 同 Serial pattern:rejected Promise(NotFoundError),catch 接管。
1654
+ "try {",
1655
+ " if (navigator.usb) {",
1656
+ " navigator.usb.requestDevice = function() {",
1657
+ ' return Promise.reject(new DOMException("xbrowser usb stub", "NotFoundError"));',
1658
+ " };",
1659
+ " window.__xb_usb_stubbed = true;",
1660
+ " }",
1661
+ "} catch (e) {}",
1662
+ // sup-W3: WebAPI stub(3/7 Bluetooth)——requestDevice 弹原生蓝牙
1663
+ // 配对框,同 Serial/USB pattern:rejected Promise(NotFoundError)。
1664
+ // CI linux headless 下 navigator.bluetooth 原生不存在——缺失时合成
1665
+ // 对象再包装,保证跨平台语义一致(requestDevice 恒 rejected)。
1666
+ "try {",
1667
+ " if (!navigator.bluetooth) {",
1668
+ ' try { Object.defineProperty(navigator, "bluetooth", { value: {}, configurable: true }); } catch (e) {}',
1669
+ " }",
1670
+ " if (navigator.bluetooth) {",
1671
+ " navigator.bluetooth.requestDevice = function() {",
1672
+ ' return Promise.reject(new DOMException("xbrowser ble stub", "NotFoundError"));',
1673
+ " };",
1674
+ " window.__xb_ble_stubbed = true;",
1675
+ " }",
1676
+ "} catch (e) {}",
1677
+ // sup-W4: WebAPI stub(4/7 HID)——requestDevices 弹原生 HID 设备
1678
+ // 选择器,同家族 pattern:rejected Promise(NotFoundError)。
1679
+ "try {",
1680
+ " if (navigator.hid) {",
1681
+ " navigator.hid.requestDevices = function() {",
1682
+ ' return Promise.reject(new DOMException("xbrowser hid stub", "NotFoundError"));',
1683
+ " };",
1684
+ " window.__xb_hid_stubbed = true;",
1685
+ " }",
1686
+ "} catch (e) {}",
1687
+ // sup-W5: WebAPI stub(5/7 WakeLock)——request('screen') 在无用户
1688
+ // 激活/权限时挂起或拒绝,等待 wake 事件的流程死锁。stub 返回合成
1689
+ // sentinel(release 可用,不真实持锁)。
1690
+ "try {",
1691
+ " if (navigator.wakeLock) {",
1692
+ " navigator.wakeLock.request = function() {",
1693
+ " return Promise.resolve({",
1694
+ " released: false,",
1695
+ ' type: "screen",',
1696
+ " release: function() { this.released = true; return Promise.resolve(); },",
1697
+ " addEventListener: function() {},",
1698
+ " removeEventListener: function() {},",
1699
+ " });",
1700
+ " };",
1701
+ " window.__xb_wakelock_stubbed = true;",
1702
+ " }",
1703
+ "} catch (e) {}",
1704
+ // sup-W6: WebAPI stub(6/7 IdleDetector)——start() 需权限气泡授权
1705
+ // 才能继续,headless 下挂起。stub 返回 rejected(NotAllowedError),
1706
+ // 页面 catch 分支接管。
1707
+ "try {",
1708
+ " if (window.IdleDetector) {",
1709
+ " IdleDetector.start = function() {",
1710
+ ' return Promise.reject(new DOMException("xbrowser idle stub", "NotAllowedError"));',
1711
+ " };",
1712
+ " window.__xb_idle_stubbed = true;",
1713
+ " }",
1714
+ "} catch (e) {}",
1715
+ // sup-W7: WebAPI stub(7/7 PaymentRequest)——show() 弹浏览器支付
1716
+ // 面板(无用户交互不关闭),headless 下 promise 挂起。stub 返回
1717
+ // rejected(NotAllowedError),页面 catch 分支接管。
1718
+ "try {",
1719
+ " if (window.PaymentRequest) {",
1720
+ " PaymentRequest.prototype.show = function() {",
1721
+ ' return Promise.reject(new DOMException("xbrowser pay stub", "NotAllowedError"));',
1722
+ " };",
1723
+ " window.__xb_pay_stubbed = true;",
1724
+ " }",
1725
+ "} catch (e) {}"
1726
+ ].join("\n")
1727
+ }, this.sessionId).catch(() => {
1728
+ });
2015
1729
  this.setupPageEvents();
2016
1730
  this.setupNetworkEvents();
2017
1731
  this.setupConsoleEvents();
@@ -2043,7 +1757,7 @@ var XBPageImpl = class _XBPageImpl {
2043
1757
  try {
2044
1758
  await this.conn.send(
2045
1759
  "Page.addScriptToEvaluateOnNewDocument",
2046
- { source: buildStealthInitScript() },
1760
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
2047
1761
  this.sessionId
2048
1762
  );
2049
1763
  } catch {
@@ -2452,7 +2166,9 @@ Last error: ${lastError.message}` : "";
2452
2166
  await this.locator(selector).click(opts);
2453
2167
  }
2454
2168
  async dblclick(selector, opts = {}) {
2455
- await this.locator(selector).click({ ...opts, clickCount: 2 });
2169
+ const locator = this.locator(selector);
2170
+ await locator.click({ ...opts, clickCount: 1 });
2171
+ await locator.click({ ...opts, clickCount: 2 });
2456
2172
  }
2457
2173
  async fill(selector, value, opts = {}) {
2458
2174
  await this.locator(selector).fill(value, opts);
@@ -2569,6 +2285,26 @@ Last error: ${lastError.message}` : "";
2569
2285
  );
2570
2286
  this._viewportSize = size;
2571
2287
  }
2288
+ /**
2289
+ * env-E2: 缩放变异——deviceScaleFactor 切换(CSS 像素坐标空间不变,
2290
+ * 物理像素翻倍)。保留当前 CSS 视口尺寸,只换 DSF。
2291
+ */
2292
+ async setDeviceScaleFactor(dsf) {
2293
+ const size = this._viewportSize ?? await this.evaluate(
2294
+ "({ width: window.innerWidth, height: window.innerHeight })"
2295
+ );
2296
+ await this.conn.send(
2297
+ "Emulation.setDeviceMetricsOverride",
2298
+ {
2299
+ width: size.width,
2300
+ height: size.height,
2301
+ deviceScaleFactor: dsf,
2302
+ mobile: false
2303
+ },
2304
+ this.sessionId
2305
+ );
2306
+ this._viewportSize = size;
2307
+ }
2572
2308
  // ── Scripts ─────────────────────────────────────────────────
2573
2309
  async addInitScript(script) {
2574
2310
  await this.conn.send(
@@ -2792,6 +2528,12 @@ Last error: ${lastError.message}` : "";
2792
2528
  async _cdpSend(method, params) {
2793
2529
  return this.conn.send(method, params, this.sessionId);
2794
2530
  }
2531
+ /** sup-s11: beforeunload 自动应答语义——'accept'(默认,离开页面,与录制
2532
+ * 意图一致)或 'dismiss'(取消导航留守原页,尽力而为:此构建上 CDP
2533
+ * accept:false 对浏览器侧导航的取消语义不稳定,不保证)。 */
2534
+ setBeforeUnloadBehavior(mode) {
2535
+ this._beforeUnloadAccept = mode === "accept";
2536
+ }
2795
2537
  /**
2796
2538
  * 开启/关闭原生文件选择框拦截(CDP stateful 开关)。
2797
2539
  *
@@ -2850,8 +2592,9 @@ Last error: ${lastError.message}` : "";
2850
2592
  }
2851
2593
  };
2852
2594
  this._emit("dialog", dialog);
2595
+ const autoAccept = p.type === "beforeunload" ? this._beforeUnloadAccept : false;
2853
2596
  setTimeout(() => {
2854
- this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
2597
+ this.conn.send("Page.handleJavaScriptDialog", { accept: autoAccept }, this.sessionId).catch(() => {
2855
2598
  });
2856
2599
  }, 0);
2857
2600
  })
@@ -2994,6 +2737,7 @@ Last error: ${lastError.message}` : "";
2994
2737
  _networkRequests = /* @__PURE__ */ new Map();
2995
2738
  _routeHandlers = [];
2996
2739
  _interceptionEnabled = false;
2740
+ _beforeUnloadAccept = true;
2997
2741
  /** Store network data — called by browser.ts installNetworkCapture or internal event handlers */
2998
2742
  _storeNetworkRequest(requestId, data) {
2999
2743
  this._networkRequests.set(requestId, { requestId, ...data });
@@ -3105,7 +2849,10 @@ Last error: ${lastError.message}` : "";
3105
2849
  if (!this._interceptionEnabled) {
3106
2850
  this._interceptionEnabled = true;
3107
2851
  await this.conn.send("Fetch.enable", {
3108
- patterns: [{ urlPattern: "*", requestStage: "Request" }]
2852
+ patterns: [{ urlPattern: "*", requestStage: "Request" }],
2853
+ // sup-s9: 保留认证自动响应(Fetch.enable 为替换语义,不带此参数
2854
+ // 会顶掉 _init 的 auth 配置)
2855
+ handleAuthRequests: true
3109
2856
  }, this.sessionId);
3110
2857
  this._subscriptions.push(
3111
2858
  this.conn.subscribe("Fetch.requestPaused", this.sessionId, (params) => {
@@ -3158,6 +2905,36 @@ Last error: ${lastError.message}` : "";
3158
2905
  });
3159
2906
  }
3160
2907
  // ── setInputFiles ───────────────────────────────────────────
2908
+ /**
2909
+ * sup-s4: Dropzone 拖拽上传模拟——Dropzone/Uppy/自绘上传区只监听
2910
+ * dragover/drop(无 input[type=file] 可点),真实用户靠 OS 文件拖入
2911
+ * 触发,自动化无法产生 OS 拖拽。页内构造 DataTransfer+File,按拖放
2912
+ * 协议派发 dragenter/dragover/drop(bubbles+cancelable,坐标取目标
2913
+ * 中心)。
2914
+ */
2915
+ async dropFiles(selector, payload) {
2916
+ await this.evaluate(`
2917
+ (function() {
2918
+ var el = ${queryJS(selector)};
2919
+ if (!el) throw new Error('drop target not found: ' + ${JSON.stringify(selector)});
2920
+ var b64 = ${JSON.stringify(payload.buffer.toString("base64"))};
2921
+ var bin = atob(b64);
2922
+ var buf = new Uint8Array(bin.length);
2923
+ for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
2924
+ var file = new File([buf], ${JSON.stringify(payload.name)}, { type: ${JSON.stringify(payload.mimeType)} });
2925
+ var dt = new DataTransfer();
2926
+ dt.items.add(file);
2927
+ var rect = el.getBoundingClientRect();
2928
+ var opts = {
2929
+ bubbles: true, cancelable: true,
2930
+ clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2,
2931
+ };
2932
+ el.dispatchEvent(new DragEvent('dragenter', Object.assign({ dataTransfer: dt }, opts)));
2933
+ el.dispatchEvent(new DragEvent('dragover', Object.assign({ dataTransfer: dt }, opts)));
2934
+ el.dispatchEvent(new DragEvent('drop', Object.assign({ dataTransfer: dt }, opts)));
2935
+ })()
2936
+ `);
2937
+ }
3161
2938
  async setInputFiles(selector, files) {
3162
2939
  const fileArr = Array.isArray(files) ? files : [files];
3163
2940
  const fileList = fileArr.map((f) => ({
@@ -3300,6 +3077,18 @@ var XBContextImpl = class {
3300
3077
  this.options = opts;
3301
3078
  this.setupAutoAttach();
3302
3079
  }
3080
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3081
+ get stealthConfig() {
3082
+ return this.options.stealthConfig;
3083
+ }
3084
+ /** sup-s9: HTTP 认证凭证经 context 传给 page 层(authRequired 自动响应) */
3085
+ get httpCredentials() {
3086
+ return this.options.httpCredentials;
3087
+ }
3088
+ /** sup-s12: 自签/无效证书放行——经 context 传给 page 层(原为死字段) */
3089
+ get ignoreHTTPSErrors() {
3090
+ return this.options.ignoreHTTPSErrors;
3091
+ }
3303
3092
  async newPage() {
3304
3093
  if (this.closed) throw new Error("Context is closed");
3305
3094
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3466,20 +3255,8 @@ var XBContextImpl = class {
3466
3255
 
3467
3256
  // src/cdp-driver/browser.ts
3468
3257
  var XBBrowserImpl = class {
3469
- conn;
3470
- _emitter = new EventEmitter3();
3471
- _contexts = /* @__PURE__ */ new Map();
3472
- _disconnected = false;
3473
- childProcess = null;
3474
- tmpDir;
3475
- _exitHandler = null;
3476
- /**
3477
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3478
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3479
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3480
- */
3481
- cdpEndpoint;
3482
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3258
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3259
+ this.launchOpts = launchOpts;
3483
3260
  this.conn = conn;
3484
3261
  this.childProcess = childProcess ?? null;
3485
3262
  this.tmpDir = tmpDir;
@@ -3507,6 +3284,20 @@ var XBBrowserImpl = class {
3507
3284
  process.on("exit", this._exitHandler);
3508
3285
  }
3509
3286
  }
3287
+ launchOpts;
3288
+ conn;
3289
+ _emitter = new EventEmitter3();
3290
+ _contexts = /* @__PURE__ */ new Map();
3291
+ _disconnected = false;
3292
+ childProcess = null;
3293
+ tmpDir;
3294
+ _exitHandler = null;
3295
+ /**
3296
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3297
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3298
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3299
+ */
3300
+ cdpEndpoint;
3510
3301
  get disconnected() {
3511
3302
  return this._disconnected;
3512
3303
  }
@@ -3527,13 +3318,16 @@ var XBBrowserImpl = class {
3527
3318
  this._exitHandler = null;
3528
3319
  }
3529
3320
  if (this.childProcess) {
3530
- const { killChrome: killChrome2 } = await import("./launcher-44STYRJC.js");
3321
+ const { killChrome: killChrome2 } = await import("./launcher-IK2FKW6T.js");
3531
3322
  await killChrome2(this.childProcess, this.tmpDir);
3532
3323
  }
3533
3324
  await this.conn.close();
3534
3325
  this._emitter.emit("disconnected");
3535
3326
  }
3536
3327
  async newContext(opts = {}) {
3328
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3329
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3330
+ }
3537
3331
  if (this._disconnected) {
3538
3332
  throw new Error("Browser is disconnected");
3539
3333
  }
@@ -3550,6 +3344,38 @@ var XBBrowserImpl = class {
3550
3344
  } catch {
3551
3345
  }
3552
3346
  const context = new XBContextImpl(this.conn, contextId, this, opts);
3347
+ if (!this.cdpEndpoint && contextId !== "default") {
3348
+ const downloadDir = join(tmpdir(), "xbrowser-downloads");
3349
+ try {
3350
+ mkdirSync(downloadDir, { recursive: true });
3351
+ } catch {
3352
+ }
3353
+ await this.conn.send("Browser.setDownloadBehavior", {
3354
+ behavior: "allowAndName",
3355
+ downloadPath: downloadDir,
3356
+ eventsEnabled: false,
3357
+ browserContextId: contextId
3358
+ }, void 0, 1e4).catch(() => {
3359
+ });
3360
+ await this.conn.send("Browser.grantPermissions", {
3361
+ permissions: [
3362
+ "notifications",
3363
+ "geolocation",
3364
+ "clipboardReadWrite",
3365
+ "clipboardSanitizedWrite",
3366
+ "videoCapture",
3367
+ "audioCapture",
3368
+ "midi"
3369
+ ],
3370
+ browserContextId: contextId
3371
+ }, void 0, 1e4).catch(() => {
3372
+ this.conn.send("Browser.grantPermissions", {
3373
+ permissions: ["notifications", "geolocation"],
3374
+ browserContextId: contextId
3375
+ }, void 0, 1e4).catch(() => {
3376
+ });
3377
+ });
3378
+ }
3553
3379
  context.on("page", (page) => {
3554
3380
  this._emitter.emit("page", page);
3555
3381
  });
@@ -3662,7 +3488,7 @@ var XBBrowserImpl = class {
3662
3488
  if (pageTargets.length === 0 && httpFallbackUrl) {
3663
3489
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3664
3490
  try {
3665
- const { getCDPTargets: getCDPTargets3 } = await import("./launcher-44STYRJC.js");
3491
+ const { getCDPTargets: getCDPTargets3 } = await import("./launcher-IK2FKW6T.js");
3666
3492
  const httpPages = await getCDPTargets3(httpFallbackUrl);
3667
3493
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3668
3494
  for (const p of httpPages) {
@@ -3760,6 +3586,9 @@ function extractAllStrings(rawArgs) {
3760
3586
  }
3761
3587
  return strings.length > 0 ? strings.join("\n") : null;
3762
3588
  }
3589
+ function isProbeMarked(code) {
3590
+ return code.includes("/* @xb-probe */");
3591
+ }
3763
3592
 
3764
3593
  // src/cdp-interceptor/rules/dom-mutation.ts
3765
3594
  var DOM_PATTERNS = [
@@ -4322,6 +4151,7 @@ var eventSimulationRule = {
4322
4151
  evaluate(ctx) {
4323
4152
  const userCode = extractUserCode(ctx);
4324
4153
  if (!userCode) return null;
4154
+ if (isProbeMarked(userCode)) return null;
4325
4155
  for (const p of EVENT_PATTERNS) {
4326
4156
  if (p.pattern.test(userCode)) {
4327
4157
  return {
@@ -4649,6 +4479,11 @@ function createRuleEngine(customRules) {
4649
4479
  ...ctx,
4650
4480
  sessionState: getSessionState(ctx.sessionId)
4651
4481
  };
4482
+ try {
4483
+ const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
4484
+ if (isProbeMarked(code)) return null;
4485
+ } catch {
4486
+ }
4652
4487
  for (const rule of rules) {
4653
4488
  if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
4654
4489
  const decision = rule.evaluate(fullCtx);
@@ -4969,8 +4804,23 @@ async function launch(options = {}) {
4969
4804
  }
4970
4805
  const conn = new CDPConnection(wsEndpoint);
4971
4806
  await conn.ready();
4807
+ let stealthOpts;
4808
+ try {
4809
+ const bv = await conn.send("Browser.getVersion");
4810
+ const m = bv.product.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
4811
+ if (m && !options.stealthConfig?.uaChProfile) {
4812
+ const { deriveUaChProfileForBinary } = await import("./stealth-JOXGV34D.js");
4813
+ stealthOpts = { stealthConfig: { uaChProfile: deriveUaChProfileForBinary(m[0], m[1]) } };
4814
+ }
4815
+ } catch {
4816
+ }
4817
+ if (options.stealthConfig) {
4818
+ stealthOpts = {
4819
+ stealthConfig: { ...stealthOpts?.stealthConfig ?? {}, ...options.stealthConfig }
4820
+ };
4821
+ }
4972
4822
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4973
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
4823
+ const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint, stealthOpts);
4974
4824
  return { browser, wsEndpoint };
4975
4825
  }
4976
4826
 
@@ -5480,12 +5330,12 @@ function logSessionEvent(event, details) {
5480
5330
  const pid = process.pid;
5481
5331
  console.error(`[SESSION] ${ts} [PID:${pid}] ${event} | ${details}`);
5482
5332
  }
5483
- var SESSION_DIR = join(homedir(), ".xbrowser", "sessions");
5333
+ var SESSION_DIR = join2(homedir(), ".xbrowser", "sessions");
5484
5334
  function sessionFile(name) {
5485
- return join(SESSION_DIR, `${name}.json`);
5335
+ return join2(SESSION_DIR, `${name}.json`);
5486
5336
  }
5487
5337
  function ensureSessionDir() {
5488
- mkdirSync(SESSION_DIR, { recursive: true });
5338
+ mkdirSync2(SESSION_DIR, { recursive: true });
5489
5339
  }
5490
5340
  var sessions = new SessionStore();
5491
5341
  var _sharedBrowser = null;
@@ -6189,7 +6039,7 @@ async function closeSessionByName(name) {
6189
6039
  } catch {
6190
6040
  }
6191
6041
  try {
6192
- const { SessionRecorder } = await import("./session-recorder-SLDBENVF.js");
6042
+ const { SessionRecorder } = await import("./session-recorder-563ISSJK.js");
6193
6043
  SessionRecorder.cleanup(session.name);
6194
6044
  } catch {
6195
6045
  }
@@ -6250,7 +6100,7 @@ function resetForTesting() {
6250
6100
  _sharedCdpProxy = null;
6251
6101
  try {
6252
6102
  for (const f of readdirSync(SESSION_DIR)) {
6253
- unlinkSync(join(SESSION_DIR, f));
6103
+ unlinkSync(join2(SESSION_DIR, f));
6254
6104
  }
6255
6105
  } catch {
6256
6106
  }
@@ -6283,9 +6133,6 @@ async function ensureProcessCanExit() {
6283
6133
  }
6284
6134
 
6285
6135
  export {
6286
- rand,
6287
- sleep,
6288
- wheelDelta,
6289
6136
  createRuleEngine,
6290
6137
  touchSession,
6291
6138
  findTargetPage,