@xbrowser/cli 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -0
  2. package/dist/{browser-Y6B3NS4Q.js → browser-BVRFSUXU.js} +3 -2
  3. package/dist/{browser-NVC7MTML.js → browser-DSYUAE7H.js} +5 -5
  4. package/dist/{browser-PBBC7RAV.js → browser-Q67BJTI2.js} +6 -4
  5. package/dist/{cdp-driver-QYFA4QTP.js → cdp-driver-DC7EIUG5.js} +4 -3
  6. package/dist/{cdp-driver-Z2PLAO42.js → cdp-driver-T7D6H2RW.js} +368 -510
  7. package/dist/{cdp-driver-73F5CVH2.js → cdp-driver-WWO2UZDR.js} +4 -3
  8. package/dist/{chunk-76EKW7YJ.js → chunk-2Q3GQRUP.js} +3 -3
  9. package/dist/{chunk-UYEJ66VX.js → chunk-4OU37CXP.js} +387 -540
  10. package/dist/{chunk-H2A5JUK5.js → chunk-75DNUI6M.js} +9 -0
  11. package/dist/{chunk-FDU5BCPV.js → chunk-7OP2ISEY.js} +378 -540
  12. package/dist/{chunk-3FWLW7FS.js → chunk-A7NEA4PA.js} +39 -1
  13. package/dist/chunk-GYB5BOSP.js +602 -0
  14. package/dist/{chunk-T2KUVO4H.js → chunk-IFKJO2UR.js} +393 -546
  15. package/dist/{chunk-NODRQGOK.js → chunk-IR6C24P7.js} +97 -0
  16. package/dist/{chunk-3KZ34AUC.js → chunk-MYH6FIBZ.js} +2 -1
  17. package/dist/{chunk-75TLPVVZ.js → chunk-T4WTKBWI.js} +2 -2
  18. package/dist/{chunk-HBHOKZPN.js → chunk-V6KGBU5J.js} +97 -0
  19. package/dist/{chunk-IWVG4XYZ.js → chunk-VCVDILH6.js} +2 -1
  20. package/dist/{chunk-A6LPGFAL.js → chunk-WYETQ5C7.js} +1 -1
  21. package/dist/cli.js +25 -13
  22. package/dist/daemon-main.js +20 -14
  23. package/dist/index.d.ts +136 -1
  24. package/dist/index.js +29 -18
  25. package/dist/keep-awake-M4KJS4B4.js +29 -0
  26. package/dist/{launcher-44STYRJC.js → launcher-IK2FKW6T.js} +1 -1
  27. package/dist/{launcher-EXJHHAUL.js → launcher-LUOMMGZC.js} +1 -1
  28. package/dist/{proxy-LUR4U5YF.js → proxy-NKCYE76M.js} +2 -2
  29. package/dist/{session-recorder-SLDBENVF.js → session-recorder-563ISSJK.js} +1 -1
  30. package/dist/{session-recorder-3BEVWHOK.js → session-recorder-GEZVQBKW.js} +1 -1
  31. package/dist/session-replayer-XM5L7LFD.js +927 -0
  32. package/dist/stealth-2AA2FSNN.js +29 -0
  33. package/dist/stealth-JOXGV34D.js +29 -0
  34. package/package.json +2 -1
  35. package/dist/session-replayer-SBYJXQZZ.js +0 -413
@@ -99,8 +99,46 @@ function queryAllJS(selector) {
99
99
  }
100
100
  return `document.querySelectorAll(${JSON.stringify(selector)})`;
101
101
  }
102
+ function queryAllDeepJS(selector) {
103
+ return `(${deepQueryAllIIFE})( ${JSON.stringify(queryAllMainJS(selector))} )`;
104
+ }
105
+ function queryAllMainJS(selector) {
106
+ if (selector.startsWith("xpath=")) {
107
+ const xpath = JSON.stringify(selector.slice(6));
108
+ return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
109
+ }
110
+ if (selector.startsWith("text=") || selector.startsWith("popup-text=")) {
111
+ return `(() => { const el = (${queryMainJS(selector)}); return el ? [el] : []; })()`;
112
+ }
113
+ return `Array.from(document.querySelectorAll(${JSON.stringify(selector)}))`;
114
+ }
115
+ var deepQueryAllIIFE = `(function(allExpr) {
116
+ const run = (root) => {
117
+ // allExpr \u8FD4\u56DE\u7684\u662F\u6570\u7EC4\uFF08querySelectorAll \u5FEB\u7167\uFF09\u2014\u2014\u4E0D\u8981\u6309\u5355\u5143\u7D20\u5305\u88C5
118
+ try { return new Function('document', 'return (' + allExpr + ')')(root) || []; }
119
+ catch (e) { return []; }
120
+ };
121
+ const out = [];
122
+ const scanRoot = (root) => {
123
+ const found = run(root);
124
+ for (const el of found) out.push(el);
125
+ let all;
126
+ try { all = root.querySelectorAll('*'); } catch (e) { return; }
127
+ for (const el of all) {
128
+ if (el.shadowRoot) scanRoot(el.shadowRoot);
129
+ if (el.tagName === 'IFRAME') {
130
+ let inner = null;
131
+ try { inner = el.contentDocument; } catch (e) { /* cross-origin */ }
132
+ if (inner) scanRoot(inner);
133
+ }
134
+ }
135
+ };
136
+ scanRoot(document);
137
+ return out;
138
+ })`;
102
139
 
103
140
  export {
104
141
  queryJS,
105
- queryAllJS
142
+ queryAllJS,
143
+ queryAllDeepJS
106
144
  };
@@ -0,0 +1,602 @@
1
+ // src/cdp-driver/stealth.ts
2
+ var DEFAULT_STEALTH_CONFIG = {
3
+ uaChProfile: {
4
+ brands: [
5
+ { brand: "Not=A?Brand", version: "99" },
6
+ { brand: "Google Chrome", version: "151" },
7
+ { brand: "Chromium", version: "151" }
8
+ ],
9
+ platform: "macOS",
10
+ platformVersion: "26.2.0",
11
+ architecture: "arm",
12
+ bitness: "64",
13
+ model: "",
14
+ uaFullVersion: "151.0.7922.175",
15
+ fullVersionList: [
16
+ { brand: "Not=A?Brand", version: "99.0.0.0" },
17
+ { brand: "Google Chrome", version: "151.0.7922.175" },
18
+ { brand: "Chromium", version: "151.0.7922.175" }
19
+ ]
20
+ },
21
+ bezierCurvature: [0.35, 0.6],
22
+ noiseAmplitude: 5.5,
23
+ overshootRange: [6, 14],
24
+ aimPause: [80, 280],
25
+ pressDuration: [60, 140],
26
+ releaseDrift: [0.8, 2.5],
27
+ landingOffsetSmall: [0.3, 2.5],
28
+ landingOffsetLarge: [1.5, 7],
29
+ smallElementThreshold: 30,
30
+ typingRhythm: {
31
+ fastProb: 0.22,
32
+ fastRange: [25, 60],
33
+ normalRange: [50, 350],
34
+ pauseProb: 0.18,
35
+ pauseRange: [400, 1200]
36
+ },
37
+ keyPressDuration: [50, 110],
38
+ typoProbability: 0.06,
39
+ wheelPeak: 180,
40
+ wheelDecayRate: 0.4
41
+ };
42
+ function deriveUaChProfileForBinary(full, major) {
43
+ const base = DEFAULT_STEALTH_CONFIG.uaChProfile;
44
+ if (!base) return void 0;
45
+ const notGrease = (b) => !b.brand.includes("Not");
46
+ return {
47
+ ...base,
48
+ brands: base.brands.map(
49
+ (b) => notGrease(b) ? { ...b, version: major } : b
50
+ ),
51
+ uaFullVersion: full,
52
+ fullVersionList: base.fullVersionList.map(
53
+ (b) => notGrease(b) ? { ...b, version: full } : b
54
+ )
55
+ };
56
+ }
57
+ function rand(min, max) {
58
+ return min + Math.random() * (max - min);
59
+ }
60
+ function sleep(ms) {
61
+ return new Promise((resolve) => setTimeout(resolve, ms));
62
+ }
63
+ function cosineEase(t) {
64
+ return 0.5 - 0.5 * Math.cos(Math.PI * t);
65
+ }
66
+ function bezierTrajectory(x0, y0, x1, y1, config = DEFAULT_STEALTH_CONFIG) {
67
+ const dist = Math.hypot(x1 - x0, y1 - y0);
68
+ const n = Math.max(10, Math.min(28, Math.round(dist / 15)));
69
+ const shortMove = dist < 120;
70
+ const curvature = shortMove ? rand(2, 6) : Math.max(dist * rand(...config.bezierCurvature), rand(18, 35));
71
+ const dir = Math.random() < 0.5 ? 1 : -1;
72
+ const d = dist || 1;
73
+ const dx = x1 - x0, dy = y1 - y0;
74
+ const c1x = x0 + dx * 0.3 - dy / d * curvature * 0.5 * dir;
75
+ const c1y = y0 + dy * 0.3 + dx / d * curvature * 0.5 * dir;
76
+ const c2x = x0 + dx * 0.7 - dy / d * curvature * 0.8 * dir;
77
+ const c2y = y0 + dy * 0.7 + dx / d * curvature * 0.8 * dir;
78
+ const points = [];
79
+ for (let i = 1; i <= n; i++) {
80
+ const t = cosineEase(i / n);
81
+ const mt = 1 - t;
82
+ let px = mt ** 3 * x0 + 3 * mt ** 2 * t * c1x + 3 * mt * t ** 2 * c2x + t ** 3 * x1;
83
+ let py = mt ** 3 * y0 + 3 * mt ** 2 * t * c1y + 3 * mt * t ** 2 * c2y + t ** 3 * y1;
84
+ const amp = shortMove ? Math.min(2, config.noiseAmplitude) : config.noiseAmplitude;
85
+ px += rand(-amp, amp);
86
+ py += rand(-amp, amp);
87
+ points.push({ x: px, y: py, delay: rand(9, 16) });
88
+ }
89
+ if (!shortMove) {
90
+ const over = rand(...config.overshootRange);
91
+ const ox = x1 + dx / d * over + rand(-2, 2);
92
+ const oy = y1 + dy / d * over + rand(-2, 2);
93
+ points.push({ x: ox, y: oy, delay: rand(14, 30) });
94
+ points.push({
95
+ x: x1 + dx / d * over * 0.4,
96
+ y: y1 + dy / d * over * 0.4,
97
+ delay: rand(14, 30)
98
+ });
99
+ }
100
+ points.push({ x: x1 + rand(-1, 1), y: y1 + rand(-1, 1), delay: rand(14, 30) });
101
+ return points;
102
+ }
103
+ function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
104
+ const isSmall = Math.min(width, height) < config.smallElementThreshold;
105
+ const range = isSmall ? config.landingOffsetSmall : config.landingOffsetLarge;
106
+ const dx = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
107
+ const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
108
+ return { dx, dy };
109
+ }
110
+ function typingDelay(config = DEFAULT_STEALTH_CONFIG) {
111
+ const roll = Math.random();
112
+ const r = config.typingRhythm;
113
+ if (roll < r.pauseProb) return rand(...r.pauseRange);
114
+ if (roll < r.pauseProb + r.fastProb) return rand(...r.fastRange);
115
+ return rand(...r.normalRange);
116
+ }
117
+ function wheelDelta(step, config = DEFAULT_STEALTH_CONFIG) {
118
+ return Math.round(
119
+ config.wheelPeak * Math.exp(-step * config.wheelDecayRate) * rand(0.85, 1.15)
120
+ );
121
+ }
122
+ var KEY_MAP = {};
123
+ for (let i = 97; i <= 122; i++) {
124
+ const ch = String.fromCharCode(i);
125
+ KEY_MAP[ch] = { key: ch, code: "Key" + ch.toUpperCase(), vk: i - 32 };
126
+ }
127
+ for (let i = 65; i <= 90; i++) {
128
+ const ch = String.fromCharCode(i);
129
+ KEY_MAP[ch] = { key: ch, code: "Key" + ch, vk: i, shift: true };
130
+ }
131
+ for (let i = 48; i <= 57; i++) {
132
+ const ch = String.fromCharCode(i);
133
+ KEY_MAP[ch] = { key: ch, code: "Digit" + ch, vk: i };
134
+ }
135
+ Object.assign(KEY_MAP, {
136
+ " ": { key: " ", code: "Space", vk: 32 },
137
+ ".": { key: ".", code: "Period", vk: 190 },
138
+ "-": { key: "-", code: "Minus", vk: 189 },
139
+ "@": { key: "@", code: "Digit2", vk: 50, shift: true },
140
+ "_": { key: "_", code: "Minus", vk: 189, shift: true }
141
+ });
142
+ function lookupKey(ch) {
143
+ return KEY_MAP[ch];
144
+ }
145
+ function buildStealthInitScript(config = DEFAULT_STEALTH_CONFIG) {
146
+ const prof = config.uaChProfile;
147
+ const profJson = prof ? JSON.stringify(prof) : "null";
148
+ return [
149
+ "(function(){",
150
+ ' window.__xbStealthVer="57";',
151
+ " window.__xbUaChProf=" + profJson + ";",
152
+ // 1. AEL event proxy
153
+ " var o=EventTarget.prototype.addEventListener;",
154
+ " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
155
+ " var _ael=function(t,f){",
156
+ " var op=arguments[2];",
157
+ ' if(typeof f!=="function")return o.call(this,t,f,op);',
158
+ " var w=function(e){",
159
+ " if(!e)return f.call(this,e);",
160
+ " return f.call(this,new Proxy(e,{get:function(k,p){",
161
+ ' if(p==="sourceCapabilities")return fc;',
162
+ // S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
163
+ // 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
164
+ // 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
165
+ // + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
166
+ ' if(p==="isTrusted"){',
167
+ ' var orig=Reflect.get(k,"isTrusted");',
168
+ " if(orig===true)return true;",
169
+ ' if(k.type==="paste")return true;',
170
+ " return orig;",
171
+ " }",
172
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
173
+ " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
174
+ " return k[p]+_f;",
175
+ " }",
176
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
177
+ " }}));",
178
+ " };",
179
+ " return o.call(this,t,w,op);",
180
+ " };",
181
+ " EventTarget.prototype.addEventListener=_ael;",
182
+ // window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
183
+ // (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
184
+ " try{",
185
+ ' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
186
+ " if(_weDesc&&_weDesc.get){",
187
+ " var _origWeGet=_weDesc.get;",
188
+ ' Object.defineProperty(Window.prototype,"event",{',
189
+ " configurable:true,",
190
+ " get:function(){",
191
+ " var ev=_origWeGet.call(this);",
192
+ " if(!ev)return ev;",
193
+ " return new Proxy(ev,{get:function(k,p){",
194
+ ' if(p==="isTrusted"){',
195
+ ' var orig=Reflect.get(k,"isTrusted");',
196
+ " if(orig===true)return true;",
197
+ ' if(k.type==="paste")return true;',
198
+ " return orig;",
199
+ " }",
200
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
201
+ " }});",
202
+ " }",
203
+ " });",
204
+ " }",
205
+ " }catch(e){}",
206
+ // 2. Screen override (prototype-level, not instance-level)
207
+ " var _gw=function(){return 1728};",
208
+ " var _gh=function(){return 1117};",
209
+ " var _gah=function(){return 1092};",
210
+ ' Object.defineProperty(Screen.prototype,"width",{get:_gw,configurable:true});',
211
+ ' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
212
+ ' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
213
+ ' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
214
+ // S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
215
+ // (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
216
+ // 原型+实例双层覆写(S172 模式)。
217
+ " try{",
218
+ ' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
219
+ ' 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;});};',
220
+ ' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
221
+ " var wrapUAD=function(uad){",
222
+ " if(!uad||uad.__xbUA)return uad;",
223
+ " try{",
224
+ ' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
225
+ " var _ghev=uad.getHighEntropyValues.bind(uad);",
226
+ " uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
227
+ ' 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]);}',
228
+ " return o;});};",
229
+ " uad.__xbUA=true;",
230
+ " }catch(e){}",
231
+ " return uad;",
232
+ " };",
233
+ ' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
234
+ " if(_nua&&_nua.get){",
235
+ " var _nuag=function(){return fixUA(_nua.get.call(this));};",
236
+ ' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
237
+ ' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
238
+ " }",
239
+ ' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
240
+ " if(_nuad&&_nuad.get){",
241
+ " var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
242
+ ' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
243
+ ' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
244
+ " }",
245
+ // S190: userAgentData 垫片——headless Chrome 151 不暴露 UA-CH API,
246
+ // 缺失本身即 headless 指纹。从 UA 派生 brands/platform 垫片 + getHighEntropyValues。
247
+ " if(!navigator.userAgentData && window.__xbUaChProf){",
248
+ " var prof = window.__xbUaChProf;",
249
+ " var uad={",
250
+ ' brands:prof.brands.map(function(b){return {brand:b.brand,version:b.version.split(".")[0]}}),',
251
+ " mobile:false,",
252
+ " platform:prof.platform,",
253
+ " getHighEntropyValues:function(hints){",
254
+ " return Promise.resolve().then(function(){",
255
+ " var all={brands:uad.brands,mobile:false,platform:prof.platform,",
256
+ " platformVersion:prof.platformVersion,architecture:prof.architecture,bitness:prof.bitness,model:prof.model,",
257
+ " uaFullVersion:prof.uaFullVersion,",
258
+ " fullVersionList:prof.fullVersionList};",
259
+ " var out={};",
260
+ " (hints||[]).forEach(function(h){if(h in all)out[h]=all[h]});",
261
+ " return out;",
262
+ " });",
263
+ " },",
264
+ " toJSON:function(){return{brands:uad.brands,mobile:false,platform:prof.platform}}",
265
+ " };",
266
+ ' Object.defineProperty(navigator,"userAgentData",{get:function(){return uad},configurable:true});',
267
+ " }",
268
+ " }catch(e){}",
269
+ // hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
270
+ // —— 真实浏览器失焦/后台时 hasFocus=false
271
+ // S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
272
+ ' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
273
+ // 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
274
+ // Headless software raster differs subtly from Chrome GPU raster —
275
+ // toDataURL hashes fingerprint the rasterizer. Inject a stable
276
+ // (per-page-load) subpixel shift into fillText so hashes look like a
277
+ // distinct-but-consistent real GPU, and hide the HEADLESS tell in
278
+ // WebGL renderer strings.
279
+ " var _seed=Math.floor(Math.random()*2147483647);",
280
+ " var _prng=function(){_seed=(_seed*48271)%2147483647;return _seed/2147483647;};",
281
+ " var _dx=(_prng()*0.4-0.2).toFixed(3)*1, _dy=(_prng()*0.4-0.2).toFixed(3)*1;",
282
+ " var _fillText=CanvasRenderingContext2D.prototype.fillText;",
283
+ " CanvasRenderingContext2D.prototype.fillText=function(t,x,y,m){",
284
+ " return _fillText.call(this,t,x+_dx,y+_dy,m);",
285
+ " };",
286
+ " var _toDataURL=HTMLCanvasElement.prototype.toDataURL;",
287
+ " HTMLCanvasElement.prototype.toDataURL=function(){",
288
+ ' var ctx=this.getContext("2d");',
289
+ " if(ctx){var d=ctx.getImageData(0,0,Math.min(this.width,2),Math.min(this.height,2));",
290
+ " for(var i=0;i<d.data.length;i+=4){if(d.data[i+3]>0){d.data[i]^=1;break;}}",
291
+ " ctx.putImageData(d,0,0);}",
292
+ " return _toDataURL.apply(this,arguments);",
293
+ " };",
294
+ " try{",
295
+ " var _gl=HTMLCanvasElement.prototype.getContext;",
296
+ " HTMLCanvasElement.prototype.getContext=function(t,o){",
297
+ " var c=_gl.call(this,t,o);",
298
+ ' if(c&&(t==="webgl"||t==="experimental-webgl")&&c.getParameter){',
299
+ " var _gp=c.getParameter.bind(c);",
300
+ " c.getParameter=function(p){",
301
+ " var v=_gp(p);",
302
+ ' if(typeof v==="string"&&/SwiftShader|Software|Rasterizer|Headless/i.test(v))',
303
+ ' return "ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Max, Unspecified Version)";',
304
+ " return v;};}",
305
+ " return c;};",
306
+ " }catch(e){}",
307
+ // 5. AudioContext fingerprint: per-load stable micro-noise on channel
308
+ // data (d21). DSP sum differences between headless software audio and
309
+ // real hardware audio are a classic fingerprint — add ±1e-7 level
310
+ // noise (inaudible, changes the sum hash).
311
+ " try{",
312
+ " var _gcd=AudioBuffer.prototype.getChannelData;",
313
+ " AudioBuffer.prototype.getChannelData=function(ch){",
314
+ " var d=_gcd.call(this,ch);",
315
+ ' var key="__xb_audio_"+ch;',
316
+ " if(!this[key]){",
317
+ " this[key]=true;",
318
+ " for(var i=0;i<d.length;i+=997){d[i]=d[i]+(_prng()-0.5)*2e-7;}",
319
+ " }",
320
+ " return d;",
321
+ " };",
322
+ " var _gffd=AnalyserNode.prototype.getFloatFrequencyData;",
323
+ " AnalyserNode.prototype.getFloatFrequencyData=function(arr){",
324
+ " _gffd.call(this,arr);",
325
+ " for(var i=0;i<arr.length;i+=31){arr[i]=arr[i]+(_prng()-0.5)*0.01;}",
326
+ " };",
327
+ " var _gfbd=AnalyserNode.prototype.getByteFrequencyData;",
328
+ " AnalyserNode.prototype.getByteFrequencyData=function(arr){",
329
+ " _gfbd.call(this,arr);",
330
+ " for(var i=0;i<arr.length;i+=31){arr[i]=(arr[i]+((_prng()*3)|0))&255;}",
331
+ " };",
332
+ " }catch(e){}",
333
+ // 3b. Font metrics + speechSynthesis + battery (d22): headless tells
334
+ " try{",
335
+ ' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
336
+ " if(_tmw&&_tmw.get){",
337
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
338
+ ' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
339
+ " }",
340
+ " }catch(e){}",
341
+ " try{",
342
+ ' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
343
+ // S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
344
+ " var _sv=SpeechSynthesis.prototype.getVoices;",
345
+ " SpeechSynthesis.prototype.getVoices=function(){",
346
+ " var real=_sv.call(this);",
347
+ " if(real&&real.length>50)return real;",
348
+ // 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
349
+ // 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
350
+ ' 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};});',
351
+ " };",
352
+ // d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
353
+ // 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
354
+ " try{",
355
+ " var _piv=function(){",
356
+ ' var frs=document.querySelectorAll("iframe");',
357
+ " for(var i=0;i<frs.length;i++){try{",
358
+ " var cw=frs[i].contentWindow;",
359
+ " if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
360
+ " cw.speechSynthesis.__xbP=true;",
361
+ " var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
362
+ " cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
363
+ ' 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};});};',
364
+ " }catch(e2){}}",
365
+ " };",
366
+ " setInterval(_piv,2000);",
367
+ " var _mo=new MutationObserver(function(muts){",
368
+ " for(var k=0;k<muts.length;k++){var ns=muts[k].addedNodes;",
369
+ ' for(var q=0;q<ns.length;q++){if(ns[q].tagName==="IFRAME")setTimeout(_piv,20);}}',
370
+ " });",
371
+ " _mo.observe(document.documentElement,{childList:true,subtree:true});",
372
+ " }catch(e){}",
373
+ " }catch(e){}",
374
+ " try{",
375
+ " var _gb=navigator.getBattery.bind(navigator);",
376
+ " navigator.getBattery=function(){",
377
+ " return _gb().then(function(b){",
378
+ ' Object.defineProperty(b,"charging",{get:function(){return true;},configurable:true});',
379
+ " return b;});",
380
+ " };",
381
+ " }catch(e){}",
382
+ // 3c. WebRTC local IP leak guard (d23): headless STUN candidates can
383
+ // expose host IPs; mDNS-only candidates are the modern Chrome default.
384
+ " try{",
385
+ " var _oc=RTCPeerConnection.prototype.createOffer;",
386
+ " RTCPeerConnection.prototype.createOffer=function(){",
387
+ " var p=_oc.apply(this,arguments);",
388
+ " var self=this;",
389
+ " return p.then(function(offer){",
390
+ ' offer.sdp=offer.sdp.split(String.fromCharCode(10)).filter(function(l){return l.indexOf("typ host")<0}).join(String.fromCharCode(10));',
391
+ " return offer;});",
392
+ " };",
393
+ " }catch(e){}",
394
+ // 3d-2. performance.now precision clamp (d34): full-precision timestamps
395
+ // (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
396
+ " try{",
397
+ " var _pn=Performance.prototype.now;",
398
+ " // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
399
+ " Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
400
+ ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
401
+ " if(_pto&&_pto.get){",
402
+ " // 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",
403
+ " // \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",
404
+ " // \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",
405
+ " var _toff=Math.floor((_seed/2147483647)*600000-300000);",
406
+ " var _tog=function(){return _pto.get.call(performance)+_toff;};",
407
+ ' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
408
+ " // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
409
+ ' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
410
+ ' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
411
+ " }",
412
+ " }catch(e){}",
413
+ // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
414
+ // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
415
+ // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
416
+ // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
417
+ // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
418
+ // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
419
+ " try{",
420
+ " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
421
+ " var _gce=PointerEvent.prototype.getCoalescedEvents;",
422
+ " var _lm=null;",
423
+ " var _gceH=function(){",
424
+ " var list=_gce.call(this);",
425
+ ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
426
+ " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
427
+ " var out=null;",
428
+ " if((!list||list.length<2)&&_lm){",
429
+ " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
430
+ // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
431
+ // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
432
+ // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
433
+ " var expN=Math.floor(dt/8)-1;",
434
+ // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
435
+ // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
436
+ " if(expN>0&&dt>=12&&Math.random()>0.15){",
437
+ " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
438
+ " var span=Math.min(dt,n*8.3);",
439
+ " var frac=Math.min(1,span/Math.max(dt,1));",
440
+ " out=[];",
441
+ " for(var i=1;i<=n;i++){",
442
+ // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
443
+ " var k=n+1-i;",
444
+ ' var ev=new PointerEvent("pointermove",{',
445
+ ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
446
+ " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
447
+ " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
448
+ " screenX:this.screenX,screenY:this.screenY,",
449
+ " buttons:this.buttons,button:this.button,",
450
+ " bubbles:true,cancelable:true,composed:true,",
451
+ " width:this.width,height:this.height,pressure:this.pressure,",
452
+ " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
453
+ " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
454
+ // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
455
+ // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
456
+ // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
457
+ // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
458
+ // 合成事件同时间戳、群内间隔塌到 ~2ms)。
459
+ " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
460
+ ' if(p==="isTrusted")return true;',
461
+ ' if(p==="timeStamp")return t2;',
462
+ ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
463
+ " }});})(ev,tsV));",
464
+ " }",
465
+ " out.push(this);",
466
+ " }",
467
+ " }",
468
+ " _lm=cur;",
469
+ " return out||list;",
470
+ " };",
471
+ " PointerEvent.prototype.getCoalescedEvents=_gceH;",
472
+ // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
473
+ // "getCoalescedEvents")——toString 白名单管不到 name 属性。
474
+ ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
475
+ " }",
476
+ " }catch(e){}",
477
+ // 3d. Chrome object depth (d24): automation fakes usually only set
478
+ // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
479
+ " try{",
480
+ " if(window.chrome){",
481
+ ' 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"}};',
482
+ ' 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};',
483
+ " if(!window.chrome.csi)window.chrome.csi=function(){return{};}",
484
+ " if(!window.chrome.loadTimes)window.chrome.loadTimes=function(){return{};}",
485
+ " }",
486
+ " }catch(e){}",
487
+ // 3e. Font availability patch (d25): headless Chromium misses some system
488
+ // fonts (e.g. Menlo on macOS) that real Chrome has. Register a FontFace
489
+ // aliasing the missing font to a local() equivalent so offsetWidth-based
490
+ // font probing sees the same availability as the faked environment.
491
+ " try{",
492
+ ' var _fontAliases=[["Menlo","Courier New"],["SF Mono","Menlo"],["Segoe UI","Helvetica"]];',
493
+ " _fontAliases.forEach(function(pa){",
494
+ ' try{var ff=new FontFace(pa[0],"local("+JSON.stringify(pa[1])+")");',
495
+ " document.fonts.add(ff);ff.load();}catch(e2){}",
496
+ " });",
497
+ " }catch(e){}",
498
+ // 3g. Notification.requestPermission 延迟(d62):headless 无原生横幅,
499
+ // request 瞬回 denied —— 真机 request 会 pending 在横幅上数秒到分钟。
500
+ // 延迟 2-6s 再 resolve,模拟横幅期(用户"看了下然后拒绝")。
501
+ " try{",
502
+ " if(window.Notification&&Notification.requestPermission){",
503
+ " var _nrp=Notification.requestPermission.bind(Notification);",
504
+ " Notification.requestPermission=function(cb){",
505
+ " return new Promise(function(res){",
506
+ " setTimeout(function(){_nrp().then(function(r){if(cb)try{cb(r)}catch(e){}res(r);});},2000+Math.random()*4000);",
507
+ " });",
508
+ " };",
509
+ " }",
510
+ " }catch(e){}",
511
+ // 3. toString disguise (name-list based)
512
+ " var _ts=Function.prototype.toString;",
513
+ " var _hf=document.hasFocus;",
514
+ " Function.prototype.toString=function(){",
515
+ ' if(this===_ael)return"function addEventListener(type, callback) { [native code] }";',
516
+ ' if(this===_hf)return"function hasFocus() { [native code] }";',
517
+ ' if(this===_gw)return"function get width() { [native code] }";',
518
+ ' if(this===_gh)return"function get height() { [native code] }";',
519
+ ' if(this===_gah)return"function get availHeight() { [native code] }";',
520
+ ' if(this===CanvasRenderingContext2D.prototype.fillText)return"function fillText() { [native code] }";',
521
+ ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
522
+ ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
523
+ ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
524
+ ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
525
+ ' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
526
+ ' if(this===Performance.prototype.now)return"function now() { [native code] }";',
527
+ ' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
528
+ " return _ts.call(this);",
529
+ " };",
530
+ // 4. onclick prototype hijack (dual-stream consistency)
531
+ " var _ba=function(k,p){",
532
+ ' if(p==="isTrusted"){',
533
+ ' var orig=Reflect.get(k,"isTrusted");',
534
+ " if(orig===true)return true;",
535
+ ' if(k.type==="paste")return true;',
536
+ " return orig;",
537
+ " }",
538
+ ' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
539
+ " var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
540
+ " }",
541
+ ' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
542
+ " };",
543
+ ' Object.defineProperty(Document.prototype,"onclick",{',
544
+ " configurable:true,",
545
+ " get:function(){var raw=this.__ocRaw||null;if(!raw)return null;var self=this;",
546
+ " return function(e){return raw.call(self,new Proxy(e,{get:function(k,p){return _ba(k,p)}}))}},",
547
+ " set:function(fn){this.__ocRaw=fn}",
548
+ " });",
549
+ "})()"
550
+ ].join("\n");
551
+ }
552
+ var StealthInjector = class {
553
+ conn;
554
+ sessionId;
555
+ scriptIdentifier;
556
+ _enabled = false;
557
+ constructor(conn, sessionId) {
558
+ this.conn = conn;
559
+ this.sessionId = sessionId;
560
+ }
561
+ get enabled() {
562
+ return this._enabled;
563
+ }
564
+ /** Inject stealth hooks (call BEFORE Page.navigate) */
565
+ async inject() {
566
+ if (this._enabled) return;
567
+ const result = await this.conn.send(
568
+ "Page.addScriptToEvaluateOnNewDocument",
569
+ { source: buildStealthInitScript() },
570
+ this.sessionId
571
+ );
572
+ this.scriptIdentifier = result.identifier;
573
+ this._enabled = true;
574
+ }
575
+ /** Remove stealth hooks */
576
+ async remove() {
577
+ if (!this._enabled || !this.scriptIdentifier) return;
578
+ await this.conn.send(
579
+ "Page.removeScriptToEvaluateOnNewDocument",
580
+ { identifier: this.scriptIdentifier },
581
+ this.sessionId
582
+ ).catch(() => {
583
+ });
584
+ this.scriptIdentifier = void 0;
585
+ this._enabled = false;
586
+ }
587
+ };
588
+
589
+ export {
590
+ DEFAULT_STEALTH_CONFIG,
591
+ deriveUaChProfileForBinary,
592
+ rand,
593
+ sleep,
594
+ cosineEase,
595
+ bezierTrajectory,
596
+ landingOffset,
597
+ typingDelay,
598
+ wheelDelta,
599
+ lookupKey,
600
+ buildStealthInitScript,
601
+ StealthInjector
602
+ };