@xbrowser/cli 1.20.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-PBBC7RAV.js → browser-C2VEOJC2.js} +2 -2
- package/dist/{browser-Y6B3NS4Q.js → browser-CNBSH53B.js} +1 -1
- package/dist/{browser-NVC7MTML.js → browser-VZOOFLAH.js} +4 -4
- package/dist/{cdp-driver-QYFA4QTP.js → cdp-driver-26LGCHXV.js} +2 -2
- package/dist/{cdp-driver-Z2PLAO42.js → cdp-driver-7Z6ZAT5E.js} +86 -17
- package/dist/{cdp-driver-73F5CVH2.js → cdp-driver-KFX4A6WR.js} +1 -1
- package/dist/{chunk-H2A5JUK5.js → chunk-75DNUI6M.js} +9 -0
- package/dist/{chunk-75TLPVVZ.js → chunk-AVSTNODR.js} +1 -1
- package/dist/{chunk-UYEJ66VX.js → chunk-CLYZPSDR.js} +86 -17
- package/dist/{chunk-T2KUVO4H.js → chunk-ESJYANQO.js} +86 -17
- package/dist/{chunk-FDU5BCPV.js → chunk-HWWG2ISJ.js} +78 -18
- package/dist/{chunk-A6LPGFAL.js → chunk-WYETQ5C7.js} +1 -1
- package/dist/{chunk-76EKW7YJ.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-SBYJXQZZ.js → session-replayer-EVD4HZBB.js} +1 -1
- package/package.json +2 -1
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-AVSTNODR.js";
|
|
24
|
+
import "./chunk-CLYZPSDR.js";
|
|
25
25
|
import "./chunk-3FWLW7FS.js";
|
|
26
26
|
import "./chunk-3KZ34AUC.js";
|
|
27
27
|
import "./chunk-GDKLH7ZY.js";
|
|
@@ -20,12 +20,12 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-Z5RTK4GW.js";
|
|
24
|
+
import "./chunk-HWWG2ISJ.js";
|
|
25
25
|
import "./chunk-3KZ34AUC.js";
|
|
26
26
|
import "./chunk-GDKLH7ZY.js";
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-WYETQ5C7.js";
|
|
28
|
+
import "./chunk-75DNUI6M.js";
|
|
29
29
|
import "./chunk-KFQGP6VL.js";
|
|
30
30
|
export {
|
|
31
31
|
closeAllSessions,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
scrollIntoView,
|
|
15
15
|
waitForActionable,
|
|
16
16
|
waitForNetworkIdle
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HWWG2ISJ.js";
|
|
18
18
|
import {
|
|
19
19
|
connectToCDP,
|
|
20
20
|
findChrome,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
launchChrome
|
|
24
24
|
} from "./chunk-3KZ34AUC.js";
|
|
25
25
|
import "./chunk-GDKLH7ZY.js";
|
|
26
|
-
import "./chunk-
|
|
26
|
+
import "./chunk-75DNUI6M.js";
|
|
27
27
|
import "./chunk-KFQGP6VL.js";
|
|
28
28
|
export {
|
|
29
29
|
CDPConnection,
|
|
@@ -21,6 +21,25 @@ import { EventEmitter } from "events";
|
|
|
21
21
|
|
|
22
22
|
// src/cdp-driver/stealth.ts
|
|
23
23
|
var DEFAULT_STEALTH_CONFIG = {
|
|
24
|
+
// S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
|
|
25
|
+
uaChProfile: {
|
|
26
|
+
brands: [
|
|
27
|
+
{ brand: "Not=A?Brand", version: "99" },
|
|
28
|
+
{ brand: "Google Chrome", version: "151" },
|
|
29
|
+
{ brand: "Chromium", version: "151" }
|
|
30
|
+
],
|
|
31
|
+
platform: "macOS",
|
|
32
|
+
platformVersion: "26.2.0",
|
|
33
|
+
architecture: "arm",
|
|
34
|
+
bitness: "64",
|
|
35
|
+
model: "",
|
|
36
|
+
uaFullVersion: "151.0.7922.175",
|
|
37
|
+
fullVersionList: [
|
|
38
|
+
{ brand: "Not=A?Brand", version: "99.0.0.0" },
|
|
39
|
+
{ brand: "Google Chrome", version: "151.0.7922.175" },
|
|
40
|
+
{ brand: "Chromium", version: "151.0.7922.175" }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
24
43
|
bezierCurvature: [0.35, 0.6],
|
|
25
44
|
noiseAmplitude: 5.5,
|
|
26
45
|
overshootRange: [6, 14],
|
|
@@ -119,10 +138,13 @@ Object.assign(KEY_MAP, {
|
|
|
119
138
|
"@": { key: "@", code: "Digit2", vk: 50, shift: true },
|
|
120
139
|
"_": { key: "_", code: "Minus", vk: 189, shift: true }
|
|
121
140
|
});
|
|
122
|
-
function buildStealthInitScript() {
|
|
141
|
+
function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
|
|
142
|
+
const prof = config.uaChProfile;
|
|
143
|
+
const profJson = prof ? JSON.stringify(prof) : "null";
|
|
123
144
|
return [
|
|
124
145
|
"(function(){",
|
|
125
146
|
' window.__xbStealthVer="57";',
|
|
147
|
+
" window.__xbUaChProf=" + profJson + ";",
|
|
126
148
|
// 1. AEL event proxy
|
|
127
149
|
" var o=EventTarget.prototype.addEventListener;",
|
|
128
150
|
" var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
|
|
@@ -216,6 +238,29 @@ function buildStealthInitScript() {
|
|
|
216
238
|
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
217
239
|
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
218
240
|
" }",
|
|
241
|
+
// S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
|
|
242
|
+
// 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
|
|
243
|
+
" if(!navigator.userAgentData && window.__xbUaChProf){",
|
|
244
|
+
" var prof = window.__xbUaChProf;",
|
|
245
|
+
" var uad={",
|
|
246
|
+
' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
|
|
247
|
+
" mobile:false,",
|
|
248
|
+
" platform:prof.platform,",
|
|
249
|
+
" getHighEntropyValues:function(hints){",
|
|
250
|
+
" return Promise.resolve().then(function(){",
|
|
251
|
+
" var all={brands:uad.brands,mobile:false,platform:prof.platform,",
|
|
252
|
+
" platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
|
|
253
|
+
" uaFullVersion:prof.uaFullVersion,",
|
|
254
|
+
" fullVersionList:prof.fullVersionList};",
|
|
255
|
+
" var out={};",
|
|
256
|
+
" (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
|
|
257
|
+
" return out;",
|
|
258
|
+
" });",
|
|
259
|
+
" },",
|
|
260
|
+
" toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
|
|
261
|
+
" };",
|
|
262
|
+
' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
|
|
263
|
+
" }",
|
|
219
264
|
" }catch(e){}",
|
|
220
265
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
221
266
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
@@ -2030,7 +2075,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
2030
2075
|
try {
|
|
2031
2076
|
await this.conn.send(
|
|
2032
2077
|
"Page.addScriptToEvaluateOnNewDocument",
|
|
2033
|
-
{ source: buildStealthInitScript() },
|
|
2078
|
+
{ source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
|
|
2034
2079
|
this.sessionId
|
|
2035
2080
|
);
|
|
2036
2081
|
} catch {
|
|
@@ -3287,6 +3332,10 @@ var XBContextImpl = class {
|
|
|
3287
3332
|
this.options = opts;
|
|
3288
3333
|
this.setupAutoAttach();
|
|
3289
3334
|
}
|
|
3335
|
+
/** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
|
|
3336
|
+
get stealthConfig() {
|
|
3337
|
+
return this.options.stealthConfig;
|
|
3338
|
+
}
|
|
3290
3339
|
async newPage() {
|
|
3291
3340
|
if (this.closed) throw new Error("Context is closed");
|
|
3292
3341
|
const { targetId } = await this._browser._createTarget(this.contextId);
|
|
@@ -3453,20 +3502,8 @@ var XBContextImpl = class {
|
|
|
3453
3502
|
|
|
3454
3503
|
// src/cdp-driver/browser.ts
|
|
3455
3504
|
var XBBrowserImpl = class {
|
|
3456
|
-
conn
|
|
3457
|
-
|
|
3458
|
-
_contexts = /* @__PURE__ */ new Map();
|
|
3459
|
-
_disconnected = false;
|
|
3460
|
-
childProcess = null;
|
|
3461
|
-
tmpDir;
|
|
3462
|
-
_exitHandler = null;
|
|
3463
|
-
/**
|
|
3464
|
-
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3465
|
-
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3466
|
-
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3467
|
-
*/
|
|
3468
|
-
cdpEndpoint;
|
|
3469
|
-
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
3505
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
|
|
3506
|
+
this.launchOpts = launchOpts;
|
|
3470
3507
|
this.conn = conn;
|
|
3471
3508
|
this.childProcess = childProcess ?? null;
|
|
3472
3509
|
this.tmpDir = tmpDir;
|
|
@@ -3494,6 +3531,20 @@ var XBBrowserImpl = class {
|
|
|
3494
3531
|
process.on("exit", this._exitHandler);
|
|
3495
3532
|
}
|
|
3496
3533
|
}
|
|
3534
|
+
launchOpts;
|
|
3535
|
+
conn;
|
|
3536
|
+
_emitter = new EventEmitter3();
|
|
3537
|
+
_contexts = /* @__PURE__ */ new Map();
|
|
3538
|
+
_disconnected = false;
|
|
3539
|
+
childProcess = null;
|
|
3540
|
+
tmpDir;
|
|
3541
|
+
_exitHandler = null;
|
|
3542
|
+
/**
|
|
3543
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3544
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3545
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3546
|
+
*/
|
|
3547
|
+
cdpEndpoint;
|
|
3497
3548
|
get disconnected() {
|
|
3498
3549
|
return this._disconnected;
|
|
3499
3550
|
}
|
|
@@ -3521,6 +3572,9 @@ var XBBrowserImpl = class {
|
|
|
3521
3572
|
this._emitter.emit("disconnected");
|
|
3522
3573
|
}
|
|
3523
3574
|
async newContext(opts = {}) {
|
|
3575
|
+
if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
|
|
3576
|
+
opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
|
|
3577
|
+
}
|
|
3524
3578
|
if (this._disconnected) {
|
|
3525
3579
|
throw new Error("Browser is disconnected");
|
|
3526
3580
|
}
|
|
@@ -3747,6 +3801,9 @@ function extractAllStrings(rawArgs) {
|
|
|
3747
3801
|
}
|
|
3748
3802
|
return strings.length > 0 ? strings.join("\n") : null;
|
|
3749
3803
|
}
|
|
3804
|
+
function isProbeMarked(code) {
|
|
3805
|
+
return code.includes("/* @xb-probe */");
|
|
3806
|
+
}
|
|
3750
3807
|
|
|
3751
3808
|
// src/cdp-interceptor/rules/dom-mutation.ts
|
|
3752
3809
|
var DOM_PATTERNS = [
|
|
@@ -4309,6 +4366,7 @@ var eventSimulationRule = {
|
|
|
4309
4366
|
evaluate(ctx) {
|
|
4310
4367
|
const userCode = extractUserCode(ctx);
|
|
4311
4368
|
if (!userCode) return null;
|
|
4369
|
+
if (isProbeMarked(userCode)) return null;
|
|
4312
4370
|
for (const p of EVENT_PATTERNS) {
|
|
4313
4371
|
if (p.pattern.test(userCode)) {
|
|
4314
4372
|
return {
|
|
@@ -4636,6 +4694,11 @@ function createRuleEngine(customRules) {
|
|
|
4636
4694
|
...ctx,
|
|
4637
4695
|
sessionState: getSessionState(ctx.sessionId)
|
|
4638
4696
|
};
|
|
4697
|
+
try {
|
|
4698
|
+
const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
|
|
4699
|
+
if (isProbeMarked(code)) return null;
|
|
4700
|
+
} catch {
|
|
4701
|
+
}
|
|
4639
4702
|
for (const rule of rules) {
|
|
4640
4703
|
if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
|
|
4641
4704
|
const decision = rule.evaluate(fullCtx);
|
|
@@ -5017,7 +5080,13 @@ async function launch(options = {}) {
|
|
|
5017
5080
|
const conn = new CDPConnection(wsEndpoint);
|
|
5018
5081
|
await conn.ready();
|
|
5019
5082
|
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
5020
|
-
const browser = new XBBrowserImpl(
|
|
5083
|
+
const browser = new XBBrowserImpl(
|
|
5084
|
+
conn,
|
|
5085
|
+
childProcess,
|
|
5086
|
+
tmpDir,
|
|
5087
|
+
httpEndpoint,
|
|
5088
|
+
options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
|
|
5089
|
+
);
|
|
5021
5090
|
return { browser, wsEndpoint };
|
|
5022
5091
|
}
|
|
5023
5092
|
export {
|
|
@@ -39,6 +39,9 @@ function extractAllStrings(rawArgs) {
|
|
|
39
39
|
}
|
|
40
40
|
return strings.length > 0 ? strings.join("\n") : null;
|
|
41
41
|
}
|
|
42
|
+
function isProbeMarked(code) {
|
|
43
|
+
return code.includes("/* @xb-probe */");
|
|
44
|
+
}
|
|
42
45
|
|
|
43
46
|
// src/cdp-interceptor/rules/dom-mutation.ts
|
|
44
47
|
var DOM_PATTERNS = [
|
|
@@ -601,6 +604,7 @@ var eventSimulationRule = {
|
|
|
601
604
|
evaluate(ctx) {
|
|
602
605
|
const userCode = extractUserCode(ctx);
|
|
603
606
|
if (!userCode) return null;
|
|
607
|
+
if (isProbeMarked(userCode)) return null;
|
|
604
608
|
for (const p of EVENT_PATTERNS) {
|
|
605
609
|
if (p.pattern.test(userCode)) {
|
|
606
610
|
return {
|
|
@@ -928,6 +932,11 @@ function createRuleEngine(customRules) {
|
|
|
928
932
|
...ctx,
|
|
929
933
|
sessionState: getSessionState(ctx.sessionId)
|
|
930
934
|
};
|
|
935
|
+
try {
|
|
936
|
+
const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
|
|
937
|
+
if (isProbeMarked(code)) return null;
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
931
940
|
for (const rule of rules) {
|
|
932
941
|
if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
|
|
933
942
|
const decision = rule.evaluate(fullCtx);
|
|
@@ -24,6 +24,25 @@ import { EventEmitter } from "events";
|
|
|
24
24
|
|
|
25
25
|
// src/cdp-driver/stealth.ts
|
|
26
26
|
var DEFAULT_STEALTH_CONFIG = {
|
|
27
|
+
// S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
|
|
28
|
+
uaChProfile: {
|
|
29
|
+
brands: [
|
|
30
|
+
{ brand: "Not=A?Brand", version: "99" },
|
|
31
|
+
{ brand: "Google Chrome", version: "151" },
|
|
32
|
+
{ brand: "Chromium", version: "151" }
|
|
33
|
+
],
|
|
34
|
+
platform: "macOS",
|
|
35
|
+
platformVersion: "26.2.0",
|
|
36
|
+
architecture: "arm",
|
|
37
|
+
bitness: "64",
|
|
38
|
+
model: "",
|
|
39
|
+
uaFullVersion: "151.0.7922.175",
|
|
40
|
+
fullVersionList: [
|
|
41
|
+
{ brand: "Not=A?Brand", version: "99.0.0.0" },
|
|
42
|
+
{ brand: "Google Chrome", version: "151.0.7922.175" },
|
|
43
|
+
{ brand: "Chromium", version: "151.0.7922.175" }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
27
46
|
bezierCurvature: [0.35, 0.6],
|
|
28
47
|
noiseAmplitude: 5.5,
|
|
29
48
|
overshootRange: [6, 14],
|
|
@@ -130,10 +149,13 @@ Object.assign(KEY_MAP, {
|
|
|
130
149
|
"@": { key: "@", code: "Digit2", vk: 50, shift: true },
|
|
131
150
|
"_": { key: "_", code: "Minus", vk: 189, shift: true }
|
|
132
151
|
});
|
|
133
|
-
function buildStealthInitScript() {
|
|
152
|
+
function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
|
|
153
|
+
const prof = config.uaChProfile;
|
|
154
|
+
const profJson = prof ? JSON.stringify(prof) : "null";
|
|
134
155
|
return [
|
|
135
156
|
"(function(){",
|
|
136
157
|
' window.__xbStealthVer="57";',
|
|
158
|
+
" window.__xbUaChProf=" + profJson + ";",
|
|
137
159
|
// 1. AEL event proxy
|
|
138
160
|
" var o=EventTarget.prototype.addEventListener;",
|
|
139
161
|
" var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
|
|
@@ -227,6 +249,29 @@ function buildStealthInitScript() {
|
|
|
227
249
|
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
228
250
|
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
229
251
|
" }",
|
|
252
|
+
// S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
|
|
253
|
+
// 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
|
|
254
|
+
" if(!navigator.userAgentData && window.__xbUaChProf){",
|
|
255
|
+
" var prof = window.__xbUaChProf;",
|
|
256
|
+
" var uad={",
|
|
257
|
+
' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
|
|
258
|
+
" mobile:false,",
|
|
259
|
+
" platform:prof.platform,",
|
|
260
|
+
" getHighEntropyValues:function(hints){",
|
|
261
|
+
" return Promise.resolve().then(function(){",
|
|
262
|
+
" var all={brands:uad.brands,mobile:false,platform:prof.platform,",
|
|
263
|
+
" platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
|
|
264
|
+
" uaFullVersion:prof.uaFullVersion,",
|
|
265
|
+
" fullVersionList:prof.fullVersionList};",
|
|
266
|
+
" var out={};",
|
|
267
|
+
" (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
|
|
268
|
+
" return out;",
|
|
269
|
+
" });",
|
|
270
|
+
" },",
|
|
271
|
+
" toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
|
|
272
|
+
" };",
|
|
273
|
+
' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
|
|
274
|
+
" }",
|
|
230
275
|
" }catch(e){}",
|
|
231
276
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
232
277
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
@@ -1939,7 +1984,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
1939
1984
|
try {
|
|
1940
1985
|
await this.conn.send(
|
|
1941
1986
|
"Page.addScriptToEvaluateOnNewDocument",
|
|
1942
|
-
{ source: buildStealthInitScript() },
|
|
1987
|
+
{ source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
|
|
1943
1988
|
this.sessionId
|
|
1944
1989
|
);
|
|
1945
1990
|
} catch {
|
|
@@ -3196,6 +3241,10 @@ var XBContextImpl = class {
|
|
|
3196
3241
|
this.options = opts;
|
|
3197
3242
|
this.setupAutoAttach();
|
|
3198
3243
|
}
|
|
3244
|
+
/** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
|
|
3245
|
+
get stealthConfig() {
|
|
3246
|
+
return this.options.stealthConfig;
|
|
3247
|
+
}
|
|
3199
3248
|
async newPage() {
|
|
3200
3249
|
if (this.closed) throw new Error("Context is closed");
|
|
3201
3250
|
const { targetId } = await this._browser._createTarget(this.contextId);
|
|
@@ -3362,20 +3411,8 @@ var XBContextImpl = class {
|
|
|
3362
3411
|
|
|
3363
3412
|
// src/cdp-driver/browser.ts
|
|
3364
3413
|
var XBBrowserImpl = class {
|
|
3365
|
-
conn
|
|
3366
|
-
|
|
3367
|
-
_contexts = /* @__PURE__ */ new Map();
|
|
3368
|
-
_disconnected = false;
|
|
3369
|
-
childProcess = null;
|
|
3370
|
-
tmpDir;
|
|
3371
|
-
_exitHandler = null;
|
|
3372
|
-
/**
|
|
3373
|
-
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3374
|
-
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3375
|
-
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3376
|
-
*/
|
|
3377
|
-
cdpEndpoint;
|
|
3378
|
-
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
3414
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
|
|
3415
|
+
this.launchOpts = launchOpts;
|
|
3379
3416
|
this.conn = conn;
|
|
3380
3417
|
this.childProcess = childProcess ?? null;
|
|
3381
3418
|
this.tmpDir = tmpDir;
|
|
@@ -3403,6 +3440,20 @@ var XBBrowserImpl = class {
|
|
|
3403
3440
|
process.on("exit", this._exitHandler);
|
|
3404
3441
|
}
|
|
3405
3442
|
}
|
|
3443
|
+
launchOpts;
|
|
3444
|
+
conn;
|
|
3445
|
+
_emitter = new EventEmitter3();
|
|
3446
|
+
_contexts = /* @__PURE__ */ new Map();
|
|
3447
|
+
_disconnected = false;
|
|
3448
|
+
childProcess = null;
|
|
3449
|
+
tmpDir;
|
|
3450
|
+
_exitHandler = null;
|
|
3451
|
+
/**
|
|
3452
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3453
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3454
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3455
|
+
*/
|
|
3456
|
+
cdpEndpoint;
|
|
3406
3457
|
get disconnected() {
|
|
3407
3458
|
return this._disconnected;
|
|
3408
3459
|
}
|
|
@@ -3430,6 +3481,9 @@ var XBBrowserImpl = class {
|
|
|
3430
3481
|
this._emitter.emit("disconnected");
|
|
3431
3482
|
}
|
|
3432
3483
|
async newContext(opts = {}) {
|
|
3484
|
+
if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
|
|
3485
|
+
opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
|
|
3486
|
+
}
|
|
3433
3487
|
if (this._disconnected) {
|
|
3434
3488
|
throw new Error("Browser is disconnected");
|
|
3435
3489
|
}
|
|
@@ -3656,6 +3710,9 @@ function extractAllStrings(rawArgs) {
|
|
|
3656
3710
|
}
|
|
3657
3711
|
return strings.length > 0 ? strings.join("\n") : null;
|
|
3658
3712
|
}
|
|
3713
|
+
function isProbeMarked(code) {
|
|
3714
|
+
return code.includes("/* @xb-probe */");
|
|
3715
|
+
}
|
|
3659
3716
|
|
|
3660
3717
|
// src/cdp-interceptor/rules/dom-mutation.ts
|
|
3661
3718
|
var DOM_PATTERNS = [
|
|
@@ -4218,6 +4275,7 @@ var eventSimulationRule = {
|
|
|
4218
4275
|
evaluate(ctx) {
|
|
4219
4276
|
const userCode = extractUserCode(ctx);
|
|
4220
4277
|
if (!userCode) return null;
|
|
4278
|
+
if (isProbeMarked(userCode)) return null;
|
|
4221
4279
|
for (const p of EVENT_PATTERNS) {
|
|
4222
4280
|
if (p.pattern.test(userCode)) {
|
|
4223
4281
|
return {
|
|
@@ -4545,6 +4603,11 @@ function createRuleEngine(customRules) {
|
|
|
4545
4603
|
...ctx,
|
|
4546
4604
|
sessionState: getSessionState(ctx.sessionId)
|
|
4547
4605
|
};
|
|
4606
|
+
try {
|
|
4607
|
+
const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
|
|
4608
|
+
if (isProbeMarked(code)) return null;
|
|
4609
|
+
} catch {
|
|
4610
|
+
}
|
|
4548
4611
|
for (const rule of rules) {
|
|
4549
4612
|
if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
|
|
4550
4613
|
const decision = rule.evaluate(fullCtx);
|
|
@@ -4926,7 +4989,13 @@ async function launch(options = {}) {
|
|
|
4926
4989
|
const conn = new CDPConnection(wsEndpoint);
|
|
4927
4990
|
await conn.ready();
|
|
4928
4991
|
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
4929
|
-
const browser = new XBBrowserImpl(
|
|
4992
|
+
const browser = new XBBrowserImpl(
|
|
4993
|
+
conn,
|
|
4994
|
+
childProcess,
|
|
4995
|
+
tmpDir,
|
|
4996
|
+
httpEndpoint,
|
|
4997
|
+
options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
|
|
4998
|
+
);
|
|
4930
4999
|
return { browser, wsEndpoint };
|
|
4931
5000
|
}
|
|
4932
5001
|
|
|
@@ -26,6 +26,25 @@ import { EventEmitter } from "events";
|
|
|
26
26
|
|
|
27
27
|
// src/cdp-driver/stealth.ts
|
|
28
28
|
var DEFAULT_STEALTH_CONFIG = {
|
|
29
|
+
// S191: macOS arm64 + Chrome 151 真实采样(来源:登录桥只读通道)
|
|
30
|
+
uaChProfile: {
|
|
31
|
+
brands: [
|
|
32
|
+
{ brand: "Not=A?Brand", version: "99" },
|
|
33
|
+
{ brand: "Google Chrome", version: "151" },
|
|
34
|
+
{ brand: "Chromium", version: "151" }
|
|
35
|
+
],
|
|
36
|
+
platform: "macOS",
|
|
37
|
+
platformVersion: "26.2.0",
|
|
38
|
+
architecture: "arm",
|
|
39
|
+
bitness: "64",
|
|
40
|
+
model: "",
|
|
41
|
+
uaFullVersion: "151.0.7922.175",
|
|
42
|
+
fullVersionList: [
|
|
43
|
+
{ brand: "Not=A?Brand", version: "99.0.0.0" },
|
|
44
|
+
{ brand: "Google Chrome", version: "151.0.7922.175" },
|
|
45
|
+
{ brand: "Chromium", version: "151.0.7922.175" }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
29
48
|
bezierCurvature: [0.35, 0.6],
|
|
30
49
|
noiseAmplitude: 5.5,
|
|
31
50
|
overshootRange: [6, 14],
|
|
@@ -132,10 +151,13 @@ Object.assign(KEY_MAP, {
|
|
|
132
151
|
"@": { key: "@", code: "Digit2", vk: 50, shift: true },
|
|
133
152
|
"_": { key: "_", code: "Minus", vk: 189, shift: true }
|
|
134
153
|
});
|
|
135
|
-
function buildStealthInitScript() {
|
|
154
|
+
function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
|
|
155
|
+
const prof = config.uaChProfile;
|
|
156
|
+
const profJson = prof ? JSON.stringify(prof) : "null";
|
|
136
157
|
return [
|
|
137
158
|
"(function(){",
|
|
138
159
|
' window.__xbStealthVer="57";',
|
|
160
|
+
" window.__xbUaChProf=" + profJson + ";",
|
|
139
161
|
// 1. AEL event proxy
|
|
140
162
|
" var o=EventTarget.prototype.addEventListener;",
|
|
141
163
|
" var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
|
|
@@ -229,6 +251,29 @@ function buildStealthInitScript() {
|
|
|
229
251
|
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
230
252
|
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
231
253
|
" }",
|
|
254
|
+
// S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
|
|
255
|
+
// 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
|
|
256
|
+
" if(!navigator.userAgentData && window.__xbUaChProf){",
|
|
257
|
+
" var prof = window.__xbUaChProf;",
|
|
258
|
+
" var uad={",
|
|
259
|
+
' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
|
|
260
|
+
" mobile:false,",
|
|
261
|
+
" platform:prof.platform,",
|
|
262
|
+
" getHighEntropyValues:function(hints){",
|
|
263
|
+
" return Promise.resolve().then(function(){",
|
|
264
|
+
" var all={brands:uad.brands,mobile:false,platform:prof.platform,",
|
|
265
|
+
" platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
|
|
266
|
+
" uaFullVersion:prof.uaFullVersion,",
|
|
267
|
+
" fullVersionList:prof.fullVersionList};",
|
|
268
|
+
" var out={};",
|
|
269
|
+
" (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
|
|
270
|
+
" return out;",
|
|
271
|
+
" });",
|
|
272
|
+
" },",
|
|
273
|
+
" toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
|
|
274
|
+
" };",
|
|
275
|
+
' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
|
|
276
|
+
" }",
|
|
232
277
|
" }catch(e){}",
|
|
233
278
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
234
279
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
@@ -2043,7 +2088,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
2043
2088
|
try {
|
|
2044
2089
|
await this.conn.send(
|
|
2045
2090
|
"Page.addScriptToEvaluateOnNewDocument",
|
|
2046
|
-
{ source: buildStealthInitScript() },
|
|
2091
|
+
{ source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
|
|
2047
2092
|
this.sessionId
|
|
2048
2093
|
);
|
|
2049
2094
|
} catch {
|
|
@@ -3300,6 +3345,10 @@ var XBContextImpl = class {
|
|
|
3300
3345
|
this.options = opts;
|
|
3301
3346
|
this.setupAutoAttach();
|
|
3302
3347
|
}
|
|
3348
|
+
/** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
|
|
3349
|
+
get stealthConfig() {
|
|
3350
|
+
return this.options.stealthConfig;
|
|
3351
|
+
}
|
|
3303
3352
|
async newPage() {
|
|
3304
3353
|
if (this.closed) throw new Error("Context is closed");
|
|
3305
3354
|
const { targetId } = await this._browser._createTarget(this.contextId);
|
|
@@ -3466,20 +3515,8 @@ var XBContextImpl = class {
|
|
|
3466
3515
|
|
|
3467
3516
|
// src/cdp-driver/browser.ts
|
|
3468
3517
|
var XBBrowserImpl = class {
|
|
3469
|
-
conn
|
|
3470
|
-
|
|
3471
|
-
_contexts = /* @__PURE__ */ new Map();
|
|
3472
|
-
_disconnected = false;
|
|
3473
|
-
childProcess = null;
|
|
3474
|
-
tmpDir;
|
|
3475
|
-
_exitHandler = null;
|
|
3476
|
-
/**
|
|
3477
|
-
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3478
|
-
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3479
|
-
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3480
|
-
*/
|
|
3481
|
-
cdpEndpoint;
|
|
3482
|
-
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
3518
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
|
|
3519
|
+
this.launchOpts = launchOpts;
|
|
3483
3520
|
this.conn = conn;
|
|
3484
3521
|
this.childProcess = childProcess ?? null;
|
|
3485
3522
|
this.tmpDir = tmpDir;
|
|
@@ -3507,6 +3544,20 @@ var XBBrowserImpl = class {
|
|
|
3507
3544
|
process.on("exit", this._exitHandler);
|
|
3508
3545
|
}
|
|
3509
3546
|
}
|
|
3547
|
+
launchOpts;
|
|
3548
|
+
conn;
|
|
3549
|
+
_emitter = new EventEmitter3();
|
|
3550
|
+
_contexts = /* @__PURE__ */ new Map();
|
|
3551
|
+
_disconnected = false;
|
|
3552
|
+
childProcess = null;
|
|
3553
|
+
tmpDir;
|
|
3554
|
+
_exitHandler = null;
|
|
3555
|
+
/**
|
|
3556
|
+
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3557
|
+
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3558
|
+
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3559
|
+
*/
|
|
3560
|
+
cdpEndpoint;
|
|
3510
3561
|
get disconnected() {
|
|
3511
3562
|
return this._disconnected;
|
|
3512
3563
|
}
|
|
@@ -3534,6 +3585,9 @@ var XBBrowserImpl = class {
|
|
|
3534
3585
|
this._emitter.emit("disconnected");
|
|
3535
3586
|
}
|
|
3536
3587
|
async newContext(opts = {}) {
|
|
3588
|
+
if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
|
|
3589
|
+
opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
|
|
3590
|
+
}
|
|
3537
3591
|
if (this._disconnected) {
|
|
3538
3592
|
throw new Error("Browser is disconnected");
|
|
3539
3593
|
}
|
|
@@ -3760,6 +3814,9 @@ function extractAllStrings(rawArgs) {
|
|
|
3760
3814
|
}
|
|
3761
3815
|
return strings.length > 0 ? strings.join("\n") : null;
|
|
3762
3816
|
}
|
|
3817
|
+
function isProbeMarked(code) {
|
|
3818
|
+
return code.includes("/* @xb-probe */");
|
|
3819
|
+
}
|
|
3763
3820
|
|
|
3764
3821
|
// src/cdp-interceptor/rules/dom-mutation.ts
|
|
3765
3822
|
var DOM_PATTERNS = [
|
|
@@ -4322,6 +4379,7 @@ var eventSimulationRule = {
|
|
|
4322
4379
|
evaluate(ctx) {
|
|
4323
4380
|
const userCode = extractUserCode(ctx);
|
|
4324
4381
|
if (!userCode) return null;
|
|
4382
|
+
if (isProbeMarked(userCode)) return null;
|
|
4325
4383
|
for (const p of EVENT_PATTERNS) {
|
|
4326
4384
|
if (p.pattern.test(userCode)) {
|
|
4327
4385
|
return {
|
|
@@ -4649,6 +4707,11 @@ function createRuleEngine(customRules) {
|
|
|
4649
4707
|
...ctx,
|
|
4650
4708
|
sessionState: getSessionState(ctx.sessionId)
|
|
4651
4709
|
};
|
|
4710
|
+
try {
|
|
4711
|
+
const code = typeof ctx.params?.expression === "string" ? ctx.params.expression : JSON.stringify(ctx.params ?? "");
|
|
4712
|
+
if (isProbeMarked(code)) return null;
|
|
4713
|
+
} catch {
|
|
4714
|
+
}
|
|
4652
4715
|
for (const rule of rules) {
|
|
4653
4716
|
if (rule.canHandle && !rule.canHandle(fullCtx)) continue;
|
|
4654
4717
|
const decision = rule.evaluate(fullCtx);
|
|
@@ -4970,7 +5033,13 @@ async function launch(options = {}) {
|
|
|
4970
5033
|
const conn = new CDPConnection(wsEndpoint);
|
|
4971
5034
|
await conn.ready();
|
|
4972
5035
|
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
4973
|
-
const browser = new XBBrowserImpl(
|
|
5036
|
+
const browser = new XBBrowserImpl(
|
|
5037
|
+
conn,
|
|
5038
|
+
childProcess,
|
|
5039
|
+
tmpDir,
|
|
5040
|
+
httpEndpoint,
|
|
5041
|
+
options.stealthConfig ? { stealthConfig: options.stealthConfig } : void 0
|
|
5042
|
+
);
|
|
4974
5043
|
return { browser, wsEndpoint };
|
|
4975
5044
|
}
|
|
4976
5045
|
|
|
@@ -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});",
|
|
@@ -226,6 +248,29 @@ function buildStealthInitScript() {
|
|
|
226
248
|
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
227
249
|
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
228
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
|
+
" }",
|
|
229
274
|
" }catch(e){}",
|
|
230
275
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
231
276
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
@@ -2040,7 +2085,7 @@ var XBPageImpl = class _XBPageImpl {
|
|
|
2040
2085
|
try {
|
|
2041
2086
|
await this.conn.send(
|
|
2042
2087
|
"Page.addScriptToEvaluateOnNewDocument",
|
|
2043
|
-
{ source: buildStealthInitScript() },
|
|
2088
|
+
{ source: buildStealthInitScript({ ...DEFAULT_STEALTH_CONFIG, ...this._contextImpl.stealthConfig ?? {} }) },
|
|
2044
2089
|
this.sessionId
|
|
2045
2090
|
);
|
|
2046
2091
|
} catch {
|
|
@@ -3297,6 +3342,10 @@ var XBContextImpl = class {
|
|
|
3297
3342
|
this.options = opts;
|
|
3298
3343
|
this.setupAutoAttach();
|
|
3299
3344
|
}
|
|
3345
|
+
/** S194: 任务 tab 的 stealth 配置(UA-CH 档案等)经 context 传给 page 层 */
|
|
3346
|
+
get stealthConfig() {
|
|
3347
|
+
return this.options.stealthConfig;
|
|
3348
|
+
}
|
|
3300
3349
|
async newPage() {
|
|
3301
3350
|
if (this.closed) throw new Error("Context is closed");
|
|
3302
3351
|
const { targetId } = await this._browser._createTarget(this.contextId);
|
|
@@ -3463,20 +3512,8 @@ var XBContextImpl = class {
|
|
|
3463
3512
|
|
|
3464
3513
|
// src/cdp-driver/browser.ts
|
|
3465
3514
|
var XBBrowserImpl = class {
|
|
3466
|
-
conn
|
|
3467
|
-
|
|
3468
|
-
_contexts = /* @__PURE__ */ new Map();
|
|
3469
|
-
_disconnected = false;
|
|
3470
|
-
childProcess = null;
|
|
3471
|
-
tmpDir;
|
|
3472
|
-
_exitHandler = null;
|
|
3473
|
-
/**
|
|
3474
|
-
* Original CDP endpoint (HTTP or ws URL) used to construct this browser.
|
|
3475
|
-
* Used by discoverContexts() as a fallback to HTTP /json/list when
|
|
3476
|
-
* Target.getTargets doesn't return page-type targets (e.g. cdp-tunnel proxy).
|
|
3477
|
-
*/
|
|
3478
|
-
cdpEndpoint;
|
|
3479
|
-
constructor(conn, childProcess, tmpDir, cdpEndpoint) {
|
|
3515
|
+
constructor(conn, childProcess, tmpDir, cdpEndpoint, launchOpts) {
|
|
3516
|
+
this.launchOpts = launchOpts;
|
|
3480
3517
|
this.conn = conn;
|
|
3481
3518
|
this.childProcess = childProcess ?? null;
|
|
3482
3519
|
this.tmpDir = tmpDir;
|
|
@@ -3504,6 +3541,20 @@ var XBBrowserImpl = class {
|
|
|
3504
3541
|
process.on("exit", this._exitHandler);
|
|
3505
3542
|
}
|
|
3506
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;
|
|
3507
3558
|
get disconnected() {
|
|
3508
3559
|
return this._disconnected;
|
|
3509
3560
|
}
|
|
@@ -3531,6 +3582,9 @@ var XBBrowserImpl = class {
|
|
|
3531
3582
|
this._emitter.emit("disconnected");
|
|
3532
3583
|
}
|
|
3533
3584
|
async newContext(opts = {}) {
|
|
3585
|
+
if (this.launchOpts?.stealthConfig && !opts.stealthConfig) {
|
|
3586
|
+
opts = { ...opts, stealthConfig: this.launchOpts.stealthConfig };
|
|
3587
|
+
}
|
|
3534
3588
|
if (this._disconnected) {
|
|
3535
3589
|
throw new Error("Browser is disconnected");
|
|
3536
3590
|
}
|
|
@@ -4084,7 +4138,13 @@ async function launch(options = {}) {
|
|
|
4084
4138
|
const conn = new CDPConnection(wsEndpoint);
|
|
4085
4139
|
await conn.ready();
|
|
4086
4140
|
const httpEndpoint = options.cdpEndpoint && !options.cdpEndpoint.startsWith("ws") ? options.cdpEndpoint : void 0;
|
|
4087
|
-
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
|
+
);
|
|
4088
4148
|
return { browser, wsEndpoint };
|
|
4089
4149
|
}
|
|
4090
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",
|