@xbrowser/cli 1.13.0 → 1.15.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.
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-CW7L53JE.js";
24
- import "./chunk-R2POF2GP.js";
23
+ } from "./chunk-K5E4CWRV.js";
24
+ import "./chunk-NTTFKS6T.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-A6LPGFAL.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-BY5TT6WZ.js";
23
+ } from "./chunk-HFP4QCZL.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-HD4FX2N5.js";
24
- import "./chunk-T2CNKWPV.js";
23
+ } from "./chunk-XQ4KOZ37.js";
24
+ import "./chunk-UXDGEDT7.js";
25
25
  import "./chunk-3FWLW7FS.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import "./chunk-GDKLH7ZY.js";
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-T2CNKWPV.js";
17
+ } from "./chunk-UXDGEDT7.js";
18
18
  import "./chunk-3FWLW7FS.js";
19
19
  import {
20
20
  connectToCDP,
@@ -24,7 +24,7 @@ var DEFAULT_STEALTH_CONFIG = {
24
24
  bezierCurvature: [0.35, 0.6],
25
25
  noiseAmplitude: 5.5,
26
26
  overshootRange: [6, 14],
27
- aimPause: [150, 400],
27
+ aimPause: [80, 280],
28
28
  pressDuration: [60, 140],
29
29
  releaseDrift: [0.8, 2.5],
30
30
  landingOffsetSmall: [0.3, 2.5],
@@ -92,6 +92,13 @@ function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
92
92
  const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
93
93
  return { dx, dy };
94
94
  }
95
+ function typingDelay(config = DEFAULT_STEALTH_CONFIG) {
96
+ const roll = Math.random();
97
+ const r = config.typingRhythm;
98
+ if (roll < r.pauseProb) return rand(...r.pauseRange);
99
+ if (roll < r.pauseProb + r.fastProb) return rand(...r.fastRange);
100
+ return rand(...r.normalRange);
101
+ }
95
102
  var KEY_MAP = {};
96
103
  for (let i = 97; i <= 122; i++) {
97
104
  const ch = String.fromCharCode(i);
@@ -115,6 +122,7 @@ Object.assign(KEY_MAP, {
115
122
  function buildStealthInitScript() {
116
123
  return [
117
124
  "(function(){",
125
+ ' window.__xbStealthVer="57";',
118
126
  // 1. AEL event proxy
119
127
  " var o=EventTarget.prototype.addEventListener;",
120
128
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -122,10 +130,10 @@ function buildStealthInitScript() {
122
130
  " var op=arguments[2];",
123
131
  ' if(typeof f!=="function")return o.call(this,t,f,op);',
124
132
  " var w=function(e){",
125
- " if(!e||e.constructor===FocusEvent||e.constructor===KeyboardEvent)return f.call(this,e);",
133
+ " if(!e)return f.call(this,e);",
126
134
  " return f.call(this,new Proxy(e,{get:function(k,p){",
127
135
  ' if(p==="sourceCapabilities")return fc;',
128
- ' if(p==="isTrusted")return k.isTrusted;',
136
+ ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
129
137
  ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
130
138
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
131
139
  " return k[p]+_f;",
@@ -277,6 +285,70 @@ function buildStealthInitScript() {
277
285
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
278
286
  ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
279
287
  " }catch(e){}",
288
+ // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
289
+ // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
290
+ // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
291
+ // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
292
+ // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
293
+ // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
294
+ " try{",
295
+ " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
296
+ " var _gce=PointerEvent.prototype.getCoalescedEvents;",
297
+ " var _lm=null;",
298
+ " var _gceH=function(){",
299
+ " var list=_gce.call(this);",
300
+ ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
301
+ " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
302
+ " var out=null;",
303
+ " if((!list||list.length<2)&&_lm){",
304
+ " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
305
+ // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
306
+ // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
307
+ // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
308
+ " var expN=Math.floor(dt/8)-1;",
309
+ // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
310
+ // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
311
+ " if(expN>0&&dt>=12&&Math.random()>0.15){",
312
+ " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
313
+ " var span=Math.min(dt,n*8.3);",
314
+ " var frac=Math.min(1,span/Math.max(dt,1));",
315
+ " out=[];",
316
+ " for(var i=1;i<=n;i++){",
317
+ // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
318
+ " var k=n+1-i;",
319
+ ' var ev=new PointerEvent("pointermove",{',
320
+ ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
321
+ " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
322
+ " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
323
+ " screenX:this.screenX,screenY:this.screenY,",
324
+ " buttons:this.buttons,button:this.button,",
325
+ " bubbles:true,cancelable:true,composed:true,",
326
+ " width:this.width,height:this.height,pressure:this.pressure,",
327
+ " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
328
+ " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
329
+ // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
330
+ // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
331
+ // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
332
+ // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
333
+ // 合成事件同时间戳、群内间隔塌到 ~2ms)。
334
+ " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
335
+ ' if(p==="isTrusted")return true;',
336
+ ' if(p==="timeStamp")return t2;',
337
+ ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
338
+ " }});})(ev,tsV));",
339
+ " }",
340
+ " out.push(this);",
341
+ " }",
342
+ " }",
343
+ " _lm=cur;",
344
+ " return out||list;",
345
+ " };",
346
+ " PointerEvent.prototype.getCoalescedEvents=_gceH;",
347
+ // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
348
+ // "getCoalescedEvents")——toString 白名单管不到 name 属性。
349
+ ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
350
+ " }",
351
+ " }catch(e){}",
280
352
  // 3d. Chrome object depth (d24): automation fakes usually only set
281
353
  // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
282
354
  " try{",
@@ -311,11 +383,12 @@ function buildStealthInitScript() {
311
383
  ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
312
384
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
313
385
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
386
+ ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
314
387
  " return _ts.call(this);",
315
388
  " };",
316
389
  // 4. onclick prototype hijack (dual-stream consistency)
317
390
  " var _ba=function(k,p){",
318
- ' if(p==="isTrusted")return k.isTrusted;',
391
+ ' if(p==="isTrusted")return Reflect.get(k,"isTrusted");',
319
392
  ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
320
393
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
321
394
  " }",
@@ -421,12 +494,14 @@ var XBMouseImpl = class {
421
494
  });
422
495
  }
423
496
  async move(x, y, opts = {}) {
497
+ const stealth = opts.stealth ?? process.env.XBROWSER_STEALTH !== "off";
424
498
  const steps = Math.max(1, opts.steps ?? 1);
425
499
  const fromX = this._x;
426
500
  const fromY = this._y;
427
501
  const dx = x - fromX;
428
502
  const dy = y - fromY;
429
503
  for (let i = 1; i <= steps; i++) {
504
+ if (stealth) await sleep(rand(16, 28));
430
505
  const t = i / steps;
431
506
  this._x = fromX + dx * t;
432
507
  this._y = fromY + dy * t;
@@ -535,37 +610,61 @@ var XBKeyboardImpl = class {
535
610
  await this.dispatchKeyEvent(params);
536
611
  }
537
612
  async type(text, opts = {}) {
538
- const delay = opts.delay ?? 0;
613
+ const stealth = opts.stealth ?? process.env.XBROWSER_STEALTH !== "off";
614
+ const fixedDelay = opts.delay ?? 0;
539
615
  for (const char of text) {
540
- if (delay > 0) await sleep2(delay);
541
- const mapping = resolveKeyMapping(char);
542
- const downParams = {
543
- type: "rawKeyDown",
544
- key: mapping.key,
545
- code: mapping.code
546
- };
547
- if (mapping.text) {
548
- downParams.text = mapping.text;
549
- downParams.unmodifiedText = mapping.text;
616
+ if (fixedDelay > 0) {
617
+ await sleep2(fixedDelay);
618
+ } else if (stealth) {
619
+ await sleep2(typingDelay());
550
620
  }
551
- if (mapping.keyCode) {
552
- downParams.windowsVirtualKeyCode = mapping.keyCode;
553
- }
554
- await this.dispatchKeyEvent(downParams);
555
- if (mapping.text) {
556
- await this.dispatchKeyEvent({
557
- type: "char",
558
- text: mapping.text
559
- });
621
+ if (stealth && char.length === 1 && Math.random() < DEFAULT_STEALTH_CONFIG.typoProbability) {
622
+ const wrong = NEIGHBOR_KEYS[char];
623
+ if (wrong && wrong !== char) {
624
+ await this.dispatchKeySequence(resolveKeyMapping(wrong), stealth);
625
+ await sleep2(rand(120, 420));
626
+ await this.dispatchKeySequence(KEY_MAP2.Backspace, stealth);
627
+ }
560
628
  }
561
- await this.dispatchKeyEvent({
562
- type: "keyUp",
563
- key: mapping.key,
564
- code: mapping.code,
565
- ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
566
- });
629
+ await this.dispatchKeySequence(resolveKeyMapping(char), stealth);
567
630
  }
568
631
  }
632
+ /**
633
+ * Single-key event sequence: rawKeyDown → (char) → keyUp.
634
+ * Shared by press() and type() — two separate implementations drifted
635
+ * apart once already (S60: type() lost the keyPressDuration that press()
636
+ * had; d50 caught it as 33/33 keys with down→up of 2ms).
637
+ */
638
+ async dispatchKeySequence(mapping, stealth) {
639
+ const downParams = {
640
+ type: "rawKeyDown",
641
+ key: mapping.key,
642
+ code: mapping.code
643
+ };
644
+ if (mapping.text) {
645
+ downParams.text = mapping.text;
646
+ downParams.unmodifiedText = mapping.text;
647
+ }
648
+ if (mapping.keyCode) {
649
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
650
+ }
651
+ await this.dispatchKeyEvent(downParams);
652
+ if (mapping.text) {
653
+ await this.dispatchKeyEvent({ type: "char", text: mapping.text });
654
+ }
655
+ if (stealth) {
656
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
657
+ }
658
+ const upParams = {
659
+ type: "keyUp",
660
+ key: mapping.key,
661
+ code: mapping.code
662
+ };
663
+ if (mapping.keyCode) {
664
+ upParams.windowsVirtualKeyCode = mapping.keyCode;
665
+ }
666
+ await this.dispatchKeyEvent(upParams);
667
+ }
569
668
  async insertText(text) {
570
669
  await this.conn.send(
571
670
  "Input.insertText",
@@ -573,6 +672,35 @@ var XBKeyboardImpl = class {
573
672
  this.sessionId
574
673
  );
575
674
  }
675
+ /**
676
+ * Navigation key press using CDP type 'keyDown' (NOT rawKeyDown).
677
+ * rawKeyDown skips browser default actions — select option navigation,
678
+ * arrow-key scrolling etc. never fire under it (d53: ArrowDown on a
679
+ * focused <select> left the value unchanged). keyDown carries the
680
+ * default action.
681
+ */
682
+ async pressNav(key) {
683
+ const mapping = resolveKeyMapping(key);
684
+ const downParams = {
685
+ type: "keyDown",
686
+ key: mapping.key,
687
+ code: mapping.code
688
+ };
689
+ if (mapping.keyCode) {
690
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
691
+ downParams.nativeVirtualKeyCode = mapping.keyCode;
692
+ }
693
+ await this.dispatchKeyEvent(downParams);
694
+ if (process.env.XBROWSER_STEALTH !== "off") {
695
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
696
+ }
697
+ await this.dispatchKeyEvent({
698
+ type: "keyUp",
699
+ key: mapping.key,
700
+ code: mapping.code,
701
+ ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
702
+ });
703
+ }
576
704
  async dispatchKeyEvent(params) {
577
705
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
578
706
  }
@@ -627,6 +755,43 @@ var KEY_MAP2 = {
627
755
  F11: { key: "F11", code: "F11", keyCode: 122 },
628
756
  F12: { key: "F12", code: "F12", keyCode: 123 }
629
757
  };
758
+ var NEIGHBOR_KEYS = {
759
+ a: "s",
760
+ b: "v",
761
+ c: "x",
762
+ d: "f",
763
+ e: "r",
764
+ f: "g",
765
+ g: "h",
766
+ h: "j",
767
+ i: "o",
768
+ j: "k",
769
+ k: "l",
770
+ l: "k",
771
+ m: "n",
772
+ n: "m",
773
+ o: "p",
774
+ p: "o",
775
+ q: "w",
776
+ r: "t",
777
+ s: "d",
778
+ t: "y",
779
+ u: "i",
780
+ v: "b",
781
+ w: "e",
782
+ x: "c",
783
+ y: "u",
784
+ z: "x",
785
+ "1": "2",
786
+ "2": "3",
787
+ "3": "4",
788
+ "4": "5",
789
+ "5": "6",
790
+ "6": "7",
791
+ "7": "8",
792
+ "8": "9",
793
+ "9": "0"
794
+ };
630
795
  function sleep2(ms) {
631
796
  return new Promise((resolve) => setTimeout(resolve, ms));
632
797
  }
@@ -944,23 +1109,13 @@ var XBLocatorImpl = class _XBLocatorImpl {
944
1109
  async press(key, opts = {}) {
945
1110
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
946
1111
  await scrollIntoView(this.page, this.selector);
947
- await this.page.evaluate(`
948
- (function() {
949
- const el = ${this._q(this.selector)};
950
- if (el) el.focus();
951
- })()
952
- `);
1112
+ await this.click({ timeout: opts.timeout });
953
1113
  await this.page.keyboard.press(key);
954
1114
  }
955
1115
  async pressSequentially(text, opts = {}) {
956
1116
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
957
1117
  await scrollIntoView(this.page, this.selector);
958
- await this.page.evaluate(`
959
- (function() {
960
- const el = ${this._q(this.selector)};
961
- if (el) el.focus();
962
- })()
963
- `);
1118
+ await this.click({ timeout: opts.timeout });
964
1119
  await this.page.keyboard.type(text, { delay: opts.delay });
965
1120
  }
966
1121
  async hover(opts = {}) {
@@ -1015,11 +1170,36 @@ var XBLocatorImpl = class _XBLocatorImpl {
1015
1170
  async selectOption(value) {
1016
1171
  await waitForActionable(this.page, this.selector);
1017
1172
  const values = Array.isArray(value) ? value : [value];
1018
- const selected = await this.page.evaluate(`
1173
+ const info = await this.page.evaluate(`
1019
1174
  (function() {
1020
1175
  const el = ${this._q(this.selector)};
1021
1176
  if (!el || el.tagName !== 'SELECT') throw new Error('Not a select element');
1022
-
1177
+ const values = ${JSON.stringify(values)};
1178
+ let target = -1, targetValue = '';
1179
+ outer:
1180
+ for (let i = 0; i < el.options.length; i++) {
1181
+ const opt = el.options[i];
1182
+ for (const v of values) {
1183
+ const hit = typeof v === 'object'
1184
+ ? (v.label !== undefined ? opt.label === v.label
1185
+ : v.value !== undefined ? opt.value === v.value
1186
+ : opt.index === v.index)
1187
+ : (opt.value === v || opt.label === v);
1188
+ if (hit) { target = i; targetValue = opt.value; break outer; }
1189
+ }
1190
+ }
1191
+ return { cur: el.selectedIndex, target: target, targetValue: targetValue, multiple: el.multiple };
1192
+ })()
1193
+ `);
1194
+ if (info.target < 0) {
1195
+ throw new Error(`Option not found: ${JSON.stringify(values)}`);
1196
+ }
1197
+ if (!info.multiple) {
1198
+ await this.click({ timeout: 5e3 });
1199
+ }
1200
+ const selected = await this.page.evaluate(`
1201
+ (function() {
1202
+ const el = ${this._q(this.selector)};
1023
1203
  const values = ${JSON.stringify(values)};
1024
1204
  const selectedValues = [];
1025
1205
 
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-R2POF2GP.js";
17
+ } from "./chunk-NTTFKS6T.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,