@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.
- package/dist/{browser-Y5SIE6LY.js → browser-C2VEOJC2.js} +2 -2
- package/dist/{browser-6LH6KJSW.js → browser-CNBSH53B.js} +1 -1
- package/dist/{browser-TUD2KA5G.js → browser-VZOOFLAH.js} +4 -4
- package/dist/{cdp-driver-2EECZW4X.js → cdp-driver-26LGCHXV.js} +2 -2
- package/dist/{cdp-driver-E7G3VHIB.js → cdp-driver-7Z6ZAT5E.js} +148 -33
- package/dist/{cdp-driver-7HO5SRNN.js → cdp-driver-KFX4A6WR.js} +1 -1
- package/dist/{chunk-H2A5JUK5.js → chunk-75DNUI6M.js} +9 -0
- package/dist/{chunk-C6PW3CPA.js → chunk-AVSTNODR.js} +1 -1
- package/dist/{chunk-GKYSCHT4.js → chunk-CLYZPSDR.js} +148 -33
- package/dist/{chunk-KLXLNQI5.js → chunk-ESJYANQO.js} +148 -33
- package/dist/{chunk-LKJZYVIX.js → chunk-HWWG2ISJ.js} +140 -34
- package/dist/{chunk-A6LPGFAL.js → chunk-WYETQ5C7.js} +1 -1
- package/dist/{chunk-2BPZ25RE.js → chunk-Z5RTK4GW.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/daemon-main.js +4 -4
- package/dist/index.d.ts +99 -0
- package/dist/index.js +8 -8
- package/dist/{proxy-LUR4U5YF.js → proxy-NKCYE76M.js} +2 -2
- package/dist/{session-replayer-RODZYHM3.js → session-replayer-EVD4HZBB.js} +1 -1
- package/package.json +2 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-GDKLH7ZY.js";
|
|
8
8
|
import {
|
|
9
9
|
createRuleEngine
|
|
10
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
-
"
|
|
266
|
-
"
|
|
267
|
-
'
|
|
268
|
-
"
|
|
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
|
-
|
|
274
|
-
"
|
|
275
|
-
"
|
|
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
|
|
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.
|
|
291
|
-
" cw.
|
|
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=
|
|
327
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
14712
|
+
const { ensureProcessCanExit } = await import("./browser-CNBSH53B.js");
|
|
14713
14713
|
await ensureProcessCanExit().catch(() => {
|
|
14714
14714
|
});
|
|
14715
14715
|
process.exit(process.exitCode || exitCode);
|
package/dist/daemon-main.js
CHANGED
|
@@ -34,13 +34,13 @@ import {
|
|
|
34
34
|
resolveLaunchOpts,
|
|
35
35
|
saveSessionDiskMeta,
|
|
36
36
|
setActivePage
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-AVSTNODR.js";
|
|
38
38
|
import {
|
|
39
39
|
createRuleEngine,
|
|
40
40
|
rand,
|
|
41
41
|
sleep,
|
|
42
42
|
wheelDelta
|
|
43
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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;
|
|
@@ -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-
|
|
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.
|
|
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",
|