agent-browser-stealth 0.14.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 (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1219 -0
  3. package/bin/agent-browser-darwin-arm64 +0 -0
  4. package/bin/agent-browser-local +0 -0
  5. package/bin/agent-browser.js +109 -0
  6. package/dist/actions.d.ts +17 -0
  7. package/dist/actions.d.ts.map +1 -0
  8. package/dist/actions.js +1917 -0
  9. package/dist/actions.js.map +1 -0
  10. package/dist/browser.d.ts +598 -0
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +2287 -0
  13. package/dist/browser.js.map +1 -0
  14. package/dist/daemon.d.ts +66 -0
  15. package/dist/daemon.d.ts.map +1 -0
  16. package/dist/daemon.js +603 -0
  17. package/dist/daemon.js.map +1 -0
  18. package/dist/diff.d.ts +18 -0
  19. package/dist/diff.d.ts.map +1 -0
  20. package/dist/diff.js +271 -0
  21. package/dist/diff.js.map +1 -0
  22. package/dist/encryption.d.ts +50 -0
  23. package/dist/encryption.d.ts.map +1 -0
  24. package/dist/encryption.js +85 -0
  25. package/dist/encryption.js.map +1 -0
  26. package/dist/ios-actions.d.ts +11 -0
  27. package/dist/ios-actions.d.ts.map +1 -0
  28. package/dist/ios-actions.js +228 -0
  29. package/dist/ios-actions.js.map +1 -0
  30. package/dist/ios-manager.d.ts +266 -0
  31. package/dist/ios-manager.d.ts.map +1 -0
  32. package/dist/ios-manager.js +1073 -0
  33. package/dist/ios-manager.js.map +1 -0
  34. package/dist/protocol.d.ts +26 -0
  35. package/dist/protocol.d.ts.map +1 -0
  36. package/dist/protocol.js +935 -0
  37. package/dist/protocol.js.map +1 -0
  38. package/dist/snapshot.d.ts +67 -0
  39. package/dist/snapshot.d.ts.map +1 -0
  40. package/dist/snapshot.js +514 -0
  41. package/dist/snapshot.js.map +1 -0
  42. package/dist/state-utils.d.ts +77 -0
  43. package/dist/state-utils.d.ts.map +1 -0
  44. package/dist/state-utils.js +178 -0
  45. package/dist/state-utils.js.map +1 -0
  46. package/dist/stealth.d.ts +22 -0
  47. package/dist/stealth.d.ts.map +1 -0
  48. package/dist/stealth.js +614 -0
  49. package/dist/stealth.js.map +1 -0
  50. package/dist/stream-server.d.ts +117 -0
  51. package/dist/stream-server.d.ts.map +1 -0
  52. package/dist/stream-server.js +309 -0
  53. package/dist/stream-server.js.map +1 -0
  54. package/dist/types.d.ts +855 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +85 -0
  59. package/scripts/build-all-platforms.sh +68 -0
  60. package/scripts/check-creepjs-headless.js +137 -0
  61. package/scripts/check-sannysoft-webdriver.js +112 -0
  62. package/scripts/check-version-sync.js +39 -0
  63. package/scripts/copy-native.js +36 -0
  64. package/scripts/postinstall.js +275 -0
  65. package/scripts/sync-upstream.sh +142 -0
  66. package/scripts/sync-version.js +69 -0
  67. package/skills/agent-browser/SKILL.md +464 -0
  68. package/skills/agent-browser/references/authentication.md +202 -0
  69. package/skills/agent-browser/references/commands.md +263 -0
  70. package/skills/agent-browser/references/profiling.md +120 -0
  71. package/skills/agent-browser/references/proxy-support.md +194 -0
  72. package/skills/agent-browser/references/session-management.md +193 -0
  73. package/skills/agent-browser/references/snapshot-refs.md +194 -0
  74. package/skills/agent-browser/references/video-recording.md +173 -0
  75. package/skills/agent-browser/templates/authenticated-session.sh +100 -0
  76. package/skills/agent-browser/templates/capture-workflow.sh +69 -0
  77. package/skills/agent-browser/templates/form-automation.sh +62 -0
@@ -0,0 +1,614 @@
1
+ /**
2
+ * Stealth mode patches to prevent browser automation detection.
3
+ *
4
+ * These scripts run via addInitScript (before any page JS) and patch the
5
+ * fingerprinting surfaces that anti-bot systems use to identify Playwright /
6
+ * Puppeteer / headless Chrome.
7
+ */
8
+ /**
9
+ * Chromium args that reduce automation fingerprint.
10
+ * Intended to be merged into the user-supplied args array at launch time.
11
+ */
12
+ export const STEALTH_CHROMIUM_ARGS = ['--disable-blink-features=AutomationControlled'];
13
+ /**
14
+ * Apply all stealth patches to a BrowserContext.
15
+ * Must be called BEFORE any page is created / navigated.
16
+ */
17
+ export async function applyStealthScripts(context, options = {}) {
18
+ await context.addInitScript({ content: buildStealthScript(options) });
19
+ }
20
+ function normalizeLocale(locale) {
21
+ if (!locale)
22
+ return undefined;
23
+ const trimmed = locale.trim();
24
+ if (!trimmed)
25
+ return undefined;
26
+ const cleaned = trimmed.split(',')[0]?.split(';')[0]?.replace(/_/g, '-');
27
+ if (!cleaned)
28
+ return undefined;
29
+ try {
30
+ return new Intl.Locale(cleaned).toString();
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ function deriveLanguages(locale) {
37
+ const normalized = normalizeLocale(locale) ?? 'en-US';
38
+ const base = normalized.split('-')[0];
39
+ if (!base || base === normalized)
40
+ return [normalized];
41
+ return [normalized, base];
42
+ }
43
+ function buildStealthScript(options) {
44
+ const locale = normalizeLocale(options.locale) ?? 'en-US';
45
+ const languages = deriveLanguages(locale);
46
+ const configScript = `const __abStealth = ${JSON.stringify({ locale, languages })};`;
47
+ // Each patch is an IIFE so variable scoping is clean
48
+ return [
49
+ configScript,
50
+ patchNavigatorWebdriver(),
51
+ patchChromeRuntime(),
52
+ patchNavigatorLanguages(),
53
+ patchNavigatorPluginsAndMimeTypes(),
54
+ patchNavigatorPermissions(),
55
+ patchWebGLVendor(),
56
+ patchCdcProperties(),
57
+ patchWindowDimensions(),
58
+ patchScreenAvailability(),
59
+ patchNavigatorHardwareConcurrency(),
60
+ patchNavigatorConnection(),
61
+ patchNavigatorShare(),
62
+ patchNavigatorContacts(),
63
+ patchPdfViewerEnabled(),
64
+ patchMediaDevices(),
65
+ patchUserAgentData(),
66
+ patchUserAgent(),
67
+ patchPerformanceMemory(),
68
+ ].join('\n');
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Individual patches
72
+ // ---------------------------------------------------------------------------
73
+ /**
74
+ * Remove navigator.webdriver entirely.
75
+ * Modern detection checks both value and property presence (`'webdriver' in navigator`).
76
+ */
77
+ function patchNavigatorWebdriver() {
78
+ return `(function(){
79
+ const removeWebdriver = (target) => {
80
+ if (!target) return;
81
+ try { delete target.webdriver; } catch {}
82
+ };
83
+ removeWebdriver(navigator);
84
+ removeWebdriver(Object.getPrototypeOf(navigator));
85
+ removeWebdriver(Navigator.prototype);
86
+ if (typeof WorkerNavigator !== 'undefined') {
87
+ removeWebdriver(WorkerNavigator.prototype);
88
+ }
89
+ })();`;
90
+ }
91
+ /**
92
+ * Ensure window.chrome and window.chrome.runtime exist.
93
+ * Headless Chrome (and Playwright) omit chrome.runtime which is a dead giveaway.
94
+ */
95
+ function patchChromeRuntime() {
96
+ return `(function(){
97
+ if (!window.chrome) { window.chrome = {}; }
98
+ if (!window.chrome.runtime) {
99
+ const makeEvent = () => ({
100
+ addListener: () => {},
101
+ removeListener: () => {},
102
+ hasListener: () => false,
103
+ hasListeners: () => false,
104
+ dispatch: () => {},
105
+ });
106
+ const makePort = () => ({
107
+ name: '',
108
+ sender: undefined,
109
+ disconnect: () => {},
110
+ onDisconnect: makeEvent(),
111
+ onMessage: makeEvent(),
112
+ postMessage: () => {},
113
+ });
114
+ const runtime = {
115
+ id: undefined,
116
+ connect: () => makePort(),
117
+ sendMessage: () => undefined,
118
+ onConnect: makeEvent(),
119
+ onMessage: makeEvent(),
120
+ };
121
+ Object.defineProperty(window.chrome, 'runtime', {
122
+ value: runtime,
123
+ configurable: true,
124
+ });
125
+ }
126
+ })();`;
127
+ }
128
+ /**
129
+ * Keep navigator.language + navigator.languages aligned with launch locale.
130
+ */
131
+ function patchNavigatorLanguages() {
132
+ return `(function(){
133
+ const config = (typeof __abStealth === 'object' && __abStealth) ? __abStealth : null;
134
+ if (!config || !Array.isArray(config.languages) || config.languages.length === 0) return;
135
+ const locale = typeof config.locale === 'string' ? config.locale : config.languages[0];
136
+ try {
137
+ Object.defineProperty(navigator, 'language', {
138
+ get: () => locale,
139
+ configurable: true,
140
+ });
141
+ } catch {}
142
+ try {
143
+ Object.defineProperty(navigator, 'languages', {
144
+ get: () => config.languages.slice(),
145
+ configurable: true,
146
+ });
147
+ } catch {}
148
+ })();`;
149
+ }
150
+ /**
151
+ * Inject realistic navigator.plugins and navigator.mimeTypes arrays.
152
+ * Headless Chrome reports an empty PluginArray; real Chrome always has a few.
153
+ */
154
+ function patchNavigatorPluginsAndMimeTypes() {
155
+ return `(function(){
156
+ const makeMimeType = (type, suffixes, description) => {
157
+ const mime = Object.create(MimeType.prototype);
158
+ Object.defineProperties(mime, {
159
+ type: { value: type, enumerable: true },
160
+ suffixes: { value: suffixes, enumerable: true },
161
+ description: { value: description, enumerable: true },
162
+ enabledPlugin: { value: null, writable: true, enumerable: true },
163
+ });
164
+ return mime;
165
+ };
166
+
167
+ const makePlugin = (name, description, filename, mimes) => {
168
+ const plugin = Object.create(Plugin.prototype);
169
+ Object.defineProperties(plugin, {
170
+ name: { value: name, enumerable: true },
171
+ description: { value: description, enumerable: true },
172
+ filename: { value: filename, enumerable: true },
173
+ length: { value: mimes.length, enumerable: true },
174
+ });
175
+ mimes.forEach((mime, i) => {
176
+ Object.defineProperty(plugin, i, {
177
+ value: mime,
178
+ enumerable: true,
179
+ });
180
+ Object.defineProperty(plugin, mime.type, {
181
+ value: mime,
182
+ enumerable: false,
183
+ });
184
+ try { mime.enabledPlugin = plugin; } catch {}
185
+ });
186
+ return plugin;
187
+ };
188
+
189
+ const pdfMime = makeMimeType('application/pdf', 'pdf', 'Portable Document Format');
190
+ const chromePdfMime = makeMimeType(
191
+ 'application/x-google-chrome-pdf',
192
+ 'pdf',
193
+ 'Portable Document Format'
194
+ );
195
+ const naclMime = makeMimeType('application/x-nacl', '', 'Native Client Executable');
196
+ const pnaclMime = makeMimeType('application/x-pnacl', '', 'Portable Native Client Executable');
197
+
198
+ const plugins = [
199
+ makePlugin('Chrome PDF Plugin', 'Portable Document Format', 'internal-pdf-viewer', [chromePdfMime]),
200
+ makePlugin('Chrome PDF Viewer', '', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', [pdfMime]),
201
+ makePlugin('Native Client', '', 'internal-nacl-plugin', [naclMime, pnaclMime]),
202
+ ];
203
+ const pluginArray = Object.create(PluginArray.prototype);
204
+ plugins.forEach((p, i) => {
205
+ pluginArray[i] = p;
206
+ pluginArray[p.name] = p;
207
+ });
208
+ Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
209
+ pluginArray.item = (i) => plugins[i] || null;
210
+ pluginArray.namedItem = (name) => plugins.find(p => p.name === name) || null;
211
+ pluginArray.refresh = () => {};
212
+ pluginArray[Symbol.iterator] = function*() { for (const p of plugins) yield p; };
213
+
214
+ const mimeTypes = [chromePdfMime, pdfMime, naclMime, pnaclMime];
215
+ const mimeTypeArray = Object.create(MimeTypeArray.prototype);
216
+ mimeTypes.forEach((m, i) => {
217
+ mimeTypeArray[i] = m;
218
+ mimeTypeArray[m.type] = m;
219
+ });
220
+ Object.defineProperty(mimeTypeArray, 'length', { get: () => mimeTypes.length });
221
+ mimeTypeArray.item = (i) => mimeTypes[i] || null;
222
+ mimeTypeArray.namedItem = (name) => mimeTypes.find(m => m.type === name) || null;
223
+ mimeTypeArray[Symbol.iterator] = function*() { for (const m of mimeTypes) yield m; };
224
+
225
+ Object.defineProperty(navigator, 'plugins', {
226
+ get: () => pluginArray,
227
+ configurable: true,
228
+ });
229
+ Object.defineProperty(navigator, 'mimeTypes', {
230
+ get: () => mimeTypeArray,
231
+ configurable: true,
232
+ });
233
+ })();`;
234
+ }
235
+ /**
236
+ * navigator.permissions.query({name:'notifications'}) should resolve to
237
+ * 'denied' in a normal browser, but Playwright throws or returns 'prompt'.
238
+ */
239
+ function patchNavigatorPermissions() {
240
+ return `(function(){
241
+ if (!navigator.permissions || !navigator.permissions.query) return;
242
+ const origQuery = navigator.permissions.query.bind(navigator.permissions);
243
+ const makePermissionStatus = (state) => {
244
+ if (typeof PermissionStatus !== 'undefined') {
245
+ const status = Object.create(PermissionStatus.prototype);
246
+ Object.defineProperty(status, 'state', {
247
+ value: state,
248
+ writable: false,
249
+ enumerable: true,
250
+ });
251
+ Object.defineProperty(status, 'onchange', {
252
+ value: null,
253
+ writable: true,
254
+ enumerable: true,
255
+ });
256
+ return status;
257
+ }
258
+ return { state, onchange: null };
259
+ };
260
+ const patchedQuery = new Proxy(origQuery, {
261
+ apply(target, thisArg, argList) {
262
+ const params = argList && argList[0];
263
+ if (params && params.name === 'notifications') {
264
+ const state = (typeof Notification !== 'undefined' && Notification.permission) || 'default';
265
+ return Promise.resolve(makePermissionStatus(state));
266
+ }
267
+ return Reflect.apply(target, navigator.permissions, argList);
268
+ }
269
+ });
270
+ try {
271
+ Object.defineProperty(navigator.permissions, 'query', {
272
+ value: patchedQuery,
273
+ configurable: true,
274
+ writable: true,
275
+ });
276
+ } catch {}
277
+ })();`;
278
+ }
279
+ /**
280
+ * WebGL vendor/renderer: headless Chrome uses SwiftShader which is distinctive.
281
+ * Patch getParameter to return Intel GPU strings when SwiftShader is detected.
282
+ */
283
+ function patchWebGLVendor() {
284
+ return `(function(){
285
+ const getCtx = HTMLCanvasElement.prototype.getContext;
286
+ HTMLCanvasElement.prototype.getContext = function(type, attrs) {
287
+ const ctx = getCtx.call(this, type, attrs);
288
+ if (ctx && (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl')) {
289
+ const origGetParameter = ctx.getParameter.bind(ctx);
290
+ ctx.getParameter = function(param) {
291
+ const ext = ctx.getExtension('WEBGL_debug_renderer_info');
292
+ if (ext) {
293
+ if (param === ext.UNMASKED_VENDOR_WEBGL) {
294
+ const real = origGetParameter(param);
295
+ return (real && real.includes('SwiftShader')) ? 'Intel Inc.' : real;
296
+ }
297
+ if (param === ext.UNMASKED_RENDERER_WEBGL) {
298
+ const real = origGetParameter(param);
299
+ return (real && real.includes('SwiftShader')) ? 'Intel Iris OpenGL Engine' : real;
300
+ }
301
+ }
302
+ return origGetParameter(param);
303
+ };
304
+ }
305
+ return ctx;
306
+ };
307
+ })();`;
308
+ }
309
+ /**
310
+ * Remove Playwright's injected cdc_ (Chrome DevTools) properties on document.
311
+ * Some older detection scripts look for these on the document element.
312
+ */
313
+ function patchCdcProperties() {
314
+ return `(function(){
315
+ const clean = (target) => {
316
+ for (const key of Object.keys(target)) {
317
+ if (/^cdc_|^\\$cdc_/.test(key)) {
318
+ delete target[key];
319
+ }
320
+ }
321
+ };
322
+ clean(document);
323
+ if (document.documentElement) clean(document.documentElement);
324
+ })();`;
325
+ }
326
+ /**
327
+ * contentWindow on cross-origin iframes: Playwright sometimes returns null
328
+ * where real browsers return a (restricted) Window object.
329
+ */
330
+ function patchWindowDimensions() {
331
+ return `(function(){
332
+ const widthDelta = 12;
333
+ const heightDelta = 74;
334
+ const patchWidth =
335
+ !Number.isFinite(window.outerWidth) ||
336
+ window.outerWidth === 0 ||
337
+ Math.abs(window.outerWidth - window.innerWidth) <= 1;
338
+ const patchHeight =
339
+ !Number.isFinite(window.outerHeight) ||
340
+ window.outerHeight === 0 ||
341
+ Math.abs(window.outerHeight - window.innerHeight) <= 1;
342
+ if (patchWidth) {
343
+ try {
344
+ Object.defineProperty(window, 'outerWidth', {
345
+ get: () => Math.max(window.innerWidth + widthDelta, window.innerWidth),
346
+ configurable: true,
347
+ });
348
+ } catch {}
349
+ }
350
+ if (patchHeight) {
351
+ try {
352
+ Object.defineProperty(window, 'outerHeight', {
353
+ get: () => Math.max(window.innerHeight + heightDelta, window.innerHeight),
354
+ configurable: true,
355
+ });
356
+ } catch {}
357
+ }
358
+ const patchScreenPosition =
359
+ (!Number.isFinite(window.screenX) || !Number.isFinite(window.screenY)) ||
360
+ (window.screenX === 0 && window.screenY === 0 && (patchWidth || patchHeight));
361
+ if (patchScreenPosition) {
362
+ try {
363
+ Object.defineProperty(window, 'screenX', {
364
+ get: () => 16,
365
+ configurable: true,
366
+ });
367
+ Object.defineProperty(window, 'screenY', {
368
+ get: () => 72,
369
+ configurable: true,
370
+ });
371
+ Object.defineProperty(window, 'screenLeft', {
372
+ get: () => 16,
373
+ configurable: true,
374
+ });
375
+ Object.defineProperty(window, 'screenTop', {
376
+ get: () => 72,
377
+ configurable: true,
378
+ });
379
+ } catch {}
380
+ }
381
+ })();`;
382
+ }
383
+ /**
384
+ * Make Screen avail* values look like a desktop with taskbar/menu bar reserved space.
385
+ */
386
+ function patchScreenAvailability() {
387
+ return `(function(){
388
+ const patchNumber = (target, key, value) => {
389
+ try {
390
+ Object.defineProperty(target, key, {
391
+ get: () => value,
392
+ configurable: true,
393
+ });
394
+ } catch {}
395
+ };
396
+ const availWidth = Number(screen.availWidth);
397
+ const availHeight = Number(screen.availHeight);
398
+ const width = Number(screen.width);
399
+ const height = Number(screen.height);
400
+ if (Number.isFinite(width) && Number.isFinite(availWidth) && availWidth >= width) {
401
+ patchNumber(screen, 'availWidth', Math.max(width - 8, 0));
402
+ }
403
+ if (Number.isFinite(height) && Number.isFinite(availHeight) && availHeight >= height) {
404
+ patchNumber(screen, 'availHeight', Math.max(height - 40, 0));
405
+ }
406
+ if (Number.isFinite(screen.availLeft) && screen.availLeft === 0) {
407
+ patchNumber(screen, 'availLeft', 0);
408
+ }
409
+ if (Number.isFinite(screen.availTop) && screen.availTop === 0) {
410
+ patchNumber(screen, 'availTop', 24);
411
+ }
412
+ })();`;
413
+ }
414
+ /**
415
+ * navigator.hardwareConcurrency: headless often reports 2 (CI);
416
+ * real desktops typically have >= 4 cores.
417
+ */
418
+ function patchNavigatorHardwareConcurrency() {
419
+ return `(function(){
420
+ if (navigator.hardwareConcurrency < 4) {
421
+ Object.defineProperty(navigator, 'hardwareConcurrency', {
422
+ get: () => 4,
423
+ configurable: true,
424
+ });
425
+ }
426
+ })();`;
427
+ }
428
+ /**
429
+ * Add missing connection.downlinkMax in Chromium headless environments.
430
+ */
431
+ function patchNavigatorConnection() {
432
+ return `(function(){
433
+ if (!navigator.connection) return;
434
+ const conn = navigator.connection;
435
+ if (typeof conn.downlinkMax === 'number') return;
436
+ try {
437
+ Object.defineProperty(conn, 'downlinkMax', {
438
+ get: () => 10,
439
+ configurable: true,
440
+ });
441
+ } catch {}
442
+ })();`;
443
+ }
444
+ /**
445
+ * Add share/canShare APIs expected on modern Chromium desktop.
446
+ */
447
+ function patchNavigatorShare() {
448
+ return `(function(){
449
+ if (typeof navigator.share !== 'function') {
450
+ try {
451
+ Object.defineProperty(navigator, 'share', {
452
+ value: async () => undefined,
453
+ configurable: true,
454
+ });
455
+ } catch {}
456
+ }
457
+ if (typeof navigator.canShare !== 'function') {
458
+ try {
459
+ Object.defineProperty(navigator, 'canShare', {
460
+ value: () => true,
461
+ configurable: true,
462
+ });
463
+ } catch {}
464
+ }
465
+ })();`;
466
+ }
467
+ /**
468
+ * Add Contacts Manager stub to avoid "missing contacts manager" signals.
469
+ */
470
+ function patchNavigatorContacts() {
471
+ return `(function(){
472
+ if (navigator.contacts) return;
473
+ try {
474
+ Object.defineProperty(navigator, 'contacts', {
475
+ value: {
476
+ select: async () => [],
477
+ getProperties: () => ['name', 'email', 'tel', 'address', 'icon'],
478
+ },
479
+ configurable: true,
480
+ });
481
+ } catch {}
482
+ })();`;
483
+ }
484
+ /**
485
+ * Chromium exposes navigator.pdfViewerEnabled=true in normal browsing mode.
486
+ */
487
+ function patchPdfViewerEnabled() {
488
+ return `(function(){
489
+ if (navigator.pdfViewerEnabled === true) return;
490
+ try {
491
+ Object.defineProperty(navigator, 'pdfViewerEnabled', {
492
+ get: () => true,
493
+ configurable: true,
494
+ });
495
+ } catch {}
496
+ })();`;
497
+ }
498
+ /**
499
+ * navigator.mediaDevices.enumerateDevices should return at least some devices
500
+ * instead of an empty array (headless default).
501
+ */
502
+ function patchMediaDevices() {
503
+ return `(function(){
504
+ if (!navigator.mediaDevices) return;
505
+ const orig = navigator.mediaDevices.enumerateDevices;
506
+ if (!orig) return;
507
+ navigator.mediaDevices.enumerateDevices = async function() {
508
+ const devices = await orig.call(navigator.mediaDevices);
509
+ if (devices.length === 0) {
510
+ return [
511
+ { deviceId: 'default', kind: 'audioinput', label: '', groupId: 'default' },
512
+ { deviceId: 'default', kind: 'videoinput', label: '', groupId: 'default' },
513
+ { deviceId: 'default', kind: 'audiooutput', label: '', groupId: 'default' },
514
+ ];
515
+ }
516
+ return devices;
517
+ };
518
+ })();`;
519
+ }
520
+ /**
521
+ * Replace "HeadlessChrome" with "Chrome" in navigator.userAgent so
522
+ * UA-based detection is bypassed at the JavaScript level.
523
+ */
524
+ function patchUserAgent() {
525
+ return `(function(){
526
+ const ua = navigator.userAgent;
527
+ if (ua.includes('HeadlessChrome')) {
528
+ const patched = ua.replace(/HeadlessChrome/g, 'Chrome');
529
+ Object.defineProperty(navigator, 'userAgent', {
530
+ get: () => patched,
531
+ configurable: true,
532
+ });
533
+ Object.defineProperty(navigator, 'appVersion', {
534
+ get: () => patched.replace('Mozilla/', ''),
535
+ configurable: true,
536
+ });
537
+ }
538
+ })();`;
539
+ }
540
+ /**
541
+ * Ensure userAgentData does not expose "HeadlessChrome" brand tokens.
542
+ */
543
+ function patchUserAgentData() {
544
+ return `(function(){
545
+ const uaData = navigator.userAgentData;
546
+ if (!uaData) return;
547
+ const sanitizeBrand = (brand) => {
548
+ if (typeof brand !== 'string') return brand;
549
+ return brand.replace(/HeadlessChrome/gi, 'Google Chrome');
550
+ };
551
+ const patchBrandList = (value) => {
552
+ if (!Array.isArray(value)) return value;
553
+ return value.map((entry) => ({
554
+ ...entry,
555
+ brand: sanitizeBrand(entry.brand),
556
+ }));
557
+ };
558
+ const patched = Object.create(Object.getPrototypeOf(uaData));
559
+ Object.defineProperties(patched, {
560
+ brands: {
561
+ get: () => patchBrandList(uaData.brands),
562
+ enumerable: true,
563
+ },
564
+ mobile: {
565
+ get: () => uaData.mobile,
566
+ enumerable: true,
567
+ },
568
+ platform: {
569
+ get: () => uaData.platform,
570
+ enumerable: true,
571
+ },
572
+ });
573
+ patched.toJSON = () => ({
574
+ brands: patchBrandList(uaData.brands),
575
+ mobile: uaData.mobile,
576
+ platform: uaData.platform,
577
+ });
578
+ patched.getHighEntropyValues = async (hints) => {
579
+ const values = await uaData.getHighEntropyValues(hints);
580
+ if (values && typeof values === 'object') {
581
+ if ('brands' in values) values.brands = patchBrandList(values.brands);
582
+ if ('fullVersionList' in values) {
583
+ values.fullVersionList = patchBrandList(values.fullVersionList);
584
+ }
585
+ }
586
+ return values;
587
+ };
588
+ try {
589
+ Object.defineProperty(navigator, 'userAgentData', {
590
+ get: () => patched,
591
+ configurable: true,
592
+ });
593
+ } catch {}
594
+ })();`;
595
+ }
596
+ /**
597
+ * Provide a fake performance.memory (Chrome-only, non-standard).
598
+ * Headless Chrome omits this; some detectors check for its presence.
599
+ */
600
+ function patchPerformanceMemory() {
601
+ return `(function(){
602
+ if (!performance.memory) {
603
+ Object.defineProperty(performance, 'memory', {
604
+ get: () => ({
605
+ jsHeapSizeLimit: 2172649472,
606
+ totalJSHeapSize: 35839739,
607
+ usedJSHeapSize: 22592767,
608
+ }),
609
+ configurable: true,
610
+ });
611
+ }
612
+ })();`;
613
+ }
614
+ //# sourceMappingURL=stealth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stealth.js","sourceRoot":"","sources":["../src/stealth.ts"],"names":[],"mappings":"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"}