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