@xbrowser/cli 1.18.1 → 1.19.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-W3Z4HPJN.js → browser-6LH6KJSW.js} +1 -1
- package/dist/{browser-SYE57VHU.js → browser-TUD2KA5G.js} +2 -2
- package/dist/{browser-UPKOT3V6.js → browser-Y5SIE6LY.js} +2 -2
- package/dist/{cdp-driver-KT6HL33W.js → cdp-driver-2EECZW4X.js} +1 -1
- package/dist/{cdp-driver-4UAV56SQ.js → cdp-driver-7HO5SRNN.js} +1 -1
- package/dist/{cdp-driver-NNBPNFF2.js → cdp-driver-E7G3VHIB.js} +41 -3
- package/dist/{chunk-EI2XZIDF.js → chunk-2BPZ25RE.js} +72 -7
- package/dist/{chunk-MMZCBUD3.js → chunk-C6PW3CPA.js} +72 -7
- package/dist/{chunk-DVKIL7H6.js → chunk-GKYSCHT4.js} +41 -3
- package/dist/{chunk-OU335JBI.js → chunk-KLXLNQI5.js} +112 -9
- package/dist/{chunk-LL37GYPP.js → chunk-LKJZYVIX.js} +41 -3
- package/dist/cli.js +724 -14
- package/dist/daemon-main.js +732 -17
- package/dist/index.js +725 -15
- package/dist/{session-replayer-J4GXUULG.js → session-replayer-RODZYHM3.js} +1 -1
- package/package.json +1 -1
|
@@ -146,8 +146,17 @@ function buildStealthInitScript() {
|
|
|
146
146
|
" if(!e)return f.call(this,e);",
|
|
147
147
|
" return f.call(this,new Proxy(e,{get:function(k,p){",
|
|
148
148
|
' if(p==="sourceCapabilities")return fc;',
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
// S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
|
|
150
|
+
// 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
|
|
151
|
+
// 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
|
|
152
|
+
// + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
|
|
153
|
+
' if(p==="isTrusted"){',
|
|
154
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
155
|
+
" if(orig===true)return true;",
|
|
156
|
+
' if(k.type==="paste")return true;',
|
|
157
|
+
" return orig;",
|
|
158
|
+
" }",
|
|
159
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
|
|
151
160
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
|
|
152
161
|
" return k[p]+_f;",
|
|
153
162
|
" }",
|
|
@@ -157,6 +166,30 @@ function buildStealthInitScript() {
|
|
|
157
166
|
" return o.call(this,t,w,op);",
|
|
158
167
|
" };",
|
|
159
168
|
" EventTarget.prototype.addEventListener=_ael;",
|
|
169
|
+
// window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
|
|
170
|
+
// (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
|
|
171
|
+
" try{",
|
|
172
|
+
' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
|
|
173
|
+
" if(_weDesc&&_weDesc.get){",
|
|
174
|
+
" var _origWeGet=_weDesc.get;",
|
|
175
|
+
' Object.defineProperty(Window.prototype,"event",{',
|
|
176
|
+
" configurable:true,",
|
|
177
|
+
" get:function(){",
|
|
178
|
+
" var ev=_origWeGet.call(this);",
|
|
179
|
+
" if(!ev)return ev;",
|
|
180
|
+
" return new Proxy(ev,{get:function(k,p){",
|
|
181
|
+
' if(p==="isTrusted"){',
|
|
182
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
183
|
+
" if(orig===true)return true;",
|
|
184
|
+
' if(k.type==="paste")return true;',
|
|
185
|
+
" return orig;",
|
|
186
|
+
" }",
|
|
187
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
188
|
+
" }});",
|
|
189
|
+
" }",
|
|
190
|
+
" });",
|
|
191
|
+
" }",
|
|
192
|
+
" }catch(e){}",
|
|
160
193
|
// 2. Screen override (prototype-level, not instance-level)
|
|
161
194
|
" var _gw=function(){return 1728};",
|
|
162
195
|
" var _gh=function(){return 1117};",
|
|
@@ -414,7 +447,12 @@ function buildStealthInitScript() {
|
|
|
414
447
|
" };",
|
|
415
448
|
// 4. onclick prototype hijack (dual-stream consistency)
|
|
416
449
|
" var _ba=function(k,p){",
|
|
417
|
-
' if(p==="isTrusted")
|
|
450
|
+
' if(p==="isTrusted"){',
|
|
451
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
452
|
+
" if(orig===true)return true;",
|
|
453
|
+
' if(k.type==="paste")return true;',
|
|
454
|
+
" return orig;",
|
|
455
|
+
" }",
|
|
418
456
|
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
|
|
419
457
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
|
|
420
458
|
" }",
|
|
@@ -5373,7 +5411,9 @@ async function resolveCDPEndpoint(raw) {
|
|
|
5373
5411
|
}
|
|
5374
5412
|
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
5375
5413
|
try {
|
|
5376
|
-
const
|
|
5414
|
+
const u = new URL(raw);
|
|
5415
|
+
u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/version";
|
|
5416
|
+
const httpResp = await fetchNoProxy(u.toString());
|
|
5377
5417
|
const data = await httpResp.json();
|
|
5378
5418
|
if (!data.webSocketDebuggerUrl) {
|
|
5379
5419
|
throw new Error(`Could not discover CDP endpoint from ${raw}`);
|
|
@@ -5511,9 +5551,17 @@ function discoverChromiumPath() {
|
|
|
5511
5551
|
}
|
|
5512
5552
|
return void 0;
|
|
5513
5553
|
}
|
|
5554
|
+
var _browserPool = /* @__PURE__ */ new Map();
|
|
5514
5555
|
async function createBrowser(options) {
|
|
5515
5556
|
if (options?.cdpEndpoint) {
|
|
5516
5557
|
const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
|
|
5558
|
+
if (!options.intercept) {
|
|
5559
|
+
const pooled = _browserPool.get(realEndpoint);
|
|
5560
|
+
if (pooled && !pooled.disconnected) {
|
|
5561
|
+
return pooled;
|
|
5562
|
+
}
|
|
5563
|
+
if (pooled) _browserPool.delete(realEndpoint);
|
|
5564
|
+
}
|
|
5517
5565
|
if (options.intercept) {
|
|
5518
5566
|
const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
|
|
5519
5567
|
_sharedCdpProxy = new CDPInterceptorProxy(config);
|
|
@@ -5526,6 +5574,7 @@ async function createBrowser(options) {
|
|
|
5526
5574
|
await browser2.discoverContexts().catch((err) => {
|
|
5527
5575
|
console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
|
|
5528
5576
|
});
|
|
5577
|
+
if (!options.intercept) _browserPool.set(realEndpoint, browser2);
|
|
5529
5578
|
return browser2;
|
|
5530
5579
|
}
|
|
5531
5580
|
const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
|
|
@@ -5912,11 +5961,37 @@ async function createSession(name, url, options) {
|
|
|
5912
5961
|
}
|
|
5913
5962
|
context = contexts[0] || await b.newContext();
|
|
5914
5963
|
let targetPage = null;
|
|
5915
|
-
|
|
5964
|
+
const ownedUrls = /* @__PURE__ */ new Set();
|
|
5965
|
+
for (const ssn of sessions.list()) {
|
|
5966
|
+
try {
|
|
5967
|
+
const u = ssn.page?.url();
|
|
5968
|
+
if (u && u !== "about:blank") ownedUrls.add(u);
|
|
5969
|
+
} catch {
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
if (url) {
|
|
5973
|
+
for (const ctx of contexts) {
|
|
5974
|
+
for (const p of ctx.pages()) {
|
|
5975
|
+
try {
|
|
5976
|
+
const pUrl = await resolvePageUrl(p);
|
|
5977
|
+
if (ownedUrls.has(pUrl)) continue;
|
|
5978
|
+
if (pUrl === url) {
|
|
5979
|
+
targetPage = p;
|
|
5980
|
+
break;
|
|
5981
|
+
}
|
|
5982
|
+
} catch {
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
if (targetPage) break;
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
if (!targetPage && targetHostname) {
|
|
5916
5989
|
for (const ctx of contexts) {
|
|
5917
5990
|
const pages = ctx.pages();
|
|
5918
5991
|
for (const p of pages) {
|
|
5919
|
-
const
|
|
5992
|
+
const pUrl0 = await resolvePageUrl(p);
|
|
5993
|
+
if (ownedUrls.has(pUrl0)) continue;
|
|
5994
|
+
const pUrl = pUrl0;
|
|
5920
5995
|
if (isRealPageUrl(pUrl) && pUrl.includes(targetHostname)) {
|
|
5921
5996
|
targetPage = p;
|
|
5922
5997
|
break;
|
|
@@ -5929,7 +6004,9 @@ async function createSession(name, url, options) {
|
|
|
5929
6004
|
for (const ctx of contexts) {
|
|
5930
6005
|
const pages = ctx.pages();
|
|
5931
6006
|
for (const p of pages) {
|
|
5932
|
-
const
|
|
6007
|
+
const pUrl0 = await resolvePageUrl(p);
|
|
6008
|
+
if (ownedUrls.has(pUrl0)) continue;
|
|
6009
|
+
const pUrl = pUrl0;
|
|
5933
6010
|
if (isRealPageUrl(pUrl)) {
|
|
5934
6011
|
targetPage = p;
|
|
5935
6012
|
break;
|
|
@@ -5940,13 +6017,36 @@ async function createSession(name, url, options) {
|
|
|
5940
6017
|
}
|
|
5941
6018
|
if (!targetPage && options?.cdpEndpoint) {
|
|
5942
6019
|
const targets = await getCDPTargets2(options.cdpEndpoint);
|
|
6020
|
+
const ownedUrlsT = /* @__PURE__ */ new Set();
|
|
6021
|
+
for (const ssn of sessions.list()) {
|
|
6022
|
+
try {
|
|
6023
|
+
const u = ssn.page?.url();
|
|
6024
|
+
if (u && u !== "about:blank") ownedUrlsT.add(u);
|
|
6025
|
+
} catch {
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
5943
6028
|
const matchTarget = targets.find(
|
|
5944
|
-
(t) => t.url && t.url !== "about:blank" && !t.url.startsWith("chrome://") && !t.url.startsWith("chrome-untrusted://") && !t.url.startsWith("chrome-error://") && (url ? t.url.includes(new URL(url).hostname) : true)
|
|
6029
|
+
(t) => t.url && t.url !== "about:blank" && !t.url.startsWith("chrome://") && !t.url.startsWith("chrome-untrusted://") && !t.url.startsWith("chrome-error://") && !ownedUrlsT.has(t.url) && (url ? t.url === url || t.url.includes(new URL(url).hostname) : true)
|
|
5945
6030
|
);
|
|
5946
6031
|
if (matchTarget && matchTarget.url) {
|
|
6032
|
+
const ownedUrls2 = /* @__PURE__ */ new Set();
|
|
6033
|
+
for (const ssn of sessions.list()) {
|
|
6034
|
+
try {
|
|
6035
|
+
const u = ssn.page?.url();
|
|
6036
|
+
if (u && u !== "about:blank") ownedUrls2.add(u);
|
|
6037
|
+
} catch {
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
const isOwned = (p) => {
|
|
6041
|
+
try {
|
|
6042
|
+
return ownedUrls2.has(p.url());
|
|
6043
|
+
} catch {
|
|
6044
|
+
return false;
|
|
6045
|
+
}
|
|
6046
|
+
};
|
|
5947
6047
|
const existing2 = context.pages().find((p) => {
|
|
5948
6048
|
try {
|
|
5949
|
-
return p.url() === matchTarget.url;
|
|
6049
|
+
return p.url() === matchTarget.url && !isOwned(p);
|
|
5950
6050
|
} catch {
|
|
5951
6051
|
return false;
|
|
5952
6052
|
}
|
|
@@ -5964,7 +6064,10 @@ async function createSession(name, url, options) {
|
|
|
5964
6064
|
const pages = context.pages();
|
|
5965
6065
|
if (pages.length > 1) {
|
|
5966
6066
|
try {
|
|
6067
|
+
const owned = /* @__PURE__ */ new Set();
|
|
6068
|
+
for (const ssn of sessions.list()) owned.add(ssn.page);
|
|
5967
6069
|
for (const p of pages) {
|
|
6070
|
+
if (owned.has(p)) continue;
|
|
5968
6071
|
const vis = await p.evaluate("document.visibilityState").catch(() => "hidden");
|
|
5969
6072
|
if (vis === "visible") {
|
|
5970
6073
|
targetPage = p;
|
|
@@ -143,8 +143,17 @@ function buildStealthInitScript() {
|
|
|
143
143
|
" if(!e)return f.call(this,e);",
|
|
144
144
|
" return f.call(this,new Proxy(e,{get:function(k,p){",
|
|
145
145
|
' if(p==="sourceCapabilities")return fc;',
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
// S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
|
|
147
|
+
// 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
|
|
148
|
+
// 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
|
|
149
|
+
// + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
|
|
150
|
+
' if(p==="isTrusted"){',
|
|
151
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
152
|
+
" if(orig===true)return true;",
|
|
153
|
+
' if(k.type==="paste")return true;',
|
|
154
|
+
" return orig;",
|
|
155
|
+
" }",
|
|
156
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
|
|
148
157
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
|
|
149
158
|
" return k[p]+_f;",
|
|
150
159
|
" }",
|
|
@@ -154,6 +163,30 @@ function buildStealthInitScript() {
|
|
|
154
163
|
" return o.call(this,t,w,op);",
|
|
155
164
|
" };",
|
|
156
165
|
" EventTarget.prototype.addEventListener=_ael;",
|
|
166
|
+
// window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
|
|
167
|
+
// (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
|
|
168
|
+
" try{",
|
|
169
|
+
' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
|
|
170
|
+
" if(_weDesc&&_weDesc.get){",
|
|
171
|
+
" var _origWeGet=_weDesc.get;",
|
|
172
|
+
' Object.defineProperty(Window.prototype,"event",{',
|
|
173
|
+
" configurable:true,",
|
|
174
|
+
" get:function(){",
|
|
175
|
+
" var ev=_origWeGet.call(this);",
|
|
176
|
+
" if(!ev)return ev;",
|
|
177
|
+
" return new Proxy(ev,{get:function(k,p){",
|
|
178
|
+
' if(p==="isTrusted"){',
|
|
179
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
180
|
+
" if(orig===true)return true;",
|
|
181
|
+
' if(k.type==="paste")return true;',
|
|
182
|
+
" return orig;",
|
|
183
|
+
" }",
|
|
184
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
185
|
+
" }});",
|
|
186
|
+
" }",
|
|
187
|
+
" });",
|
|
188
|
+
" }",
|
|
189
|
+
" }catch(e){}",
|
|
157
190
|
// 2. Screen override (prototype-level, not instance-level)
|
|
158
191
|
" var _gw=function(){return 1728};",
|
|
159
192
|
" var _gh=function(){return 1117};",
|
|
@@ -411,7 +444,12 @@ function buildStealthInitScript() {
|
|
|
411
444
|
" };",
|
|
412
445
|
// 4. onclick prototype hijack (dual-stream consistency)
|
|
413
446
|
" var _ba=function(k,p){",
|
|
414
|
-
' if(p==="isTrusted")
|
|
447
|
+
' if(p==="isTrusted"){',
|
|
448
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
449
|
+
" if(orig===true)return true;",
|
|
450
|
+
' if(k.type==="paste")return true;',
|
|
451
|
+
" return orig;",
|
|
452
|
+
" }",
|
|
415
453
|
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
|
|
416
454
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
|
|
417
455
|
" }",
|