@xbrowser/cli 1.19.0 → 1.21.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.
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-GDKLH7ZY.js";
8
8
  import {
9
9
  createRuleEngine
10
- } from "./chunk-H2A5JUK5.js";
10
+ } from "./chunk-75DNUI6M.js";
11
11
  import {
12
12
  __require
13
13
  } from "./chunk-KFQGP6VL.js";
@@ -23,6 +23,25 @@ import { EventEmitter } from "events";
23
23
 
24
24
  // src/cdp-driver/stealth.ts
25
25
  var DEFAULT_STEALTH_CONFIG = {
26
+ // S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
27
+ uaChProfile: {
28
+ brands: [
29
+ { brand: "Not=A?Brand", version: "99" },
30
+ { brand: "Google Chrome", version: "151" },
31
+ { brand: "Chromium", version: "151" }
32
+ ],
33
+ platform: "macOS",
34
+ platformVersion: "26.2.0",
35
+ architecture: "arm",
36
+ bitness: "64",
37
+ model: "",
38
+ uaFullVersion: "151.0.7922.175",
39
+ fullVersionList: [
40
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
41
+ { brand: "Google Chrome", version: "151.0.7922.175" },
42
+ { brand: "Chromium", version: "151.0.7922.175" }
43
+ ]
44
+ },
26
45
  bezierCurvature: [0.35, 0.6],
27
46
  noiseAmplitude: 5.5,
28
47
  overshootRange: [6, 14],
@@ -129,10 +148,13 @@ Object.assign(KEY_MAP, {
129
148
  "@": { key: "@", code: "Digit2", vk: 50, shift: true },
130
149
  "_": { key: "_", code: "Minus", vk: 189, shift: true }
131
150
  });
132
- function buildStealthInitScript() {
151
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
152
+ const prof = config.uaChProfile;
153
+ const profJson = prof ? JSON.stringify(prof) : "null";
133
154
  return [
134
155
  "(function(){",
135
156
  ' window.__xbStealthVer="57";',
157
+ " window.__xbUaChProf=" + profJson + ";",
136
158
  // 1. AEL event proxy
137
159
  " var o=EventTarget.prototype.addEventListener;",
138
160
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -195,9 +217,65 @@ function buildStealthInitScript() {
195
217
  ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
196
218
  ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
197
219
  ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
220
+ // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
221
+ // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
222
+ // 原型+实例双层覆写(S172 模式)。
223
+ " try{",
224
+ ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
225
+ ' 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;});};',
226
+ ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
227
+ " var wrapUAD=function(uad){",
228
+ " if(!uad||uad.__xbUA)return uad;",
229
+ " try{",
230
+ ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
231
+ " var _ghev=uad.getHighEntropyValues.bind(uad);",
232
+ " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
233
+ ' 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]);}',
234
+ " return o;});};",
235
+ " uad.__xbUA=true;",
236
+ " }catch(e){}",
237
+ " return uad;",
238
+ " };",
239
+ ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
240
+ " if(_nua&&_nua.get){",
241
+ " var _nuag=function(){return fixUA(_nua.get.call(this));};",
242
+ ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
243
+ ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
244
+ " }",
245
+ ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
246
+ " if(_nuad&&_nuad.get){",
247
+ " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
248
+ ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
249
+ ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
250
+ " }",
251
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
252
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
253
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
254
+ " var prof = window.__xbUaChProf;",
255
+ " var uad={",
256
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
257
+ " mobile:false,",
258
+ " platform:prof.platform,",
259
+ " getHighEntropyValues:function(hints){",
260
+ " return Promise.resolve().then(function(){",
261
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
262
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
263
+ " uaFullVersion:prof.uaFullVersion,",
264
+ " fullVersionList:prof.fullVersionList};",
265
+ " var out={};",
266
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
267
+ " return out;",
268
+ " });",
269
+ " },",
270
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
271
+ " };",
272
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
273
+ " }",
274
+ " }catch(e){}",
198
275
  // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
199
276
  // —— 真实浏览器失焦/后台时 hasFocus=false
200
- ' document.hasFocus=function(){return document.visibilityState==="visible";};',
277
+ // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
278
+ ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
201
279
  // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
202
280
  // Headless software raster differs subtly from Chrome GPU raster —
203
281
  // toDataURL hashes fingerprint the rasterizer. Inject a stable
@@ -260,26 +338,25 @@ function buildStealthInitScript() {
260
338
  " }catch(e){}",
261
339
  // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
262
340
  " try{",
263
- " var _mt=CanvasRenderingContext2D.prototype.measureText;",
264
341
  ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
265
- " CanvasRenderingContext2D.prototype.measureText=function(t){",
266
- " var m=_mt.call(this,t);",
267
- ' try{Object.defineProperty(m,"width",{get:function(){return _tmw.get.call(m)+_dx*0.01;},configurable:true});}catch(e){}',
268
- " return m;",
269
- " };",
342
+ " if(_tmw&&_tmw.get){",
343
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
344
+ ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
345
+ " }",
270
346
  " }catch(e){}",
271
347
  " try{",
272
348
  ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
273
- " var _sv=speechSynthesis.getVoices.bind(speechSynthesis);",
274
- " speechSynthesis.getVoices=function(){",
275
- " var real=_sv();",
349
+ // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
350
+ " var _sv=SpeechSynthesis.prototype.getVoices;",
351
+ " SpeechSynthesis.prototype.getVoices=function(){",
352
+ " var real=_sv.call(this);",
276
353
  " if(real&&real.length>50)return real;",
277
354
  // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
278
355
  // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
279
356
  ' 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};});',
280
357
  " };",
281
358
  // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
282
- // 轮询同源 iframe,对其 contentWindow.speechSynthesis 同样 patch
359
+ // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
283
360
  " try{",
284
361
  " var _piv=function(){",
285
362
  ' var frs=document.querySelectorAll("iframe");',
@@ -287,8 +364,8 @@ function buildStealthInitScript() {
287
364
  " var cw=frs[i].contentWindow;",
288
365
  " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
289
366
  " cw.speechSynthesis.__xbP=true;",
290
- " var _sv2=cw.speechSynthesis.getVoices.bind(cw.speechSynthesis);",
291
- " cw.speechSynthesis.getVoices=function(){var r=_sv2();if(r&&r.length>50)return r;",
367
+ " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
368
+ " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
292
369
  ' 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};});};',
293
370
  " }catch(e2){}}",
294
371
  " };",
@@ -323,10 +400,21 @@ function buildStealthInitScript() {
323
400
  // 3d-2. performance.now precision clamp (d34): full-precision timestamps
324
401
  // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
325
402
  " try{",
326
- " var _pn=performance.now.bind(performance);",
327
- " performance.now=function(){return Math.round(_pn()*10)/10;};",
403
+ " var _pn=Performance.prototype.now;",
404
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
405
+ " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
328
406
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
329
- ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
407
+ " if(_pto&&_pto.get){",
408
+ " // 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",
409
+ " // \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",
410
+ " // \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",
411
+ " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
412
+ " var _tog=function(){return _pto.get.call(performance)+_toff;};",
413
+ ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
414
+ " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
415
+ ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
416
+ ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
417
+ " }",
330
418
  " }catch(e){}",
331
419
  // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
332
420
  // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
@@ -440,6 +528,9 @@ function buildStealthInitScript() {
440
528
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
441
529
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
442
530
  ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
531
+ ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
532
+ ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
533
+ ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
443
534
  " return _ts.call(this);",
444
535
  " };",
445
536
  // 4. onclick prototype hijack (dual-stream consistency)
@@ -1994,7 +2085,7 @@ var XBPageImpl = class _XBPageImpl {
1994
2085
  try {
1995
2086
  await this.conn.send(
1996
2087
  "Page.addScriptToEvaluateOnNewDocument",
1997
- { source: buildStealthInitScript() },
2088
+ { source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
1998
2089
  this.sessionId
1999
2090
  );
2000
2091
  } catch {
@@ -3251,6 +3342,10 @@ var XBContextImpl = class {
3251
3342
  this.options = opts;
3252
3343
  this.setupAutoAttach();
3253
3344
  }
3345
+ /** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
3346
+ get stealthConfig() {
3347
+ return this.options.stealthConfig;
3348
+ }
3254
3349
  async newPage() {
3255
3350
  if (this.closed) throw new Error("Context is closed");
3256
3351
  const { targetId } = await this._browser._createTarget(this.contextId);
@@ -3417,20 +3512,8 @@ var XBContextImpl = class {
3417
3512
 
3418
3513
  // src/cdp-driver/browser.ts
3419
3514
  var XBBrowserImpl = class {
3420
- conn;
3421
- _emitter = new EventEmitter3();
3422
- _contexts = /* @__PURE__ */ new Map();
3423
- _disconnected = false;
3424
- childProcess = null;
3425
- tmpDir;
3426
- _exitHandler = null;
3427
- /**
3428
- * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3429
- * Used by discoverContexts() as a fallback to HTTP /json/list when
3430
- * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3431
- */
3432
- cdpEndpoint;
3433
- constructor(conn, childProcess, tmpDir, cdpEndpoint) {
3515
+ constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
3516
+ this.launchOpts = launchOpts;
3434
3517
  this.conn = conn;
3435
3518
  this.childProcess = childProcess ?? null;
3436
3519
  this.tmpDir = tmpDir;
@@ -3458,6 +3541,20 @@ var XBBrowserImpl = class {
3458
3541
  process.on("exit", this._exitHandler);
3459
3542
  }
3460
3543
  }
3544
+ launchOpts;
3545
+ conn;
3546
+ _emitter = new EventEmitter3();
3547
+ _contexts = /* @__PURE__ */ new Map();
3548
+ _disconnected = false;
3549
+ childProcess = null;
3550
+ tmpDir;
3551
+ _exitHandler = null;
3552
+ /**
3553
+ * Original CDP endpoint (HTTP or ws URL) used to construct this browser.
3554
+ * Used by discoverContexts() as a fallback to HTTP /json/list when
3555
+ * Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
3556
+ */
3557
+ cdpEndpoint;
3461
3558
  get disconnected() {
3462
3559
  return this._disconnected;
3463
3560
  }
@@ -3485,6 +3582,9 @@ var XBBrowserImpl = class {
3485
3582
  this._emitter.emit("disconnected");
3486
3583
  }
3487
3584
  async newContext(opts = {}) {
3585
+ if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
3586
+ opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
3587
+ }
3488
3588
  if (this._disconnected) {
3489
3589
  throw new Error("Browser is disconnected");
3490
3590
  }
@@ -4038,7 +4138,13 @@ async function launch(options = {}) {
4038
4138
  const conn = new CDPConnection(wsEndpoint);
4039
4139
  await conn.ready();
4040
4140
  const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
4041
- const browser = new XBBrowserImpl(conn, childProcess, tmpDir, httpEndpoint);
4141
+ const browser = new XBBrowserImpl(
4142
+ conn,
4143
+ childProcess,
4144
+ tmpDir,
4145
+ httpEndpoint,
4146
+ options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
4147
+ );
4042
4148
  return { browser, wsEndpoint };
4043
4149
  }
4044
4150
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuleEngine
3
- } from "./chunk-H2A5JUK5.js";
3
+ } from "./chunk-75DNUI6M.js";
4
4
 
5
5
  // src/cdp-interceptor/proxy.ts
6
6
  import { WebSocketServer, WebSocket } from "ws";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-LKJZYVIX.js";
3
+ } from "./chunk-HWWG2ISJ.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
7
7
  import {
8
8
  CDPInterceptorProxy
9
- } from "./chunk-A6LPGFAL.js";
9
+ } from "./chunk-WYETQ5C7.js";
10
10
 
11
11
  // src/browser.ts
12
12
  import { randomUUID } from "crypto";
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ import {
72
72
  setActivePage,
73
73
  sleep,
74
74
  wheelDelta
75
- } from "./chunk-KLXLNQI5.js";
75
+ } from "./chunk-ESJYANQO.js";
76
76
  import "./chunk-3KZ34AUC.js";
77
77
  import {
78
78
  errMsg
@@ -8248,7 +8248,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8248
8248
  }
8249
8249
  let targetPageOverride = null;
8250
8250
  if (_target && extraOpts?.cdpEndpoint) {
8251
- const { findTargetPage } = await import("./browser-6LH6KJSW.js");
8251
+ const { findTargetPage } = await import("./browser-CNBSH53B.js");
8252
8252
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8253
8253
  if (!targetPageOverride) {
8254
8254
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -14433,7 +14433,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14433
14433
  const targetPage = pages[cmdTabIndex];
14434
14434
  await targetPage.bringToFront().catch(() => {
14435
14435
  });
14436
- const { setActivePage: setActivePage2 } = await import("./browser-6LH6KJSW.js");
14436
+ const { setActivePage: setActivePage2 } = await import("./browser-CNBSH53B.js");
14437
14437
  setActivePage2(session, targetPage);
14438
14438
  }
14439
14439
  }
@@ -14709,7 +14709,7 @@ async function main() {
14709
14709
  const command = process.argv[2];
14710
14710
  const isLongRunning = command === "preview" || command === "serve";
14711
14711
  if (!isLongRunning) {
14712
- const { ensureProcessCanExit } = await import("./browser-6LH6KJSW.js");
14712
+ const { ensureProcessCanExit } = await import("./browser-CNBSH53B.js");
14713
14713
  await ensureProcessCanExit().catch(() => {
14714
14714
  });
14715
14715
  process.exit(process.exitCode || exitCode);
@@ -34,13 +34,13 @@ import {
34
34
  resolveLaunchOpts,
35
35
  saveSessionDiskMeta,
36
36
  setActivePage
37
- } from "./chunk-C6PW3CPA.js";
37
+ } from "./chunk-AVSTNODR.js";
38
38
  import {
39
39
  createRuleEngine,
40
40
  rand,
41
41
  sleep,
42
42
  wheelDelta
43
- } from "./chunk-GKYSCHT4.js";
43
+ } from "./chunk-CLYZPSDR.js";
44
44
  import {
45
45
  queryJS
46
46
  } from "./chunk-3FWLW7FS.js";
@@ -7766,7 +7766,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7766
7766
  }
7767
7767
  let targetPageOverride = null;
7768
7768
  if (_target && extraOpts?.cdpEndpoint) {
7769
- const { findTargetPage } = await import("./browser-Y5SIE6LY.js");
7769
+ const { findTargetPage } = await import("./browser-C2VEOJC2.js");
7770
7770
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7771
7771
  if (!targetPageOverride) {
7772
7772
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9588,7 +9588,7 @@ function createRPCHandler() {
9588
9588
  if (isNewFormat) {
9589
9589
  try {
9590
9590
  const replayErrors = [];
9591
- const { SessionReplayer } = await import("./session-replayer-RODZYHM3.js");
9591
+ const { SessionReplayer } = await import("./session-replayer-EVD4HZBB.js");
9592
9592
  const replayer = new SessionReplayer({
9593
9593
  page: session.page,
9594
9594
  stepDelay: slowMo * 500,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,103 @@ import { ZodType, ZodTypeDef, z } from 'zod';
6
6
 
7
7
  declare const version: any;
8
8
 
9
+ /**
10
+ * stealth.ts — Behavioral stealth layer for CDP driver
11
+ *
12
+ * Enhances mouse/keyboard with human-like patterns:
13
+ * • Bezier trajectory with cosine easing + noise + overshoot correction
14
+ * • Landing offset from element center (no dead-center clicks)
15
+ * • Press/release drift (finger micro-movement)
16
+ * • Three-tier typing rhythm with random pauses
17
+ * • Typo + backspace correction
18
+ * • Wheel exponential decay (momentum)
19
+ *
20
+ * Also provides init script for page-level stealth hooks:
21
+ *
22
+ * ── 维护红线(S183 站点观测实证)────────────────────────────
23
+ * 1. 永不覆写 navigator.webdriver —— 原生 false 即最佳伪装。
24
+ * 掘金等站点以 ~5s/次轮询 Navigator.prototype.webdriver 的
25
+ * descriptor(getter native + 实例无覆写 = 正常),任何覆写
26
+ * 都会在 descriptor 检测下露馅。
27
+ * 2. 实例覆写必须原型层同步(S172)—— 实例 defineProperty 可被
28
+ * X.prototype.p.call(target) 一行逃逸,见 stealth-audit.test.ts。
29
+ * 3. 覆写函数必须进 toString native 伪装名单(S172b 审计规则 R2)。
30
+ * ────────────────────────────────────────────────────────
31
+ * Also provides init script for page-level stealth hooks:
32
+ * • AEL event proxy (sourceCapabilities / isTrusted / coordinate floatification)
33
+ * • onclick prototype hijack (dual-stream consistency)
34
+ * • Screen/hasFocus override + toString disguise
35
+ *
36
+ * 124 rounds of attack-defense validated (output/cdp-duel-s5/)
37
+ */
38
+
39
+ interface UaChProfile {
40
+ /** GREASE brands 形状,例:Not=A?Brand:99(从真实环境采样) */
41
+ brands: Array<{
42
+ brand: string;
43
+ version: string;
44
+ }>;
45
+ /** 平台(macOS / Windows / Linux) */
46
+ platform: string;
47
+ /** 平台版本(高熵) */
48
+ platformVersion: string;
49
+ /** 架构(arm / x86) */
50
+ architecture: string;
51
+ /** 位宽(64 / 32) */
52
+ bitness: string;
53
+ /** 设备型号(桌面端通常空) */
54
+ model: string;
55
+ /** 完整版本号(uaFullVersion,例:151.0.7922.175) */
56
+ uaFullVersion: string;
57
+ /** 完整版本列表(fullVersionList,与 brands 同形状+全版本) */
58
+ fullVersionList: Array<{
59
+ brand: string;
60
+ version: string;
61
+ }>;
62
+ }
63
+ interface StealthConfig {
64
+ /**
65
+ * S191: UA-CH 真实档案——垫片在 navigator.userAgentData 缺失时
66
+ * 以此生成 API 表面(值来自真实有头 Chrome 采样,形状一致)。
67
+ * 未提供时从 UA 字符串派生兜底值。
68
+ */
69
+ uaChProfile?: UaChProfile;
70
+ /** Bezier curvature range [min, max] as fraction of distance */
71
+ bezierCurvature: [number, number];
72
+ /** Trajectory noise amplitude ±px */
73
+ noiseAmplitude: number;
74
+ /** Overshoot distance range [min, max] px */
75
+ overshootRange: [number, number];
76
+ /** Aim pause before click [min, max] ms */
77
+ aimPause: [number, number];
78
+ /** Press duration [min, max] ms */
79
+ pressDuration: [number, number];
80
+ /** Release coordinate drift [min, max] px */
81
+ releaseDrift: [number, number];
82
+ /** Landing offset for small elements [min, max] px */
83
+ landingOffsetSmall: [number, number];
84
+ /** Landing offset for large elements [min, max] px */
85
+ landingOffsetLarge: [number, number];
86
+ /** Elements smaller than this (px) use small offset */
87
+ smallElementThreshold: number;
88
+ /** Typing rhythm probabilities */
89
+ typingRhythm: {
90
+ fastProb: number;
91
+ fastRange: [number, number];
92
+ normalRange: [number, number];
93
+ pauseProb: number;
94
+ pauseRange: [number, number];
95
+ };
96
+ /** Key press duration [min, max] ms */
97
+ keyPressDuration: [number, number];
98
+ /** Typo probability per field (0-1) */
99
+ typoProbability: number;
100
+ /** Wheel peak delta */
101
+ wheelPeak: number;
102
+ /** Wheel decay rate */
103
+ wheelDecayRate: number;
104
+ }
105
+
9
106
  /**
10
107
  * XBrowser CDP Driver — Abstract Interface Types
11
108
  *
@@ -38,6 +135,8 @@ interface XBContextOptions {
38
135
  userAgent?: string;
39
136
  extraHTTPHeaders?: Record<string, string>;
40
137
  ignoreHTTPSErrors?: boolean;
138
+ /** S194: stealth 伪装配置(UA-CH 档案等)——经 context 传至 page 的注入层 */
139
+ stealthConfig?: Partial<StealthConfig>;
41
140
  }
42
141
  interface XBContext {
43
142
  newPage(): Promise<XBPage>;
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  resolveLaunchOpts,
19
19
  saveSessionDiskMeta,
20
20
  setActivePage
21
- } from "./chunk-2BPZ25RE.js";
21
+ } from "./chunk-Z5RTK4GW.js";
22
22
  import {
23
23
  detectAntiBot,
24
24
  formatDetectionMessage
@@ -86,7 +86,7 @@ import {
86
86
  rand,
87
87
  sleep,
88
88
  wheelDelta
89
- } from "./chunk-LKJZYVIX.js";
89
+ } from "./chunk-HWWG2ISJ.js";
90
90
  import "./chunk-3KZ34AUC.js";
91
91
  import {
92
92
  errMsg
@@ -94,7 +94,7 @@ import {
94
94
  import {
95
95
  CDPInterceptorProxy,
96
96
  advise
97
- } from "./chunk-A6LPGFAL.js";
97
+ } from "./chunk-WYETQ5C7.js";
98
98
  import {
99
99
  automationSignalsRule,
100
100
  createRuleEngine,
@@ -106,7 +106,7 @@ import {
106
106
  mouseTrajectoryRule,
107
107
  networkAnomalyRule,
108
108
  pageLifecycleRule
109
- } from "./chunk-H2A5JUK5.js";
109
+ } from "./chunk-75DNUI6M.js";
110
110
  import {
111
111
  SessionRecorder
112
112
  } from "./chunk-NODRQGOK.js";
@@ -8599,7 +8599,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8599
8599
  }
8600
8600
  let targetPageOverride = null;
8601
8601
  if (_target && extraOpts?.cdpEndpoint) {
8602
- const { findTargetPage } = await import("./browser-TUD2KA5G.js");
8602
+ const { findTargetPage } = await import("./browser-VZOOFLAH.js");
8603
8603
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8604
8604
  if (!targetPageOverride) {
8605
8605
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -14822,7 +14822,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14822
14822
  const targetPage = pages[cmdTabIndex];
14823
14823
  await targetPage.bringToFront().catch(() => {
14824
14824
  });
14825
- const { setActivePage: setActivePage2 } = await import("./browser-TUD2KA5G.js");
14825
+ const { setActivePage: setActivePage2 } = await import("./browser-VZOOFLAH.js");
14826
14826
  setActivePage2(session, targetPage);
14827
14827
  }
14828
14828
  }
@@ -17995,7 +17995,7 @@ var DataCollector = class {
17995
17995
  return results;
17996
17996
  }
17997
17997
  async createBrowserContext() {
17998
- const { launch } = await import("./cdp-driver-2EECZW4X.js");
17998
+ const { launch } = await import("./cdp-driver-26LGCHXV.js");
17999
17999
  const { browser } = await launch({
18000
18000
  headless: true,
18001
18001
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -18035,7 +18035,7 @@ var DataCollector = class {
18035
18035
 
18036
18036
  // src/cdp-interceptor/index.ts
18037
18037
  async function createCDPInterceptor(config) {
18038
- const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-LUR4U5YF.js");
18038
+ const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-NKCYE76M.js");
18039
18039
  const proxy = new CDPInterceptorProxy2(config);
18040
18040
  await proxy.start();
18041
18041
  return proxy;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CDPInterceptorProxy
3
- } from "./chunk-A6LPGFAL.js";
4
- import "./chunk-H2A5JUK5.js";
3
+ } from "./chunk-WYETQ5C7.js";
4
+ import "./chunk-75DNUI6M.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
7
7
  CDPInterceptorProxy
@@ -34,7 +34,7 @@ var SessionReplayer = class {
34
34
  if (this.opts.page) {
35
35
  this.page = this.opts.page;
36
36
  } else if (this.opts.cdpUrl) {
37
- const { launch } = await import("./cdp-driver-7HO5SRNN.js");
37
+ const { launch } = await import("./cdp-driver-KFX4A6WR.js");
38
38
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
39
39
  let contexts = browser.contexts();
40
40
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -75,6 +75,7 @@
75
75
  "lint": "eslint src/ bin/ --ext .ts",
76
76
  "lint:plugin-contract": "node lint-scripts/check-plugin-contract.mjs && node lint-scripts/check-plugin-status.mjs",
77
77
  "lint:plugin-status": "node lint-scripts/check-plugin-status.mjs --update",
78
+ "stealth:check": "vitest run tests/cdp-driver/stealth-health.test.ts tests/cdp-driver/stealth-audit.test.ts tests/cdp-driver/stealth-probe.test.ts",
78
79
  "lint:docs": "node lint-scripts/check-docs-consistency.mjs",
79
80
  "typecheck": "tsc --noEmit",
80
81
  "start": "node dist/cli.js",