@xbrowser/cli 1.14.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.
@@ -29,7 +29,7 @@ var DEFAULT_STEALTH_CONFIG = {
29
29
  bezierCurvature: [0.35, 0.6],
30
30
  noiseAmplitude: 5.5,
31
31
  overshootRange: [6, 14],
32
- aimPause: [150, 400],
32
+ aimPause: [80, 280],
33
33
  pressDuration: [60, 140],
34
34
  releaseDrift: [0.8, 2.5],
35
35
  landingOffsetSmall: [0.3, 2.5],
@@ -50,6 +50,9 @@ var DEFAULT_STEALTH_CONFIG = {
50
50
  function rand(min, max) {
51
51
  return min + Math.random() * (max - min);
52
52
  }
53
+ function sleep(ms) {
54
+ return new Promise((resolve) => setTimeout(resolve, ms));
55
+ }
53
56
  function cosineEase(t) {
54
57
  return 0.5 - 0.5 * Math.cos(Math.PI * t);
55
58
  }
@@ -97,6 +100,18 @@ function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
97
100
  const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
98
101
  return { dx, dy };
99
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
+ }
100
115
  var KEY_MAP = {};
101
116
  for (let i = 97; i <= 122; i++) {
102
117
  const ch = String.fromCharCode(i);
@@ -120,6 +135,7 @@ Object.assign(KEY_MAP, {
120
135
  function buildStealthInitScript() {
121
136
  return [
122
137
  "(function(){",
138
+ ' window.__xbStealthVer="57";',
123
139
  // 1. AEL event proxy
124
140
  " var o=EventTarget.prototype.addEventListener;",
125
141
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -282,6 +298,70 @@ function buildStealthInitScript() {
282
298
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
283
299
  ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
284
300
  " }catch(e){}",
301
+ // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
302
+ // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
303
+ // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
304
+ // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
305
+ // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
306
+ // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
307
+ " try{",
308
+ " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
309
+ " var _gce=PointerEvent.prototype.getCoalescedEvents;",
310
+ " var _lm=null;",
311
+ " var _gceH=function(){",
312
+ " var list=_gce.call(this);",
313
+ ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
314
+ " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
315
+ " var out=null;",
316
+ " if((!list||list.length<2)&&_lm){",
317
+ " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
318
+ // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
319
+ // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
320
+ // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
321
+ " var expN=Math.floor(dt/8)-1;",
322
+ // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
323
+ // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
324
+ " if(expN>0&&dt>=12&&Math.random()>0.15){",
325
+ " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
326
+ " var span=Math.min(dt,n*8.3);",
327
+ " var frac=Math.min(1,span/Math.max(dt,1));",
328
+ " out=[];",
329
+ " for(var i=1;i<=n;i++){",
330
+ // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
331
+ " var k=n+1-i;",
332
+ ' var ev=new PointerEvent("pointermove",{',
333
+ ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
334
+ " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
335
+ " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
336
+ " screenX:this.screenX,screenY:this.screenY,",
337
+ " buttons:this.buttons,button:this.button,",
338
+ " bubbles:true,cancelable:true,composed:true,",
339
+ " width:this.width,height:this.height,pressure:this.pressure,",
340
+ " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
341
+ " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
342
+ // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
343
+ // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
344
+ // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
345
+ // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
346
+ // 合成事件同时间戳、群内间隔塌到 ~2ms)。
347
+ " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
348
+ ' if(p==="isTrusted")return true;',
349
+ ' if(p==="timeStamp")return t2;',
350
+ ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
351
+ " }});})(ev,tsV));",
352
+ " }",
353
+ " out.push(this);",
354
+ " }",
355
+ " }",
356
+ " _lm=cur;",
357
+ " return out||list;",
358
+ " };",
359
+ " PointerEvent.prototype.getCoalescedEvents=_gceH;",
360
+ // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
361
+ // "getCoalescedEvents")——toString 白名单管不到 name 属性。
362
+ ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
363
+ " }",
364
+ " }catch(e){}",
285
365
  // 3d. Chrome object depth (d24): automation fakes usually only set
286
366
  // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
287
367
  " try{",
@@ -316,6 +396,7 @@ function buildStealthInitScript() {
316
396
  ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
317
397
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
318
398
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
399
+ ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
319
400
  " return _ts.call(this);",
320
401
  " };",
321
402
  // 4. onclick prototype hijack (dual-stream consistency)
@@ -376,26 +457,26 @@ var XBMouseImpl = class {
376
457
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
377
458
  this._x = p.x;
378
459
  this._y = p.y;
379
- await sleep(p.delay);
460
+ await sleep2(p.delay);
380
461
  }
381
462
  this._x = tx;
382
463
  this._y = ty;
383
464
  if (_truncated) {
384
465
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: tx, y: ty, button: this._button });
385
466
  }
386
- await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
467
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
387
468
  } else {
388
469
  await this.move(tx, ty);
389
470
  }
390
471
  await this.down({ button });
391
- await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
472
+ await sleep2(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
392
473
  const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
393
474
  const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
394
475
  this._x = rx;
395
476
  this._y = ry;
396
477
  await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
397
478
  for (let i = 1; i < (opts.clickCount ?? 1); i++) {
398
- if (opts.delay) await sleep(opts.delay);
479
+ if (opts.delay) await sleep2(opts.delay);
399
480
  await this.down({ button });
400
481
  await this.up({ button });
401
482
  }
@@ -426,12 +507,14 @@ var XBMouseImpl = class {
426
507
  });
427
508
  }
428
509
  async move(x, y, opts = {}) {
510
+ const stealth = opts.stealth ?? process.env.XBROWSER_STEALTH !== "off";
429
511
  const steps = Math.max(1, opts.steps ?? 1);
430
512
  const fromX = this._x;
431
513
  const fromY = this._y;
432
514
  const dx = x - fromX;
433
515
  const dy = y - fromY;
434
516
  for (let i = 1; i <= steps; i++) {
517
+ if (stealth) await sleep2(rand(16, 28));
435
518
  const t = i / steps;
436
519
  this._x = fromX + dx * t;
437
520
  this._y = fromY + dy * t;
@@ -458,7 +541,7 @@ var XBMouseImpl = class {
458
541
  await this.conn.send(method, params, this.sessionId);
459
542
  }
460
543
  };
461
- function sleep(ms) {
544
+ function sleep2(ms) {
462
545
  return new Promise((resolve) => setTimeout(resolve, ms));
463
546
  }
464
547
 
@@ -488,17 +571,17 @@ var XBKeyboardImpl = class {
488
571
  await this.dispatchKeyEvent(downParams);
489
572
  if (mapping.text) {
490
573
  if (process.env.XBROWSER_STEALTH !== "off") {
491
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
574
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
492
575
  }
493
576
  await this.dispatchKeyEvent({
494
577
  type: "char",
495
578
  text: mapping.text
496
579
  });
497
580
  }
498
- if (delay > 0) await sleep2(delay);
581
+ if (delay > 0) await sleep3(delay);
499
582
  else {
500
583
  if (process.env.XBROWSER_STEALTH !== "off") {
501
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
584
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
502
585
  }
503
586
  }
504
587
  const upParams = {
@@ -544,45 +627,56 @@ var XBKeyboardImpl = class {
544
627
  const fixedDelay = opts.delay ?? 0;
545
628
  for (const char of text) {
546
629
  if (fixedDelay > 0) {
547
- await sleep2(fixedDelay);
630
+ await sleep3(fixedDelay);
548
631
  } else if (stealth) {
549
- const roll = Math.random();
550
- const cfg = DEFAULT_STEALTH_CONFIG.typingRhythm;
551
- if (roll < cfg.pauseProb) {
552
- await sleep2(rand(cfg.pauseRange[0], cfg.pauseRange[1]));
553
- } else if (roll < cfg.pauseProb + cfg.fastProb) {
554
- await sleep2(rand(cfg.fastRange[0], cfg.fastRange[1]));
555
- } else {
556
- await sleep2(rand(cfg.normalRange[0], cfg.normalRange[1]));
557
- }
558
- }
559
- const mapping = resolveKeyMapping(char);
560
- const downParams = {
561
- type: "rawKeyDown",
562
- key: mapping.key,
563
- code: mapping.code
564
- };
565
- if (mapping.text) {
566
- downParams.text = mapping.text;
567
- downParams.unmodifiedText = mapping.text;
568
- }
569
- if (mapping.keyCode) {
570
- downParams.windowsVirtualKeyCode = mapping.keyCode;
632
+ await sleep3(typingDelay());
571
633
  }
572
- await this.dispatchKeyEvent(downParams);
573
- if (mapping.text) {
574
- await this.dispatchKeyEvent({
575
- type: "char",
576
- text: mapping.text
577
- });
634
+ if (stealth && char.length === 1 && Math.random() < DEFAULT_STEALTH_CONFIG.typoProbability) {
635
+ const wrong = NEIGHBOR_KEYS[char];
636
+ if (wrong && wrong !== char) {
637
+ await this.dispatchKeySequence(resolveKeyMapping(wrong), stealth);
638
+ await sleep3(rand(120, 420));
639
+ await this.dispatchKeySequence(KEY_MAP2.Backspace, stealth);
640
+ }
578
641
  }
579
- await this.dispatchKeyEvent({
580
- type: "keyUp",
581
- key: mapping.key,
582
- code: mapping.code,
583
- ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
584
- });
642
+ await this.dispatchKeySequence(resolveKeyMapping(char), stealth);
643
+ }
644
+ }
645
+ /**
646
+ * Single-key event sequence: rawKeyDown (char) → keyUp.
647
+ * Shared by press() and type() — two separate implementations drifted
648
+ * apart once already (S60: type() lost the keyPressDuration that press()
649
+ * had; d50 caught it as 33/33 keys with down→up of 2ms).
650
+ */
651
+ async dispatchKeySequence(mapping, stealth) {
652
+ const downParams = {
653
+ type: "rawKeyDown",
654
+ key: mapping.key,
655
+ code: mapping.code
656
+ };
657
+ if (mapping.text) {
658
+ downParams.text = mapping.text;
659
+ downParams.unmodifiedText = mapping.text;
660
+ }
661
+ if (mapping.keyCode) {
662
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
663
+ }
664
+ await this.dispatchKeyEvent(downParams);
665
+ if (mapping.text) {
666
+ await this.dispatchKeyEvent({ type: "char", text: mapping.text });
667
+ }
668
+ if (stealth) {
669
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
670
+ }
671
+ const upParams = {
672
+ type: "keyUp",
673
+ key: mapping.key,
674
+ code: mapping.code
675
+ };
676
+ if (mapping.keyCode) {
677
+ upParams.windowsVirtualKeyCode = mapping.keyCode;
585
678
  }
679
+ await this.dispatchKeyEvent(upParams);
586
680
  }
587
681
  async insertText(text) {
588
682
  await this.conn.send(
@@ -591,6 +685,35 @@ var XBKeyboardImpl = class {
591
685
  this.sessionId
592
686
  );
593
687
  }
688
+ /**
689
+ * Navigation key press using CDP type 'keyDown' (NOT rawKeyDown).
690
+ * rawKeyDown skips browser default actions — select option navigation,
691
+ * arrow-key scrolling etc. never fire under it (d53: ArrowDown on a
692
+ * focused <select> left the value unchanged). keyDown carries the
693
+ * default action.
694
+ */
695
+ async pressNav(key) {
696
+ const mapping = resolveKeyMapping(key);
697
+ const downParams = {
698
+ type: "keyDown",
699
+ key: mapping.key,
700
+ code: mapping.code
701
+ };
702
+ if (mapping.keyCode) {
703
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
704
+ downParams.nativeVirtualKeyCode = mapping.keyCode;
705
+ }
706
+ await this.dispatchKeyEvent(downParams);
707
+ if (process.env.XBROWSER_STEALTH !== "off") {
708
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
709
+ }
710
+ await this.dispatchKeyEvent({
711
+ type: "keyUp",
712
+ key: mapping.key,
713
+ code: mapping.code,
714
+ ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
715
+ });
716
+ }
594
717
  async dispatchKeyEvent(params) {
595
718
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
596
719
  }
@@ -645,7 +768,44 @@ var KEY_MAP2 = {
645
768
  F11: { key: "F11", code: "F11", keyCode: 122 },
646
769
  F12: { key: "F12", code: "F12", keyCode: 123 }
647
770
  };
648
- function sleep2(ms) {
771
+ var NEIGHBOR_KEYS = {
772
+ a: "s",
773
+ b: "v",
774
+ c: "x",
775
+ d: "f",
776
+ e: "r",
777
+ f: "g",
778
+ g: "h",
779
+ h: "j",
780
+ i: "o",
781
+ j: "k",
782
+ k: "l",
783
+ l: "k",
784
+ m: "n",
785
+ n: "m",
786
+ o: "p",
787
+ p: "o",
788
+ q: "w",
789
+ r: "t",
790
+ s: "d",
791
+ t: "y",
792
+ u: "i",
793
+ v: "b",
794
+ w: "e",
795
+ x: "c",
796
+ y: "u",
797
+ z: "x",
798
+ "1": "2",
799
+ "2": "3",
800
+ "3": "4",
801
+ "4": "5",
802
+ "5": "6",
803
+ "6": "7",
804
+ "7": "8",
805
+ "8": "9",
806
+ "9": "0"
807
+ };
808
+ function sleep3(ms) {
649
809
  return new Promise((resolve) => setTimeout(resolve, ms));
650
810
  }
651
811
 
@@ -962,23 +1122,13 @@ var XBLocatorImpl = class _XBLocatorImpl {
962
1122
  async press(key, opts = {}) {
963
1123
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
964
1124
  await scrollIntoView(this.page, this.selector);
965
- await this.page.evaluate(`
966
- (function() {
967
- const el = ${this._q(this.selector)};
968
- if (el) el.focus();
969
- })()
970
- `);
1125
+ await this.click({ timeout: opts.timeout });
971
1126
  await this.page.keyboard.press(key);
972
1127
  }
973
1128
  async pressSequentially(text, opts = {}) {
974
1129
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
975
1130
  await scrollIntoView(this.page, this.selector);
976
- await this.page.evaluate(`
977
- (function() {
978
- const el = ${this._q(this.selector)};
979
- if (el) el.focus();
980
- })()
981
- `);
1131
+ await this.click({ timeout: opts.timeout });
982
1132
  await this.page.keyboard.type(text, { delay: opts.delay });
983
1133
  }
984
1134
  async hover(opts = {}) {
@@ -1033,11 +1183,36 @@ var XBLocatorImpl = class _XBLocatorImpl {
1033
1183
  async selectOption(value) {
1034
1184
  await waitForActionable(this.page, this.selector);
1035
1185
  const values = Array.isArray(value) ? value : [value];
1036
- const selected = await this.page.evaluate(`
1186
+ const info = await this.page.evaluate(`
1037
1187
  (function() {
1038
1188
  const el = ${this._q(this.selector)};
1039
1189
  if (!el || el.tagName !== 'SELECT') throw new Error('Not a select element');
1040
-
1190
+ const values = ${JSON.stringify(values)};
1191
+ let target = -1, targetValue = '';
1192
+ outer:
1193
+ for (let i = 0; i < el.options.length; i++) {
1194
+ const opt = el.options[i];
1195
+ for (const v of values) {
1196
+ const hit = typeof v === 'object'
1197
+ ? (v.label !== undefined ? opt.label === v.label
1198
+ : v.value !== undefined ? opt.value === v.value
1199
+ : opt.index === v.index)
1200
+ : (opt.value === v || opt.label === v);
1201
+ if (hit) { target = i; targetValue = opt.value; break outer; }
1202
+ }
1203
+ }
1204
+ return { cur: el.selectedIndex, target: target, targetValue: targetValue, multiple: el.multiple };
1205
+ })()
1206
+ `);
1207
+ if (info.target < 0) {
1208
+ throw new Error(`Option not found: ${JSON.stringify(values)}`);
1209
+ }
1210
+ if (!info.multiple) {
1211
+ await this.click({ timeout: 5e3 });
1212
+ }
1213
+ const selected = await this.page.evaluate(`
1214
+ (function() {
1215
+ const el = ${this._q(this.selector)};
1041
1216
  const values = ${JSON.stringify(values)};
1042
1217
  const selectedValues = [];
1043
1218
 
@@ -5830,6 +6005,9 @@ async function ensureProcessCanExit() {
5830
6005
  }
5831
6006
 
5832
6007
  export {
6008
+ rand,
6009
+ sleep,
6010
+ wheelDelta,
5833
6011
  createRuleEngine,
5834
6012
  touchSession,
5835
6013
  findTargetPage,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-PYQGDBAF.js";
3
+ } from "./chunk-NTTFKS6T.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";