@xbrowser/cli 1.11.0 → 1.12.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/dist/{browser-V3JIWSTR.js → browser-3HQKDZQ6.js} +4 -3
  2. package/dist/{browser-Q5APBNF6.js → browser-ILXTEWH4.js} +1 -1
  3. package/dist/{browser-XNU7JQYC.js → browser-XPDMY2NX.js} +2 -2
  4. package/dist/{cdp-driver-VEK6BNN6.js → cdp-driver-ESH3PDE6.js} +1 -1
  5. package/dist/{cdp-driver-XFTTZI5O.js → cdp-driver-N2E2ZNSC.js} +1142 -12
  6. package/dist/{cdp-driver-GD6YBBGE.js → cdp-driver-YIKR4BUX.js} +2 -1
  7. package/dist/chunk-A6LPGFAL.js +437 -0
  8. package/dist/{chunk-7OFM755Z.js → chunk-DKM2JOZL.js} +202 -12
  9. package/dist/{chunk-ABXMBNQ6.js → chunk-H2A5JUK5.js} +37 -466
  10. package/dist/{chunk-BNO7OKO4.js → chunk-HBMEFSTB.js} +39 -0
  11. package/dist/chunk-KJTABK3Z.js +1255 -0
  12. package/dist/{chunk-DWDXEGVK.js → chunk-NKW4A74J.js} +9 -3
  13. package/dist/{chunk-SQHSZENE.js → chunk-OMU63E6J.js} +3 -22
  14. package/dist/{chunk-JEDP4PJW.js → chunk-OVW2UEWN.js} +878 -687
  15. package/dist/{chunk-XKQVUFUS.js → chunk-R6IJ6PIV.js} +2 -2
  16. package/dist/{chunk-WHPBNUKB.js → chunk-SH6OTPXN.js} +42 -46
  17. package/dist/{chunk-MWFHZUIY.js → chunk-TEXCXIBW.js} +1 -1
  18. package/dist/{chunk-XQ4HRPDJ.js → chunk-Z6CUR3VG.js} +1143 -12
  19. package/dist/{chunk-IIM5GOD7.js → chunk-ZTHE5RBZ.js} +7 -1
  20. package/dist/cli.js +1075 -569
  21. package/dist/{daemon-client-MMDRYCF5.js → daemon-client-7D6EZNOE.js} +42 -46
  22. package/dist/{daemon-client-Y2YSZCGK.js → daemon-client-HEGAXWGK.js} +1 -1
  23. package/dist/daemon-main.js +950 -499
  24. package/dist/{human-interaction-5AO42MBA.js → human-interaction-4YR2N6R6.js} +2 -2
  25. package/dist/{human-interaction-C5OEGXGO.js → human-interaction-Y5IDH6LD.js} +1 -1
  26. package/dist/{human-interaction-ISXZTAYY.js → human-interaction-ZUTR5AC2.js} +2 -2
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +1086 -578
  29. package/dist/{proxy-C6CK3UH5.js → proxy-LUR4U5YF.js} +2 -1
  30. package/dist/{recovery-J2ISVGUL.js → recovery-FTZGV6VY.js} +2 -2
  31. package/dist/{recovery-ZJVVHP7N.js → recovery-L5GLDX4O.js} +1 -1
  32. package/dist/{recovery-EF33LKRJ.js → recovery-Z3RDZENA.js} +2 -2
  33. package/dist/{session-replayer-WIUYVN5J.js → session-replayer-K4A6OI4M.js} +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-HVPUVVSA.js +0 -2194
@@ -5,6 +5,9 @@ import {
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
8
+ import {
9
+ createRuleEngine
10
+ } from "./chunk-H2A5JUK5.js";
8
11
  import {
9
12
  __require
10
13
  } from "./chunk-KFQGP6VL.js";
@@ -124,8 +127,8 @@ function buildStealthInitScript() {
124
127
  " if(!e||e.constructor===FocusEvent||e.constructor===KeyboardEvent)return f.call(this,e);",
125
128
  " return f.call(this,new Proxy(e,{get:function(k,p){",
126
129
  ' if(p==="sourceCapabilities")return fc;',
127
- ' if(p==="isTrusted")return true;',
128
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
130
+ ' if(p==="isTrusted")return k.isTrusted;',
131
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
129
132
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
130
133
  " return k[p]+_f;",
131
134
  " }",
@@ -144,6 +147,126 @@ function buildStealthInitScript() {
144
147
  ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
145
148
  ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
146
149
  " document.hasFocus=function(){return true};",
150
+ // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
151
+ // Headless software raster differs subtly from Chrome GPU raster —
152
+ // toDataURL hashes fingerprint the rasterizer. Inject a stable
153
+ // (per-page-load) subpixel shift into fillText so hashes look like a
154
+ // distinct-but-consistent real GPU, and hide the HEADLESS tell in
155
+ // WebGL renderer strings.
156
+ " var _seed=Math.floor(Math.random()*2147483647);",
157
+ " var _prng=function(){_seed=(_seed*48271)%2147483647;return _seed/2147483647;};",
158
+ " var _dx=(_prng()*0.4-0.2).toFixed(3)*1, _dy=(_prng()*0.4-0.2).toFixed(3)*1;",
159
+ " var _fillText=CanvasRenderingContext2D.prototype.fillText;",
160
+ " CanvasRenderingContext2D.prototype.fillText=function(t,x,y,m){",
161
+ " return _fillText.call(this,t,x+_dx,y+_dy,m);",
162
+ " };",
163
+ " var _toDataURL=HTMLCanvasElement.prototype.toDataURL;",
164
+ " HTMLCanvasElement.prototype.toDataURL=function(){",
165
+ ' var ctx=this.getContext("2d");',
166
+ " if(ctx){var d=ctx.getImageData(0,0,Math.min(this.width,2),Math.min(this.height,2));",
167
+ " for(var i=0;i<d.data.length;i+=4){if(d.data[i+3]>0){d.data[i]^=1;break;}}",
168
+ " ctx.putImageData(d,0,0);}",
169
+ " return _toDataURL.apply(this,arguments);",
170
+ " };",
171
+ " try{",
172
+ " var _gl=HTMLCanvasElement.prototype.getContext;",
173
+ " HTMLCanvasElement.prototype.getContext=function(t,o){",
174
+ " var c=_gl.call(this,t,o);",
175
+ ' if(c&&(t==="webgl"||t==="experimental-webgl")&&c.getParameter){',
176
+ " var _gp=c.getParameter.bind(c);",
177
+ " c.getParameter=function(p){",
178
+ " var v=_gp(p);",
179
+ ' if(typeof v==="string"&&/SwiftShader|Software|Rasterizer|Headless/i.test(v))',
180
+ ' return "ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Max, Unspecified Version)";',
181
+ " return v;};}",
182
+ " return c;};",
183
+ " }catch(e){}",
184
+ // 5. AudioContext fingerprint: per-load stable micro-noise on channel
185
+ // data (d21). DSP sum differences between headless software audio and
186
+ // real hardware audio are a classic fingerprint — add ±1e-7 level
187
+ // noise (inaudible, changes the sum hash).
188
+ " try{",
189
+ " var _gcd=AudioBuffer.prototype.getChannelData;",
190
+ " AudioBuffer.prototype.getChannelData=function(ch){",
191
+ " var d=_gcd.call(this,ch);",
192
+ ' var key="__xb_audio_"+ch;',
193
+ " if(!this[key]){",
194
+ " this[key]=true;",
195
+ " for(var i=0;i<d.length;i+=997){d[i]=d[i]+(_prng()-0.5)*2e-7;}",
196
+ " }",
197
+ " return d;",
198
+ " };",
199
+ " var _gffd=AnalyserNode.prototype.getFloatFrequencyData;",
200
+ " AnalyserNode.prototype.getFloatFrequencyData=function(arr){",
201
+ " _gffd.call(this,arr);",
202
+ " for(var i=0;i<arr.length;i+=31){arr[i]=arr[i]+(_prng()-0.5)*0.01;}",
203
+ " };",
204
+ " var _gfbd=AnalyserNode.prototype.getByteFrequencyData;",
205
+ " AnalyserNode.prototype.getByteFrequencyData=function(arr){",
206
+ " _gfbd.call(this,arr);",
207
+ " for(var i=0;i<arr.length;i+=31){arr[i]=(arr[i]+((_prng()*3)|0))&255;}",
208
+ " };",
209
+ " }catch(e){}",
210
+ // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
211
+ " try{",
212
+ " var _mt=CanvasRenderingContext2D.prototype.measureText;",
213
+ ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
214
+ " CanvasRenderingContext2D.prototype.measureText=function(t){",
215
+ " var m=_mt.call(this,t);",
216
+ ' try{Object.defineProperty(m,"width",{get:function(){return _tmw.get.call(m)+_dx*0.01;},configurable:true});}catch(e){}',
217
+ " return m;",
218
+ " };",
219
+ " }catch(e){}",
220
+ " try{",
221
+ ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
222
+ " var _sv=speechSynthesis.getVoices.bind(speechSynthesis);",
223
+ " speechSynthesis.getVoices=function(){",
224
+ " var real=_sv();",
225
+ " if(real&&real.length)return real;",
226
+ ' 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};});',
227
+ " };",
228
+ " }catch(e){}",
229
+ " try{",
230
+ " var _gb=navigator.getBattery.bind(navigator);",
231
+ " navigator.getBattery=function(){",
232
+ " return _gb().then(function(b){",
233
+ ' Object.defineProperty(b,"charging",{get:function(){return true;},configurable:true});',
234
+ " return b;});",
235
+ " };",
236
+ " }catch(e){}",
237
+ // 3c. WebRTC local IP leak guard (d23): headless STUN candidates can
238
+ // expose host IPs; mDNS-only candidates are the modern Chrome default.
239
+ " try{",
240
+ " var _oc=RTCPeerConnection.prototype.createOffer;",
241
+ " RTCPeerConnection.prototype.createOffer=function(){",
242
+ " var p=_oc.apply(this,arguments);",
243
+ " var self=this;",
244
+ " return p.then(function(offer){",
245
+ ' offer.sdp=offer.sdp.split(String.fromCharCode(10)).filter(function(l){return l.indexOf("typ host")<0}).join(String.fromCharCode(10));',
246
+ " return offer;});",
247
+ " };",
248
+ " }catch(e){}",
249
+ // 3d. Chrome object depth (d24): automation fakes usually only set
250
+ // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
251
+ " try{",
252
+ " if(window.chrome){",
253
+ ' 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"}};',
254
+ ' 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};',
255
+ " if(!window.chrome.csi)window.chrome.csi=function(){return{};}",
256
+ " if(!window.chrome.loadTimes)window.chrome.loadTimes=function(){return{};}",
257
+ " }",
258
+ " }catch(e){}",
259
+ // 3e. Font availability patch (d25): headless Chromium misses some system
260
+ // fonts (e.g. Menlo on macOS) that real Chrome has. Register a FontFace
261
+ // aliasing the missing font to a local() equivalent so offsetWidth-based
262
+ // font probing sees the same availability as the faked environment.
263
+ " try{",
264
+ ' var _fontAliases=[["Menlo","Courier New"],["SF Mono","Menlo"],["Segoe UI","Helvetica"]];',
265
+ " _fontAliases.forEach(function(pa){",
266
+ ' try{var ff=new FontFace(pa[0],"local("+JSON.stringify(pa[1])+")");',
267
+ " document.fonts.add(ff);ff.load();}catch(e2){}",
268
+ " });",
269
+ " }catch(e){}",
147
270
  // 3. toString disguise (name-list based)
148
271
  " var _ts=Function.prototype.toString;",
149
272
  " var _hf=document.hasFocus;",
@@ -153,12 +276,16 @@ function buildStealthInitScript() {
153
276
  ' if(this===_gw)return"function get width() { [native code] }";',
154
277
  ' if(this===_gh)return"function get height() { [native code] }";',
155
278
  ' if(this===_gah)return"function get availHeight() { [native code] }";',
279
+ ' if(this===CanvasRenderingContext2D.prototype.fillText)return"function fillText() { [native code] }";',
280
+ ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
281
+ ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
282
+ ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
156
283
  " return _ts.call(this);",
157
284
  " };",
158
285
  // 4. onclick prototype hijack (dual-stream consistency)
159
286
  " var _ba=function(k,p){",
160
- ' if(p==="isTrusted")return true;',
161
- ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isFinite(k[p])){',
287
+ ' if(p==="isTrusted")return k.isTrusted;',
288
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
162
289
  " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
163
290
  " }",
164
291
  ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
@@ -194,7 +321,7 @@ var XBMouseImpl = class {
194
321
  }
195
322
  async click(x, y, opts = {}) {
196
323
  const button = opts.button ?? "left";
197
- const stealth = opts.stealth ?? true;
324
+ const stealth = opts.stealth ?? process.env.XBROWSER_STEALTH !== "off";
198
325
  let tx = x, ty = y;
199
326
  if (stealth && opts.elementWidth !== void 0 && opts.elementHeight !== void 0) {
200
327
  const off = landingOffset(opts.elementWidth, opts.elementHeight);
@@ -203,12 +330,23 @@ var XBMouseImpl = class {
203
330
  }
204
331
  if (stealth) {
205
332
  const traj = bezierTrajectory(this._x, this._y, tx, ty);
333
+ const _tb = Date.now();
334
+ let _truncated = false;
206
335
  for (const p of traj) {
336
+ if (Date.now() - _tb > 5e3) {
337
+ _truncated = true;
338
+ break;
339
+ }
207
340
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
208
341
  this._x = p.x;
209
342
  this._y = p.y;
210
343
  await sleep(p.delay);
211
344
  }
345
+ this._x = tx;
346
+ this._y = ty;
347
+ if (_truncated) {
348
+ await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: tx, y: ty, button: this._button });
349
+ }
212
350
  await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
213
351
  } else {
214
352
  await this.move(tx, ty);
@@ -313,12 +451,20 @@ var XBKeyboardImpl = class {
313
451
  }
314
452
  await this.dispatchKeyEvent(downParams);
315
453
  if (mapping.text) {
454
+ if (process.env.XBROWSER_STEALTH !== "off") {
455
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
456
+ }
316
457
  await this.dispatchKeyEvent({
317
458
  type: "char",
318
459
  text: mapping.text
319
460
  });
320
461
  }
321
462
  if (delay > 0) await sleep2(delay);
463
+ else {
464
+ if (process.env.XBROWSER_STEALTH !== "off") {
465
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
466
+ }
467
+ }
322
468
  const upParams = {
323
469
  type: "keyUp",
324
470
  key: mapping.key,
@@ -2028,6 +2174,10 @@ Last error: ${lastError.message}` : "";
2028
2174
  this.sessionId
2029
2175
  );
2030
2176
  }
2177
+ /** 覆盖 User-Agent(headless 环境对抗 UA 检测的标配能力) */
2178
+ async setUserAgent(userAgent) {
2179
+ await this._setUserAgent(userAgent);
2180
+ }
2031
2181
  /** Internal: set extra HTTP headers */
2032
2182
  async _setExtraHTTPHeaders(headers) {
2033
2183
  await this.setExtraHTTPHeaders(headers);
@@ -3157,9 +3307,28 @@ var XBBrowserImpl = class {
3157
3307
  // src/cdp-driver/connection.ts
3158
3308
  import { EventEmitter as EventEmitter4 } from "events";
3159
3309
  import { WebSocket } from "ws";
3160
- var CDPConnection = class extends EventEmitter4 {
3310
+ var CDPConnection = class _CDPConnection extends EventEmitter4 {
3161
3311
  ws;
3162
3312
  nextId = 1;
3313
+ /** CDP Guard:全部出站命令过规则引擎(env XBROWSER_CDP_GUARD=off 关闭)。
3314
+ * 173 条规则 / 9 模块:合成事件 block、指纹暴露警告、自动化信号拦截。 */
3315
+ static __guard;
3316
+ get guard() {
3317
+ if (_CDPConnection.__guard === void 0) {
3318
+ if (process.env.XBROWSER_CDP_GUARD === "off") {
3319
+ _CDPConnection.__guard = null;
3320
+ } else {
3321
+ try {
3322
+ const g = createRuleEngine();
3323
+ g.start();
3324
+ _CDPConnection.__guard = g;
3325
+ } catch {
3326
+ _CDPConnection.__guard = null;
3327
+ }
3328
+ }
3329
+ }
3330
+ return _CDPConnection.__guard;
3331
+ }
3163
3332
  pending = /* @__PURE__ */ new Map();
3164
3333
  closed = false;
3165
3334
  closeReason = null;
@@ -3183,6 +3352,8 @@ var CDPConnection = class extends EventEmitter4 {
3183
3352
  * ping, the connection is considered dead and forcibly closed. */
3184
3353
  keepaliveTimer = null;
3185
3354
  pongTimer = null;
3355
+ /** 最近一次收到任何 WS 消息的时间——有数据流动即证明连接存活 */
3356
+ lastIncomingAt = Date.now();
3186
3357
  startKeepalive() {
3187
3358
  this.ws.on("pong", () => {
3188
3359
  if (this.pongTimer) {
@@ -3194,9 +3365,14 @@ var CDPConnection = class extends EventEmitter4 {
3194
3365
  if (this.ws.readyState === WebSocket.OPEN) {
3195
3366
  if (!this.pongTimer) {
3196
3367
  this.pongTimer = setTimeout(() => {
3368
+ const recentlyActive = Date.now() - this.lastIncomingAt < 3e4;
3369
+ if (recentlyActive) {
3370
+ this.pongTimer = null;
3371
+ return;
3372
+ }
3197
3373
  if (!this.closed) {
3198
3374
  this.closed = true;
3199
- this.closeReason = "keepalive timeout (no pong in 10s)";
3375
+ this.closeReason = "keepalive timeout (no pong and idle >30s)";
3200
3376
  try {
3201
3377
  this.ws.terminate();
3202
3378
  } catch {
@@ -3208,7 +3384,7 @@ var CDPConnection = class extends EventEmitter4 {
3208
3384
  this.pending.clear();
3209
3385
  this.emit("disconnect");
3210
3386
  }
3211
- }, 1e4);
3387
+ }, 6e4);
3212
3388
  }
3213
3389
  this.ws.ping?.();
3214
3390
  } else if (this.closed) {
@@ -3246,10 +3422,23 @@ var CDPConnection = class extends EventEmitter4 {
3246
3422
  * @param method — CDP domain.method (e.g. "Page.navigate")
3247
3423
  * @param params — method parameters
3248
3424
  * @param sessionId — optional flat session ID for sub-targets
3249
- * @param timeoutMs — response timeout (default: 30s)
3425
+ * @param timeoutMs — response timeout (default: 30s;重 SPA 加载期间主线程忙,
3426
+ * evaluate 可排队 >30s——Runtime.evaluate 单独放宽到 90s)
3250
3427
  * @returns the `result` field from the CDP response
3251
3428
  */
3252
- async send(method, params, sessionId, timeoutMs = 3e4) {
3429
+ async send(method, params, sessionId, timeoutMs) {
3430
+ const effectiveTimeout = timeoutMs ?? (method === "Runtime.evaluate" ? 9e4 : 3e4);
3431
+ if (this.guard) {
3432
+ const decision = this.guard.evaluate({
3433
+ method,
3434
+ params: params ?? {},
3435
+ sessionId: sessionId ?? this.defaultSessionId ?? "",
3436
+ direction: "client\u2192browser"
3437
+ });
3438
+ if (decision && decision.action === "block") {
3439
+ throw new Error(`[CDP-Guard] ${decision.reason}${decision.suggestion ? " | \u66FF\u4EE3: " + decision.suggestion : ""} [${decision.ruleId}]`);
3440
+ }
3441
+ }
3253
3442
  if (this.closed) {
3254
3443
  throw new Error(`CDP connection closed: ${this.closeReason ?? "unknown"}`);
3255
3444
  }
@@ -3264,8 +3453,8 @@ var CDPConnection = class extends EventEmitter4 {
3264
3453
  return new Promise((resolve, reject) => {
3265
3454
  const timeout = setTimeout(() => {
3266
3455
  this.pending.delete(id);
3267
- reject(new Error(`CDP timeout: ${method} (${timeoutMs}ms)`));
3268
- }, timeoutMs);
3456
+ reject(new Error(`CDP timeout: ${method} (${effectiveTimeout}ms)`));
3457
+ }, effectiveTimeout);
3269
3458
  this.pending.set(id, {
3270
3459
  resolve: (v) => {
3271
3460
  clearTimeout(timeout);
@@ -3348,6 +3537,7 @@ var CDPConnection = class extends EventEmitter4 {
3348
3537
  // ── Private ─────────────────────────────────────────────────
3349
3538
  bindWebSocket() {
3350
3539
  this.ws.on("message", (raw) => {
3540
+ this.lastIncomingAt = Date.now();
3351
3541
  let msg;
3352
3542
  try {
3353
3543
  msg = JSON.parse(raw.toString());