agent-browser-stealth 0.17.0-fork.2 → 0.24.0-fork.2
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/README.md +1256 -240
- package/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/bin/agent-browser-win32-x64.exe +0 -0
- package/bin/agent-browser.js +13 -2
- package/extensions/tab-group-cdp/content-script.js +425 -0
- package/extensions/tab-group-cdp/icons/icon.svg +7 -0
- package/extensions/tab-group-cdp/manifest.json +34 -0
- package/extensions/tab-group-cdp/page-bridge.js +133 -0
- package/extensions/tab-group-cdp/service-worker.js +2249 -0
- package/extensions/tab-group-cdp/sidepanel.css +258 -0
- package/extensions/tab-group-cdp/sidepanel.html +28 -0
- package/extensions/tab-group-cdp/sidepanel.js +1225 -0
- package/package.json +17 -69
- package/scripts/build-all-platforms.sh +6 -0
- package/scripts/check-version-sync.js +14 -2
- package/scripts/copy-native.js +8 -50
- package/scripts/postinstall.js +149 -165
- package/scripts/windows-debug/provision.sh +220 -0
- package/scripts/windows-debug/run.sh +92 -0
- package/scripts/windows-debug/start.sh +43 -0
- package/scripts/windows-debug/stop.sh +28 -0
- package/scripts/windows-debug/sync.sh +27 -0
- package/skills/agent-browser/SKILL.md +256 -159
- package/skills/agent-browser/references/authentication.md +101 -0
- package/skills/agent-browser/references/commands.md +34 -2
- package/skills/agent-browser/references/snapshot-refs.md +25 -0
- package/skills/agentcore/SKILL.md +115 -0
- package/skills/dogfood/SKILL.md +4 -2
- package/skills/electron/SKILL.md +26 -2
- package/skills/slack/SKILL.md +0 -9
- package/skills/slack/references/slack-tasks.md +2 -8
- package/skills/vercel-sandbox/SKILL.md +280 -0
- package/bin/agent-browser-local +0 -0
- package/bin/agent-browser-stealth +0 -0
- package/bin/agent-browser-stealth.d +0 -1
- package/dist/action-policy.d.ts +0 -14
- package/dist/action-policy.d.ts.map +0 -1
- package/dist/action-policy.js +0 -253
- package/dist/action-policy.js.map +0 -1
- package/dist/actions.d.ts +0 -21
- package/dist/actions.d.ts.map +0 -1
- package/dist/actions.js +0 -2139
- package/dist/actions.js.map +0 -1
- package/dist/auth-cli.d.ts +0 -2
- package/dist/auth-cli.d.ts.map +0 -1
- package/dist/auth-cli.js +0 -97
- package/dist/auth-cli.js.map +0 -1
- package/dist/auth-vault.d.ts +0 -36
- package/dist/auth-vault.d.ts.map +0 -1
- package/dist/auth-vault.js +0 -125
- package/dist/auth-vault.js.map +0 -1
- package/dist/browser.d.ts +0 -665
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -3210
- package/dist/browser.js.map +0 -1
- package/dist/confirmation.d.ts +0 -8
- package/dist/confirmation.d.ts.map +0 -1
- package/dist/confirmation.js +0 -30
- package/dist/confirmation.js.map +0 -1
- package/dist/daemon.d.ts +0 -78
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js +0 -744
- package/dist/daemon.js.map +0 -1
- package/dist/diff.d.ts +0 -18
- package/dist/diff.d.ts.map +0 -1
- package/dist/diff.js +0 -271
- package/dist/diff.js.map +0 -1
- package/dist/domain-filter.d.ts +0 -28
- package/dist/domain-filter.d.ts.map +0 -1
- package/dist/domain-filter.js +0 -149
- package/dist/domain-filter.js.map +0 -1
- package/dist/encryption.d.ts +0 -73
- package/dist/encryption.d.ts.map +0 -1
- package/dist/encryption.js +0 -171
- package/dist/encryption.js.map +0 -1
- package/dist/ios-actions.d.ts +0 -11
- package/dist/ios-actions.d.ts.map +0 -1
- package/dist/ios-actions.js +0 -228
- package/dist/ios-actions.js.map +0 -1
- package/dist/ios-manager.d.ts +0 -266
- package/dist/ios-manager.d.ts.map +0 -1
- package/dist/ios-manager.js +0 -1073
- package/dist/ios-manager.js.map +0 -1
- package/dist/protocol.d.ts +0 -26
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -990
- package/dist/protocol.js.map +0 -1
- package/dist/snapshot.d.ts +0 -67
- package/dist/snapshot.d.ts.map +0 -1
- package/dist/snapshot.js +0 -514
- package/dist/snapshot.js.map +0 -1
- package/dist/state-utils.d.ts +0 -77
- package/dist/state-utils.d.ts.map +0 -1
- package/dist/state-utils.js +0 -178
- package/dist/state-utils.js.map +0 -1
- package/dist/stealth.d.ts +0 -41
- package/dist/stealth.d.ts.map +0 -1
- package/dist/stealth.js +0 -1743
- package/dist/stealth.js.map +0 -1
- package/dist/stream-server.d.ts +0 -117
- package/dist/stream-server.d.ts.map +0 -1
- package/dist/stream-server.js +0 -309
- package/dist/stream-server.js.map +0 -1
- package/dist/types.d.ts +0 -973
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/scripts/check-creepjs-headless.js +0 -137
- package/scripts/check-daemon-pid-recovery.js +0 -148
- package/scripts/check-sannysoft-webdriver.js +0 -112
- package/scripts/check-stealth-regression.js +0 -199
- package/scripts/check-turnstile-testkey.ts +0 -125
- package/scripts/clawhub-sync.sh +0 -27
- package/scripts/sync-upstream.sh +0 -142
- package/scripts/verify-bundled-binaries.js +0 -71
- package/scripts/verify-native-version.js +0 -48
- package/scripts/verify-packed-host-binary.js +0 -88
- package/scripts/verify-registry-host-binary.js +0 -120
- package/skills/agent-browser-stealth/SKILL.md +0 -127
package/dist/stealth.js
DELETED
|
@@ -1,1743 +0,0 @@
|
|
|
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 = [
|
|
13
|
-
'--disable-blink-features=AutomationControlled',
|
|
14
|
-
'--use-gl=angle',
|
|
15
|
-
'--use-angle=default',
|
|
16
|
-
];
|
|
17
|
-
const CDP_SOURCE_URL_SANITIZED = Symbol('ab.cdpSourceUrlSanitized');
|
|
18
|
-
function stripSourceUrlLabels(input) {
|
|
19
|
-
let output = input;
|
|
20
|
-
output = output.replace(/\n?\s*\/\/[@#]\s*sourceURL=[^\n\r]*/gi, '');
|
|
21
|
-
output = output.replace(/\n?\s*\/\*[@#]\s*sourceURL=[\s\S]*?\*\//gi, '');
|
|
22
|
-
return output;
|
|
23
|
-
}
|
|
24
|
-
function sanitizeCdpPayload(method, params) {
|
|
25
|
-
if (!params || typeof params !== 'object')
|
|
26
|
-
return params;
|
|
27
|
-
const sanitizeField = (payload, field) => {
|
|
28
|
-
const value = payload[field];
|
|
29
|
-
if (typeof value !== 'string')
|
|
30
|
-
return payload;
|
|
31
|
-
const cleaned = stripSourceUrlLabels(value);
|
|
32
|
-
if (cleaned === value)
|
|
33
|
-
return payload;
|
|
34
|
-
return { ...payload, [field]: cleaned };
|
|
35
|
-
};
|
|
36
|
-
switch (method) {
|
|
37
|
-
case 'Runtime.evaluate':
|
|
38
|
-
case 'Runtime.compileScript':
|
|
39
|
-
return sanitizeField(params, 'expression');
|
|
40
|
-
case 'Runtime.callFunctionOn':
|
|
41
|
-
return sanitizeField(params, 'functionDeclaration');
|
|
42
|
-
case 'Page.addScriptToEvaluateOnNewDocument':
|
|
43
|
-
return sanitizeField(params, 'source');
|
|
44
|
-
default:
|
|
45
|
-
return params;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Patch CDPSession.send so Runtime/Page script payloads no longer carry
|
|
50
|
-
* sourceURL labels that reveal automation internals.
|
|
51
|
-
*/
|
|
52
|
-
export function wrapCDPSessionSourceUrlSanitizer(session) {
|
|
53
|
-
if (!session || typeof session.send !== 'function')
|
|
54
|
-
return session;
|
|
55
|
-
if (session[CDP_SOURCE_URL_SANITIZED])
|
|
56
|
-
return session;
|
|
57
|
-
const nativeSend = session.send.bind(session);
|
|
58
|
-
const wrappedSend = (method, params) => {
|
|
59
|
-
return nativeSend(method, sanitizeCdpPayload(method, params));
|
|
60
|
-
};
|
|
61
|
-
try {
|
|
62
|
-
Object.defineProperty(session, 'send', {
|
|
63
|
-
value: wrappedSend,
|
|
64
|
-
configurable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
try {
|
|
70
|
-
session.send = wrappedSend;
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return session;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
Object.defineProperty(session, CDP_SOURCE_URL_SANITIZED, {
|
|
78
|
-
value: true,
|
|
79
|
-
configurable: false,
|
|
80
|
-
enumerable: false,
|
|
81
|
-
writable: false,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
session[CDP_SOURCE_URL_SANITIZED] = true;
|
|
86
|
-
}
|
|
87
|
-
return session;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Apply all stealth patches to a BrowserContext.
|
|
91
|
-
* Must be called BEFORE any page is created / navigated.
|
|
92
|
-
*/
|
|
93
|
-
export async function applyStealthScripts(context, options = {}) {
|
|
94
|
-
await context.addInitScript({ content: buildStealthScript(options) });
|
|
95
|
-
// Apply CDP-level User-Agent override so Workers also get the patched UA.
|
|
96
|
-
// This must be done per-page since CDP sessions are page-scoped.
|
|
97
|
-
for (const page of context.pages()) {
|
|
98
|
-
await applyCDPStealthToPage(page, options);
|
|
99
|
-
}
|
|
100
|
-
context.on('page', (page) => applyCDPStealthToPage(page, options));
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Apply browser-level CDP overrides that affect all targets (including Workers).
|
|
104
|
-
* Call this right after browser.launch() and before creating pages.
|
|
105
|
-
*/
|
|
106
|
-
export async function applyBrowserLevelStealth(browser, options = {}) {
|
|
107
|
-
try {
|
|
108
|
-
const cdp = wrapCDPSessionSourceUrlSanitizer(await browser.newBrowserCDPSession());
|
|
109
|
-
const version = await cdp.send('Browser.getVersion');
|
|
110
|
-
const rawUA = version?.userAgent ?? '';
|
|
111
|
-
const explicitUA = options.userAgent?.trim();
|
|
112
|
-
if (!explicitUA && !rawUA.includes('HeadlessChrome')) {
|
|
113
|
-
await cdp.detach();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const patchedUA = explicitUA || rawUA.replace(/HeadlessChrome/g, 'Chrome');
|
|
117
|
-
const acceptLanguage = options.acceptLanguage ?? 'en-US,en;q=0.9';
|
|
118
|
-
const metadata = buildUserAgentMetadata(patchedUA);
|
|
119
|
-
// Override on all existing targets
|
|
120
|
-
const { targetInfos } = await cdp.send('Target.getTargets');
|
|
121
|
-
for (const target of targetInfos) {
|
|
122
|
-
try {
|
|
123
|
-
const { sessionId } = await cdp.send('Target.attachToTarget', {
|
|
124
|
-
targetId: target.targetId,
|
|
125
|
-
flatten: true,
|
|
126
|
-
});
|
|
127
|
-
await cdp.send('Emulation.setUserAgentOverride', {
|
|
128
|
-
userAgent: patchedUA,
|
|
129
|
-
acceptLanguage,
|
|
130
|
-
platform: getPlatformString(),
|
|
131
|
-
userAgentMetadata: metadata,
|
|
132
|
-
});
|
|
133
|
-
await cdp.send('Target.detachFromTarget', { sessionId }).catch(() => { });
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// Some targets don't support Emulation domain
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
await cdp.detach();
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// newBrowserCDPSession not available -- silently skip
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
async function applyCDPStealthToPage(page, options = {}) {
|
|
146
|
-
try {
|
|
147
|
-
const cdp = wrapCDPSessionSourceUrlSanitizer(await page.context().newCDPSession(page));
|
|
148
|
-
const ua = await cdp.send('Browser.getVersion').catch(() => null);
|
|
149
|
-
const rawUA = ua?.userAgent ?? '';
|
|
150
|
-
const explicitUA = options.userAgent?.trim();
|
|
151
|
-
const patchedUA = explicitUA || rawUA.replace(/HeadlessChrome/g, 'Chrome');
|
|
152
|
-
const acceptLanguage = options.acceptLanguage ?? 'en-US,en;q=0.9';
|
|
153
|
-
const metadata = buildUserAgentMetadata(patchedUA);
|
|
154
|
-
await cdp.send('Emulation.setUserAgentOverride', {
|
|
155
|
-
userAgent: patchedUA,
|
|
156
|
-
acceptLanguage,
|
|
157
|
-
platform: getPlatformString(),
|
|
158
|
-
userAgentMetadata: metadata,
|
|
159
|
-
});
|
|
160
|
-
// Set default background color to opaque white (headless default is transparent)
|
|
161
|
-
await cdp
|
|
162
|
-
.send('Emulation.setDefaultBackgroundColorOverride', {
|
|
163
|
-
color: { r: 255, g: 255, b: 255, a: 1 },
|
|
164
|
-
})
|
|
165
|
-
.catch(() => { });
|
|
166
|
-
// Keep CDP session alive so the override persists for Workers
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// CDP not available (non-Chromium) -- silently skip
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
function buildUserAgentMetadata(patchedUA) {
|
|
173
|
-
const versionMatch = patchedUA.match(/Chrome\/(\d+)/);
|
|
174
|
-
const majorVersion = versionMatch?.[1] ?? '120';
|
|
175
|
-
const fullVersionMatch = patchedUA.match(/Chrome\/([\d.]+)/);
|
|
176
|
-
const fullVersion = fullVersionMatch?.[1] ?? `${majorVersion}.0.0.0`;
|
|
177
|
-
return {
|
|
178
|
-
brands: [
|
|
179
|
-
{ brand: 'Chromium', version: majorVersion },
|
|
180
|
-
{ brand: 'Google Chrome', version: majorVersion },
|
|
181
|
-
{ brand: 'Not-A.Brand', version: '99' },
|
|
182
|
-
],
|
|
183
|
-
fullVersionList: [
|
|
184
|
-
{ brand: 'Chromium', version: fullVersion },
|
|
185
|
-
{ brand: 'Google Chrome', version: fullVersion },
|
|
186
|
-
{ brand: 'Not-A.Brand', version: '99.0.0.0' },
|
|
187
|
-
],
|
|
188
|
-
fullVersion: fullVersion,
|
|
189
|
-
platform: getPlatformHint(),
|
|
190
|
-
platformVersion: getPlatformVersionHint(),
|
|
191
|
-
architecture: 'x86',
|
|
192
|
-
model: '',
|
|
193
|
-
mobile: false,
|
|
194
|
-
bitness: '64',
|
|
195
|
-
wow64: false,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function getPlatformString() {
|
|
199
|
-
if (typeof process !== 'undefined') {
|
|
200
|
-
if (process.platform === 'darwin')
|
|
201
|
-
return 'macOS';
|
|
202
|
-
if (process.platform === 'win32')
|
|
203
|
-
return 'Windows';
|
|
204
|
-
}
|
|
205
|
-
return 'Linux';
|
|
206
|
-
}
|
|
207
|
-
function getPlatformHint() {
|
|
208
|
-
if (typeof process !== 'undefined') {
|
|
209
|
-
if (process.platform === 'darwin')
|
|
210
|
-
return 'macOS';
|
|
211
|
-
if (process.platform === 'win32')
|
|
212
|
-
return 'Windows';
|
|
213
|
-
}
|
|
214
|
-
return 'Linux';
|
|
215
|
-
}
|
|
216
|
-
function getPlatformVersionHint() {
|
|
217
|
-
if (typeof process !== 'undefined') {
|
|
218
|
-
if (process.platform === 'darwin')
|
|
219
|
-
return '13.0.0';
|
|
220
|
-
if (process.platform === 'win32')
|
|
221
|
-
return '10.0.0';
|
|
222
|
-
}
|
|
223
|
-
return '6.1.0';
|
|
224
|
-
}
|
|
225
|
-
function normalizeLocale(locale) {
|
|
226
|
-
if (!locale)
|
|
227
|
-
return undefined;
|
|
228
|
-
const trimmed = locale.trim();
|
|
229
|
-
if (!trimmed)
|
|
230
|
-
return undefined;
|
|
231
|
-
const cleaned = trimmed.split(',')[0]?.split(';')[0]?.replace(/_/g, '-');
|
|
232
|
-
if (!cleaned)
|
|
233
|
-
return undefined;
|
|
234
|
-
try {
|
|
235
|
-
return new Intl.Locale(cleaned).toString();
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function deriveLanguages(locale) {
|
|
242
|
-
const normalized = normalizeLocale(locale) ?? 'en-US';
|
|
243
|
-
const base = normalized.split('-')[0];
|
|
244
|
-
if (!base || base === normalized)
|
|
245
|
-
return [normalized];
|
|
246
|
-
return [normalized, base];
|
|
247
|
-
}
|
|
248
|
-
function buildStealthScript(options) {
|
|
249
|
-
const locale = normalizeLocale(options.locale) ?? 'en-US';
|
|
250
|
-
const languages = deriveLanguages(locale);
|
|
251
|
-
const configScript = `const __abStealth = ${JSON.stringify({
|
|
252
|
-
locale,
|
|
253
|
-
languages,
|
|
254
|
-
allowWebGLContextFallback: options.allowWebGLContextFallback === true,
|
|
255
|
-
})};`;
|
|
256
|
-
// Each patch is an IIFE so variable scoping is clean
|
|
257
|
-
return [
|
|
258
|
-
configScript,
|
|
259
|
-
patchNavigatorWebdriver(),
|
|
260
|
-
patchCssSupportsWebdriverHeuristic(),
|
|
261
|
-
patchDisableDevtoolAutoBootstrap(),
|
|
262
|
-
patchChromeRuntime(),
|
|
263
|
-
patchChromeLegacyApis(),
|
|
264
|
-
patchIframeContentWindow(),
|
|
265
|
-
patchNavigatorLanguages(),
|
|
266
|
-
patchNavigatorVendor(),
|
|
267
|
-
patchNavigatorPluginsAndMimeTypes(),
|
|
268
|
-
patchNavigatorPermissions(),
|
|
269
|
-
patchWebGLVendor(),
|
|
270
|
-
patchCdcProperties(),
|
|
271
|
-
patchSourceUrlStackTraces(),
|
|
272
|
-
patchWindowDimensions(),
|
|
273
|
-
patchScreenDimensions(),
|
|
274
|
-
patchScreenAvailability(),
|
|
275
|
-
patchNavigatorHardwareConcurrency(),
|
|
276
|
-
patchNotificationPermission(),
|
|
277
|
-
patchActiveTextColorHeuristic(),
|
|
278
|
-
patchNavigatorConnection(),
|
|
279
|
-
patchWorkerConnection(),
|
|
280
|
-
patchNavigatorShare(),
|
|
281
|
-
patchNavigatorContacts(),
|
|
282
|
-
patchContentIndex(),
|
|
283
|
-
patchPrefersColorSchemeHeuristic(),
|
|
284
|
-
patchPdfViewerEnabled(),
|
|
285
|
-
patchMediaCodecs(),
|
|
286
|
-
patchMediaDevices(),
|
|
287
|
-
patchUserAgentData(),
|
|
288
|
-
patchUserAgent(),
|
|
289
|
-
patchPerformanceMemory(),
|
|
290
|
-
patchDefaultBackgroundColor(),
|
|
291
|
-
].join('\n');
|
|
292
|
-
}
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
// Individual patches
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
/**
|
|
297
|
-
* CreepJS uses CSS.supports('border-end-end-radius: initial') + webdriver
|
|
298
|
-
* undefined to infer automation. Keep this one probe neutral.
|
|
299
|
-
*/
|
|
300
|
-
function patchCssSupportsWebdriverHeuristic() {
|
|
301
|
-
return `(function(){
|
|
302
|
-
if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return;
|
|
303
|
-
const nativeSupports = CSS.supports.bind(CSS);
|
|
304
|
-
const normalize = (value) => String(value).replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
305
|
-
const target = 'border-end-end-radius: initial';
|
|
306
|
-
const patchedSupports = function(...args) {
|
|
307
|
-
if (args.length === 1 && normalize(args[0]) === target) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
if (args.length >= 2 && normalize(args[0] + ': ' + args[1]) === target) {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
return nativeSupports(...args);
|
|
314
|
-
};
|
|
315
|
-
try {
|
|
316
|
-
Object.defineProperty(patchedSupports, 'name', { value: 'supports', configurable: true });
|
|
317
|
-
Object.defineProperty(patchedSupports, 'toString', {
|
|
318
|
-
value: () => nativeSupports.toString(),
|
|
319
|
-
configurable: true,
|
|
320
|
-
});
|
|
321
|
-
} catch {}
|
|
322
|
-
try {
|
|
323
|
-
Object.defineProperty(CSS, 'supports', {
|
|
324
|
-
value: patchedSupports,
|
|
325
|
-
configurable: true,
|
|
326
|
-
writable: true,
|
|
327
|
-
});
|
|
328
|
-
} catch {
|
|
329
|
-
try { CSS.supports = patchedSupports; } catch {}
|
|
330
|
-
}
|
|
331
|
-
})();`;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Some high-risk sites ship disable-devtool with auto bootstrap via
|
|
335
|
-
* document.querySelector('[disable-devtool-auto]').
|
|
336
|
-
*
|
|
337
|
-
* Hiding only this selector prevents the default self-close/redirect behavior
|
|
338
|
-
* without affecting normal selector queries.
|
|
339
|
-
*/
|
|
340
|
-
function patchDisableDevtoolAutoBootstrap() {
|
|
341
|
-
return `(function(){
|
|
342
|
-
if (typeof Document === 'undefined') return;
|
|
343
|
-
const AUTO_SELECTOR = '[disable-devtool-auto]';
|
|
344
|
-
const NEVER_MATCH_SELECTOR = 'script[__ab_disable_devtool_never_match__="1"]';
|
|
345
|
-
const normalize = (value) => {
|
|
346
|
-
if (typeof value !== 'string') return '';
|
|
347
|
-
return value.replace(/\\s+/g, '').toLowerCase();
|
|
348
|
-
};
|
|
349
|
-
const shouldHideSelector = (selector) => normalize(selector) === AUTO_SELECTOR;
|
|
350
|
-
|
|
351
|
-
const patchQueryMethod = (proto, method) => {
|
|
352
|
-
if (!proto) return;
|
|
353
|
-
const native = proto[method];
|
|
354
|
-
if (typeof native !== 'function') return;
|
|
355
|
-
|
|
356
|
-
const wrapped = function(selector, ...args) {
|
|
357
|
-
if (shouldHideSelector(selector)) {
|
|
358
|
-
return native.call(this, NEVER_MATCH_SELECTOR, ...args);
|
|
359
|
-
}
|
|
360
|
-
return native.call(this, selector, ...args);
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
Object.defineProperty(wrapped, 'name', {
|
|
365
|
-
value: native.name,
|
|
366
|
-
configurable: true,
|
|
367
|
-
});
|
|
368
|
-
Object.defineProperty(wrapped, 'toString', {
|
|
369
|
-
value: () => native.toString(),
|
|
370
|
-
configurable: true,
|
|
371
|
-
});
|
|
372
|
-
} catch {}
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
Object.defineProperty(proto, method, {
|
|
376
|
-
value: wrapped,
|
|
377
|
-
configurable: true,
|
|
378
|
-
writable: true,
|
|
379
|
-
});
|
|
380
|
-
} catch {}
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
patchQueryMethod(Document.prototype, 'querySelector');
|
|
384
|
-
patchQueryMethod(Document.prototype, 'querySelectorAll');
|
|
385
|
-
if (typeof Element !== 'undefined') {
|
|
386
|
-
patchQueryMethod(Element.prototype, 'querySelector');
|
|
387
|
-
patchQueryMethod(Element.prototype, 'querySelectorAll');
|
|
388
|
-
}
|
|
389
|
-
})();`;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Remove navigator.webdriver entirely.
|
|
393
|
-
* Modern detection checks both value and property presence (`'webdriver' in navigator`).
|
|
394
|
-
*/
|
|
395
|
-
function patchNavigatorWebdriver() {
|
|
396
|
-
return `(function(){
|
|
397
|
-
const removeWebdriver = (target) => {
|
|
398
|
-
if (!target) return;
|
|
399
|
-
try { delete target.webdriver; } catch {}
|
|
400
|
-
};
|
|
401
|
-
removeWebdriver(navigator);
|
|
402
|
-
removeWebdriver(Object.getPrototypeOf(navigator));
|
|
403
|
-
removeWebdriver(Navigator.prototype);
|
|
404
|
-
if (typeof WorkerNavigator !== 'undefined') {
|
|
405
|
-
removeWebdriver(WorkerNavigator.prototype);
|
|
406
|
-
}
|
|
407
|
-
})();`;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Ensure window.chrome and window.chrome.runtime exist.
|
|
411
|
-
* Headless Chrome (and Playwright) omit chrome.runtime which is a dead giveaway.
|
|
412
|
-
*/
|
|
413
|
-
function patchChromeRuntime() {
|
|
414
|
-
return `(function(){
|
|
415
|
-
const chromeObject = ('chrome' in window && window.chrome) ? window.chrome : {};
|
|
416
|
-
if (!('chrome' in window)) {
|
|
417
|
-
try {
|
|
418
|
-
Object.defineProperty(Window.prototype, 'chrome', {
|
|
419
|
-
get: () => chromeObject,
|
|
420
|
-
configurable: true,
|
|
421
|
-
});
|
|
422
|
-
} catch {
|
|
423
|
-
try { Object.defineProperty(window, 'chrome', { value: chromeObject, configurable: true }); } catch {}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!chromeObject.runtime) {
|
|
427
|
-
const makeEvent = () => ({
|
|
428
|
-
addListener: () => {},
|
|
429
|
-
removeListener: () => {},
|
|
430
|
-
hasListener: () => false,
|
|
431
|
-
hasListeners: () => false,
|
|
432
|
-
dispatch: () => {},
|
|
433
|
-
});
|
|
434
|
-
const makePort = () => ({
|
|
435
|
-
name: '',
|
|
436
|
-
sender: undefined,
|
|
437
|
-
disconnect: () => {},
|
|
438
|
-
onDisconnect: makeEvent(),
|
|
439
|
-
onMessage: makeEvent(),
|
|
440
|
-
postMessage: () => {},
|
|
441
|
-
});
|
|
442
|
-
const runtime = {
|
|
443
|
-
id: undefined,
|
|
444
|
-
connect: () => makePort(),
|
|
445
|
-
sendMessage: () => undefined,
|
|
446
|
-
onConnect: makeEvent(),
|
|
447
|
-
onMessage: makeEvent(),
|
|
448
|
-
};
|
|
449
|
-
Object.defineProperty(chromeObject, 'runtime', {
|
|
450
|
-
value: runtime,
|
|
451
|
-
configurable: true,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
})();`;
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Add deprecated-but-still-probed Chrome APIs: chrome.app, chrome.csi, chrome.loadTimes.
|
|
458
|
-
*/
|
|
459
|
-
function patchChromeLegacyApis() {
|
|
460
|
-
return `(function(){
|
|
461
|
-
const chromeObject = ('chrome' in window && window.chrome) ? window.chrome : null;
|
|
462
|
-
if (!chromeObject) return;
|
|
463
|
-
const nativeNow = Date.now;
|
|
464
|
-
const nativeToString = Function.prototype.toString;
|
|
465
|
-
const timing = window.performance && window.performance.timing ? window.performance.timing : null;
|
|
466
|
-
const getNavigationEntry = () => {
|
|
467
|
-
try {
|
|
468
|
-
return performance.getEntriesByType('navigation')[0] || { nextHopProtocol: 'h2', type: 'other' };
|
|
469
|
-
} catch {
|
|
470
|
-
return { nextHopProtocol: 'h2', type: 'other' };
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
const defineValue = (target, key, value) => {
|
|
474
|
-
try {
|
|
475
|
-
Object.defineProperty(target, key, {
|
|
476
|
-
value,
|
|
477
|
-
configurable: true,
|
|
478
|
-
writable: true,
|
|
479
|
-
});
|
|
480
|
-
return true;
|
|
481
|
-
} catch {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
const patchFunctionShape = (fn, name) => {
|
|
486
|
-
try {
|
|
487
|
-
Object.defineProperty(fn, 'name', { value: name, configurable: true });
|
|
488
|
-
Object.defineProperty(fn, 'toString', {
|
|
489
|
-
value: () => nativeToString.call(nativeNow).replace('now', name),
|
|
490
|
-
configurable: true,
|
|
491
|
-
});
|
|
492
|
-
} catch {}
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
if (!('app' in chromeObject)) {
|
|
496
|
-
const invokeError = (name) => new TypeError('Error in invocation of app.' + name + '()');
|
|
497
|
-
const app = {
|
|
498
|
-
isInstalled: false,
|
|
499
|
-
InstallState: {
|
|
500
|
-
DISABLED: 'disabled',
|
|
501
|
-
INSTALLED: 'installed',
|
|
502
|
-
NOT_INSTALLED: 'not_installed',
|
|
503
|
-
},
|
|
504
|
-
RunningState: {
|
|
505
|
-
CANNOT_RUN: 'cannot_run',
|
|
506
|
-
READY_TO_RUN: 'ready_to_run',
|
|
507
|
-
RUNNING: 'running',
|
|
508
|
-
},
|
|
509
|
-
getDetails: function getDetails() {
|
|
510
|
-
if (arguments.length) throw invokeError('getDetails');
|
|
511
|
-
return null;
|
|
512
|
-
},
|
|
513
|
-
getIsInstalled: function getIsInstalled() {
|
|
514
|
-
if (arguments.length) throw invokeError('getIsInstalled');
|
|
515
|
-
return false;
|
|
516
|
-
},
|
|
517
|
-
runningState: function runningState() {
|
|
518
|
-
if (arguments.length) throw invokeError('runningState');
|
|
519
|
-
return 'cannot_run';
|
|
520
|
-
},
|
|
521
|
-
};
|
|
522
|
-
defineValue(chromeObject, 'app', app);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (!('csi' in chromeObject) && timing) {
|
|
526
|
-
const csi = function csi() {
|
|
527
|
-
return {
|
|
528
|
-
onloadT: timing.domContentLoadedEventEnd,
|
|
529
|
-
startE: timing.navigationStart,
|
|
530
|
-
pageT: Date.now() - timing.navigationStart,
|
|
531
|
-
tran: 15,
|
|
532
|
-
};
|
|
533
|
-
};
|
|
534
|
-
patchFunctionShape(csi, 'csi');
|
|
535
|
-
defineValue(chromeObject, 'csi', csi);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (!('loadTimes' in chromeObject) && timing) {
|
|
539
|
-
const toFixed = (num, fixed) => {
|
|
540
|
-
const matcher = new RegExp('^-?\\\\d+(?:.\\\\d{0,' + (fixed || -1) + '})?');
|
|
541
|
-
const match = String(num).match(matcher);
|
|
542
|
-
return match ? match[0] : String(num);
|
|
543
|
-
};
|
|
544
|
-
const loadTimes = function loadTimes() {
|
|
545
|
-
const navigationEntry = getNavigationEntry();
|
|
546
|
-
const nextHopProtocol = navigationEntry.nextHopProtocol || 'h2';
|
|
547
|
-
let firstPaint = timing.loadEventEnd / 1000;
|
|
548
|
-
try {
|
|
549
|
-
const paintEntries = performance.getEntriesByType('paint');
|
|
550
|
-
if (paintEntries && paintEntries[0] && typeof paintEntries[0].startTime === 'number') {
|
|
551
|
-
firstPaint = (paintEntries[0].startTime + performance.timeOrigin) / 1000;
|
|
552
|
-
}
|
|
553
|
-
} catch {}
|
|
554
|
-
return {
|
|
555
|
-
connectionInfo: nextHopProtocol,
|
|
556
|
-
npnNegotiatedProtocol: ['h2', 'hq'].includes(nextHopProtocol) ? nextHopProtocol : 'unknown',
|
|
557
|
-
navigationType: navigationEntry.type || 'other',
|
|
558
|
-
wasAlternateProtocolAvailable: false,
|
|
559
|
-
wasFetchedViaSpdy: ['h2', 'hq'].includes(nextHopProtocol),
|
|
560
|
-
wasNpnNegotiated: ['h2', 'hq'].includes(nextHopProtocol),
|
|
561
|
-
firstPaintAfterLoadTime: 0,
|
|
562
|
-
requestTime: timing.navigationStart / 1000,
|
|
563
|
-
startLoadTime: timing.navigationStart / 1000,
|
|
564
|
-
commitLoadTime: timing.responseStart / 1000,
|
|
565
|
-
finishDocumentLoadTime: timing.domContentLoadedEventEnd / 1000,
|
|
566
|
-
finishLoadTime: timing.loadEventEnd / 1000,
|
|
567
|
-
firstPaintTime: toFixed(firstPaint, 3),
|
|
568
|
-
};
|
|
569
|
-
};
|
|
570
|
-
patchFunctionShape(loadTimes, 'loadTimes');
|
|
571
|
-
defineValue(chromeObject, 'loadTimes', loadTimes);
|
|
572
|
-
}
|
|
573
|
-
})();`;
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Fix srcdoc iframe.contentWindow signals used by classic HEADCHR_IFRAME checks.
|
|
577
|
-
* We only intercept iframe creation and srcdoc assignment to keep impact minimal.
|
|
578
|
-
*/
|
|
579
|
-
function patchIframeContentWindow() {
|
|
580
|
-
return `(function(){
|
|
581
|
-
if (typeof document === 'undefined' || typeof document.createElement !== 'function') return;
|
|
582
|
-
const nativeCreateElement = document.createElement.bind(document);
|
|
583
|
-
const nativeSrcdocDescriptor =
|
|
584
|
-
typeof HTMLIFrameElement !== 'undefined'
|
|
585
|
-
? Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'srcdoc')
|
|
586
|
-
: null;
|
|
587
|
-
const srcdocGetter = nativeSrcdocDescriptor && nativeSrcdocDescriptor.get;
|
|
588
|
-
const srcdocSetter = nativeSrcdocDescriptor && nativeSrcdocDescriptor.set;
|
|
589
|
-
const iframeProxyMap = new WeakMap();
|
|
590
|
-
const patchedIframes = new WeakSet();
|
|
591
|
-
|
|
592
|
-
const ensureContentWindowProxy = (iframe) => {
|
|
593
|
-
if (!iframe || iframeProxyMap.has(iframe)) return;
|
|
594
|
-
try {
|
|
595
|
-
if (iframe.contentWindow) return;
|
|
596
|
-
} catch {}
|
|
597
|
-
const proxy = new Proxy(window, {
|
|
598
|
-
get(target, key) {
|
|
599
|
-
if (key === 'self') return proxy;
|
|
600
|
-
if (key === 'frameElement') return iframe;
|
|
601
|
-
if (key === '0') return undefined;
|
|
602
|
-
return Reflect.get(target, key, target);
|
|
603
|
-
},
|
|
604
|
-
});
|
|
605
|
-
iframeProxyMap.set(iframe, proxy);
|
|
606
|
-
try {
|
|
607
|
-
Object.defineProperty(iframe, 'contentWindow', {
|
|
608
|
-
get: () => proxy,
|
|
609
|
-
set: () => undefined,
|
|
610
|
-
enumerable: true,
|
|
611
|
-
configurable: false,
|
|
612
|
-
});
|
|
613
|
-
} catch {}
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const patchIframeSrcdoc = (iframe) => {
|
|
617
|
-
if (!iframe || patchedIframes.has(iframe)) return;
|
|
618
|
-
patchedIframes.add(iframe);
|
|
619
|
-
try {
|
|
620
|
-
Object.defineProperty(iframe, 'srcdoc', {
|
|
621
|
-
configurable: true,
|
|
622
|
-
get() {
|
|
623
|
-
if (typeof srcdocGetter === 'function') {
|
|
624
|
-
return srcdocGetter.call(this);
|
|
625
|
-
}
|
|
626
|
-
return '';
|
|
627
|
-
},
|
|
628
|
-
set(value) {
|
|
629
|
-
ensureContentWindowProxy(this);
|
|
630
|
-
if (typeof srcdocSetter === 'function') {
|
|
631
|
-
srcdocSetter.call(this, value);
|
|
632
|
-
} else {
|
|
633
|
-
this.setAttribute('srcdoc', String(value ?? ''));
|
|
634
|
-
}
|
|
635
|
-
},
|
|
636
|
-
});
|
|
637
|
-
} catch {}
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
const patchedCreateElement = function(...args) {
|
|
641
|
-
const element = nativeCreateElement(...args);
|
|
642
|
-
try {
|
|
643
|
-
const name = args && args.length > 0 ? String(args[0]).toLowerCase() : '';
|
|
644
|
-
if (name === 'iframe') {
|
|
645
|
-
patchIframeSrcdoc(element);
|
|
646
|
-
}
|
|
647
|
-
} catch {}
|
|
648
|
-
return element;
|
|
649
|
-
};
|
|
650
|
-
try {
|
|
651
|
-
Object.defineProperty(patchedCreateElement, 'name', {
|
|
652
|
-
value: 'createElement',
|
|
653
|
-
configurable: true,
|
|
654
|
-
});
|
|
655
|
-
Object.defineProperty(patchedCreateElement, 'toString', {
|
|
656
|
-
value: () => nativeCreateElement.toString(),
|
|
657
|
-
configurable: true,
|
|
658
|
-
});
|
|
659
|
-
} catch {}
|
|
660
|
-
try {
|
|
661
|
-
Object.defineProperty(document, 'createElement', {
|
|
662
|
-
value: patchedCreateElement,
|
|
663
|
-
configurable: true,
|
|
664
|
-
writable: true,
|
|
665
|
-
});
|
|
666
|
-
} catch {
|
|
667
|
-
try { document.createElement = patchedCreateElement; } catch {}
|
|
668
|
-
}
|
|
669
|
-
})();`;
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Keep navigator.language + navigator.languages aligned with launch locale.
|
|
673
|
-
*/
|
|
674
|
-
function patchNavigatorLanguages() {
|
|
675
|
-
return `(function(){
|
|
676
|
-
const config = (typeof __abStealth === 'object' && __abStealth) ? __abStealth : null;
|
|
677
|
-
if (!config || !Array.isArray(config.languages) || config.languages.length === 0) return;
|
|
678
|
-
const locale = typeof config.locale === 'string' ? config.locale : config.languages[0];
|
|
679
|
-
try {
|
|
680
|
-
Object.defineProperty(navigator, 'language', {
|
|
681
|
-
get: () => locale,
|
|
682
|
-
configurable: true,
|
|
683
|
-
});
|
|
684
|
-
} catch {}
|
|
685
|
-
try {
|
|
686
|
-
Object.defineProperty(navigator, 'languages', {
|
|
687
|
-
get: () => config.languages.slice(),
|
|
688
|
-
configurable: true,
|
|
689
|
-
});
|
|
690
|
-
} catch {}
|
|
691
|
-
})();`;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Keep navigator.vendor aligned with regular Chrome.
|
|
695
|
-
*/
|
|
696
|
-
function patchNavigatorVendor() {
|
|
697
|
-
return `(function(){
|
|
698
|
-
const ua = String(navigator.userAgent || '');
|
|
699
|
-
if (!/Chrome\\//.test(ua) || /Firefox\\//.test(ua)) return;
|
|
700
|
-
const target = 'Google Inc.';
|
|
701
|
-
const proto = Object.getPrototypeOf(navigator);
|
|
702
|
-
try {
|
|
703
|
-
if (navigator.vendor === target) return;
|
|
704
|
-
} catch {}
|
|
705
|
-
const defineVendor = (targetObj) => {
|
|
706
|
-
if (!targetObj) return false;
|
|
707
|
-
try {
|
|
708
|
-
Object.defineProperty(targetObj, 'vendor', {
|
|
709
|
-
get: () => target,
|
|
710
|
-
configurable: true,
|
|
711
|
-
});
|
|
712
|
-
return true;
|
|
713
|
-
} catch {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
if (defineVendor(proto)) {
|
|
718
|
-
try { delete (navigator).vendor; } catch {}
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
defineVendor(navigator);
|
|
722
|
-
})();`;
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Inject realistic navigator.plugins and navigator.mimeTypes arrays.
|
|
726
|
-
* Headless Chrome reports an empty PluginArray; real Chrome always has a few.
|
|
727
|
-
*/
|
|
728
|
-
function patchNavigatorPluginsAndMimeTypes() {
|
|
729
|
-
return `(function(){
|
|
730
|
-
const makeMimeType = (type, suffixes, description) => {
|
|
731
|
-
const mime = Object.create(MimeType.prototype);
|
|
732
|
-
Object.defineProperties(mime, {
|
|
733
|
-
type: { value: type, enumerable: true },
|
|
734
|
-
suffixes: { value: suffixes, enumerable: true },
|
|
735
|
-
description: { value: description, enumerable: true },
|
|
736
|
-
enabledPlugin: { value: null, writable: true, enumerable: true },
|
|
737
|
-
});
|
|
738
|
-
return mime;
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
const makePlugin = (name, description, filename, mimes) => {
|
|
742
|
-
const plugin = Object.create(Plugin.prototype);
|
|
743
|
-
Object.defineProperties(plugin, {
|
|
744
|
-
name: { value: name, enumerable: true },
|
|
745
|
-
description: { value: description, enumerable: true },
|
|
746
|
-
filename: { value: filename, enumerable: true },
|
|
747
|
-
length: { value: mimes.length, enumerable: true },
|
|
748
|
-
});
|
|
749
|
-
mimes.forEach((mime, i) => {
|
|
750
|
-
Object.defineProperty(plugin, i, {
|
|
751
|
-
value: mime,
|
|
752
|
-
enumerable: true,
|
|
753
|
-
});
|
|
754
|
-
Object.defineProperty(plugin, mime.type, {
|
|
755
|
-
value: mime,
|
|
756
|
-
enumerable: false,
|
|
757
|
-
});
|
|
758
|
-
try { mime.enabledPlugin = plugin; } catch {}
|
|
759
|
-
});
|
|
760
|
-
return plugin;
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
const pdfMime = makeMimeType('application/pdf', 'pdf', 'Portable Document Format');
|
|
764
|
-
const chromePdfMime = makeMimeType(
|
|
765
|
-
'application/x-google-chrome-pdf',
|
|
766
|
-
'pdf',
|
|
767
|
-
'Portable Document Format'
|
|
768
|
-
);
|
|
769
|
-
const naclMime = makeMimeType('application/x-nacl', '', 'Native Client Executable');
|
|
770
|
-
const pnaclMime = makeMimeType('application/x-pnacl', '', 'Portable Native Client Executable');
|
|
771
|
-
|
|
772
|
-
const plugins = [
|
|
773
|
-
makePlugin('Chrome PDF Plugin', 'Portable Document Format', 'internal-pdf-viewer', [chromePdfMime]),
|
|
774
|
-
makePlugin('Chrome PDF Viewer', '', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', [pdfMime]),
|
|
775
|
-
makePlugin('Native Client', '', 'internal-nacl-plugin', [naclMime, pnaclMime]),
|
|
776
|
-
];
|
|
777
|
-
const pluginArray = Object.create(PluginArray.prototype);
|
|
778
|
-
plugins.forEach((p, i) => {
|
|
779
|
-
pluginArray[i] = p;
|
|
780
|
-
pluginArray[p.name] = p;
|
|
781
|
-
});
|
|
782
|
-
Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
|
|
783
|
-
pluginArray.item = (i) => plugins[i] || null;
|
|
784
|
-
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) || null;
|
|
785
|
-
pluginArray.refresh = () => {};
|
|
786
|
-
pluginArray[Symbol.iterator] = function*() { for (const p of plugins) yield p; };
|
|
787
|
-
|
|
788
|
-
const mimeTypes = [chromePdfMime, pdfMime, naclMime, pnaclMime];
|
|
789
|
-
const mimeTypeArray = Object.create(MimeTypeArray.prototype);
|
|
790
|
-
mimeTypes.forEach((m, i) => {
|
|
791
|
-
mimeTypeArray[i] = m;
|
|
792
|
-
mimeTypeArray[m.type] = m;
|
|
793
|
-
});
|
|
794
|
-
Object.defineProperty(mimeTypeArray, 'length', { get: () => mimeTypes.length });
|
|
795
|
-
mimeTypeArray.item = (i) => mimeTypes[i] || null;
|
|
796
|
-
mimeTypeArray.namedItem = (name) => mimeTypes.find(m => m.type === name) || null;
|
|
797
|
-
mimeTypeArray[Symbol.iterator] = function*() { for (const m of mimeTypes) yield m; };
|
|
798
|
-
|
|
799
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
800
|
-
get: () => pluginArray,
|
|
801
|
-
configurable: true,
|
|
802
|
-
});
|
|
803
|
-
Object.defineProperty(navigator, 'mimeTypes', {
|
|
804
|
-
get: () => mimeTypeArray,
|
|
805
|
-
configurable: true,
|
|
806
|
-
});
|
|
807
|
-
})();`;
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* navigator.permissions.query({name:'notifications'}) should resolve to
|
|
811
|
-
* 'denied' in a normal browser, but Playwright throws or returns 'prompt'.
|
|
812
|
-
*/
|
|
813
|
-
function patchNavigatorPermissions() {
|
|
814
|
-
return `(function(){
|
|
815
|
-
if (!navigator.permissions || !navigator.permissions.query) return;
|
|
816
|
-
const origQuery = navigator.permissions.query.bind(navigator.permissions);
|
|
817
|
-
const makePermissionStatus = (state) => {
|
|
818
|
-
if (typeof PermissionStatus !== 'undefined') {
|
|
819
|
-
const status = Object.create(PermissionStatus.prototype);
|
|
820
|
-
Object.defineProperty(status, 'state', {
|
|
821
|
-
value: state,
|
|
822
|
-
writable: false,
|
|
823
|
-
enumerable: true,
|
|
824
|
-
});
|
|
825
|
-
Object.defineProperty(status, 'onchange', {
|
|
826
|
-
value: null,
|
|
827
|
-
writable: true,
|
|
828
|
-
enumerable: true,
|
|
829
|
-
});
|
|
830
|
-
return status;
|
|
831
|
-
}
|
|
832
|
-
return { state, onchange: null };
|
|
833
|
-
};
|
|
834
|
-
const patchedQuery = new Proxy(origQuery, {
|
|
835
|
-
apply(target, thisArg, argList) {
|
|
836
|
-
const params = argList && argList[0];
|
|
837
|
-
if (params && params.name === 'notifications') {
|
|
838
|
-
const state = (typeof Notification !== 'undefined' && Notification.permission) || 'default';
|
|
839
|
-
return Promise.resolve(makePermissionStatus(state));
|
|
840
|
-
}
|
|
841
|
-
return Reflect.apply(target, navigator.permissions, argList);
|
|
842
|
-
}
|
|
843
|
-
});
|
|
844
|
-
try {
|
|
845
|
-
Object.defineProperty(navigator.permissions, 'query', {
|
|
846
|
-
value: patchedQuery,
|
|
847
|
-
configurable: true,
|
|
848
|
-
writable: true,
|
|
849
|
-
});
|
|
850
|
-
} catch {}
|
|
851
|
-
})();`;
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* WebGL vendor/renderer: headless Chrome uses SwiftShader which is distinctive.
|
|
855
|
-
* Patch getParameter to return Intel GPU strings when SwiftShader is detected.
|
|
856
|
-
*/
|
|
857
|
-
function patchWebGLVendor() {
|
|
858
|
-
return `(function(){
|
|
859
|
-
const getCtx = HTMLCanvasElement.prototype.getContext;
|
|
860
|
-
const WEBGL_VENDOR = 'Intel Inc.';
|
|
861
|
-
const WEBGL_RENDERER = 'Intel Iris OpenGL Engine';
|
|
862
|
-
const DEBUG_RENDERER_INFO = {
|
|
863
|
-
UNMASKED_VENDOR_WEBGL: 0x9245,
|
|
864
|
-
UNMASKED_RENDERER_WEBGL: 0x9246,
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
const createFallbackWebGLContext = (canvas, requestedType) => {
|
|
868
|
-
const isWebGL2 = requestedType === 'webgl2';
|
|
869
|
-
const ctx = {
|
|
870
|
-
__abFallbackWebGLContext: true,
|
|
871
|
-
canvas,
|
|
872
|
-
drawingBufferWidth: canvas.width || 300,
|
|
873
|
-
drawingBufferHeight: canvas.height || 150,
|
|
874
|
-
VENDOR: 0x1F00,
|
|
875
|
-
RENDERER: 0x1F01,
|
|
876
|
-
VERSION: 0x1F02,
|
|
877
|
-
SHADING_LANGUAGE_VERSION: 0x8B8C,
|
|
878
|
-
getExtension(name) {
|
|
879
|
-
if (name === 'WEBGL_debug_renderer_info') return DEBUG_RENDERER_INFO;
|
|
880
|
-
return null;
|
|
881
|
-
},
|
|
882
|
-
getSupportedExtensions() {
|
|
883
|
-
return ['WEBGL_debug_renderer_info'];
|
|
884
|
-
},
|
|
885
|
-
getContextAttributes() {
|
|
886
|
-
return {
|
|
887
|
-
alpha: true,
|
|
888
|
-
antialias: true,
|
|
889
|
-
depth: true,
|
|
890
|
-
desynchronized: false,
|
|
891
|
-
failIfMajorPerformanceCaveat: false,
|
|
892
|
-
powerPreference: 'default',
|
|
893
|
-
premultipliedAlpha: true,
|
|
894
|
-
preserveDrawingBuffer: false,
|
|
895
|
-
stencil: false,
|
|
896
|
-
};
|
|
897
|
-
},
|
|
898
|
-
getParameter(param) {
|
|
899
|
-
if (param === DEBUG_RENDERER_INFO.UNMASKED_VENDOR_WEBGL || param === this.VENDOR) {
|
|
900
|
-
return WEBGL_VENDOR;
|
|
901
|
-
}
|
|
902
|
-
if (param === DEBUG_RENDERER_INFO.UNMASKED_RENDERER_WEBGL || param === this.RENDERER) {
|
|
903
|
-
return WEBGL_RENDERER;
|
|
904
|
-
}
|
|
905
|
-
if (param === this.VERSION) {
|
|
906
|
-
return isWebGL2
|
|
907
|
-
? 'WebGL 2.0 (OpenGL ES 3.0 Chromium)'
|
|
908
|
-
: 'WebGL 1.0 (OpenGL ES 2.0 Chromium)';
|
|
909
|
-
}
|
|
910
|
-
if (param === this.SHADING_LANGUAGE_VERSION) {
|
|
911
|
-
return isWebGL2
|
|
912
|
-
? 'WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)'
|
|
913
|
-
: 'WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)';
|
|
914
|
-
}
|
|
915
|
-
return 0;
|
|
916
|
-
},
|
|
917
|
-
getError() { return 0; },
|
|
918
|
-
clear() {},
|
|
919
|
-
clearColor() {},
|
|
920
|
-
createBuffer() { return {}; },
|
|
921
|
-
bindBuffer() {},
|
|
922
|
-
bufferData() {},
|
|
923
|
-
createProgram() { return {}; },
|
|
924
|
-
createShader() { return {}; },
|
|
925
|
-
shaderSource() {},
|
|
926
|
-
compileShader() {},
|
|
927
|
-
attachShader() {},
|
|
928
|
-
linkProgram() {},
|
|
929
|
-
useProgram() {},
|
|
930
|
-
viewport() {},
|
|
931
|
-
drawArrays() {},
|
|
932
|
-
readPixels() {},
|
|
933
|
-
finish() {},
|
|
934
|
-
flush() {},
|
|
935
|
-
};
|
|
936
|
-
try {
|
|
937
|
-
const proto =
|
|
938
|
-
requestedType === 'webgl2' && typeof WebGL2RenderingContext !== 'undefined'
|
|
939
|
-
? WebGL2RenderingContext.prototype
|
|
940
|
-
: typeof WebGLRenderingContext !== 'undefined'
|
|
941
|
-
? WebGLRenderingContext.prototype
|
|
942
|
-
: null;
|
|
943
|
-
if (proto) Object.setPrototypeOf(ctx, proto);
|
|
944
|
-
} catch {}
|
|
945
|
-
return ctx;
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
HTMLCanvasElement.prototype.getContext = function(type, attrs) {
|
|
949
|
-
const ctx = getCtx.call(this, type, attrs);
|
|
950
|
-
if (
|
|
951
|
-
(type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl') &&
|
|
952
|
-
!ctx &&
|
|
953
|
-
__abStealth &&
|
|
954
|
-
__abStealth.allowWebGLContextFallback === true
|
|
955
|
-
) {
|
|
956
|
-
return createFallbackWebGLContext(this, type);
|
|
957
|
-
}
|
|
958
|
-
if (ctx && (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl')) {
|
|
959
|
-
const origGetParameter = ctx.getParameter.bind(ctx);
|
|
960
|
-
ctx.getParameter = function(param) {
|
|
961
|
-
const ext = ctx.getExtension('WEBGL_debug_renderer_info');
|
|
962
|
-
if (ext) {
|
|
963
|
-
if (param === ext.UNMASKED_VENDOR_WEBGL) {
|
|
964
|
-
const real = origGetParameter(param);
|
|
965
|
-
return (real && real.includes('SwiftShader')) ? WEBGL_VENDOR : real;
|
|
966
|
-
}
|
|
967
|
-
if (param === ext.UNMASKED_RENDERER_WEBGL) {
|
|
968
|
-
const real = origGetParameter(param);
|
|
969
|
-
return (real && real.includes('SwiftShader')) ? WEBGL_RENDERER : real;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
if (param === ctx.VENDOR) return WEBGL_VENDOR;
|
|
973
|
-
if (param === ctx.RENDERER) return WEBGL_RENDERER;
|
|
974
|
-
return origGetParameter(param);
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
return ctx;
|
|
978
|
-
};
|
|
979
|
-
})();`;
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Remove Playwright's injected cdc_ (Chrome DevTools) properties on document.
|
|
983
|
-
* Some older detection scripts look for these on the document element.
|
|
984
|
-
*/
|
|
985
|
-
function patchCdcProperties() {
|
|
986
|
-
return `(function(){
|
|
987
|
-
const clean = (target) => {
|
|
988
|
-
for (const key of Object.keys(target)) {
|
|
989
|
-
if (/^cdc_|^\\$cdc_/.test(key)) {
|
|
990
|
-
delete target[key];
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
clean(document);
|
|
995
|
-
if (document.documentElement) clean(document.documentElement);
|
|
996
|
-
})();`;
|
|
997
|
-
}
|
|
998
|
-
/**
|
|
999
|
-
* Remove Playwright/Puppeteer sourceURL tokens from error stacks.
|
|
1000
|
-
* This mirrors the intent of the sourceurl evasion: reduce obvious
|
|
1001
|
-
* automation-only script labels in stack traces.
|
|
1002
|
-
*/
|
|
1003
|
-
function patchSourceUrlStackTraces() {
|
|
1004
|
-
return `(function(){
|
|
1005
|
-
if (typeof Error === 'undefined') return;
|
|
1006
|
-
const sanitizeStack = (value) => {
|
|
1007
|
-
if (typeof value !== 'string') return value;
|
|
1008
|
-
let stack = value;
|
|
1009
|
-
stack = stack.replace(/\\/\\/# sourceURL=.*$/gm, '');
|
|
1010
|
-
stack = stack.replace(/__playwright_evaluation_script__/g, '<anonymous>');
|
|
1011
|
-
stack = stack.replace(/__puppeteer_evaluation_script__/g, '<anonymous>');
|
|
1012
|
-
stack = stack.replace(/__pw_evaluation_script__/g, '<anonymous>');
|
|
1013
|
-
return stack;
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
const nativePrepare = Error.prepareStackTrace;
|
|
1017
|
-
Error.prepareStackTrace = function(error, structuredStackTrace) {
|
|
1018
|
-
let stackString;
|
|
1019
|
-
if (typeof nativePrepare === 'function') {
|
|
1020
|
-
stackString = nativePrepare.call(this, error, structuredStackTrace);
|
|
1021
|
-
} else {
|
|
1022
|
-
const name = error && error.name ? String(error.name) : 'Error';
|
|
1023
|
-
const message = error && error.message ? String(error.message) : '';
|
|
1024
|
-
const header = message ? name + ': ' + message : name;
|
|
1025
|
-
const frames = Array.isArray(structuredStackTrace)
|
|
1026
|
-
? structuredStackTrace.map((frame) => ' at ' + String(frame))
|
|
1027
|
-
: [];
|
|
1028
|
-
stackString = [header].concat(frames).join('\\n');
|
|
1029
|
-
}
|
|
1030
|
-
return sanitizeStack(String(stackString));
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
if (typeof Error.captureStackTrace === 'function') {
|
|
1034
|
-
const nativeCapture = Error.captureStackTrace;
|
|
1035
|
-
Error.captureStackTrace = function(targetObject, constructorOpt) {
|
|
1036
|
-
nativeCapture.call(this, targetObject, constructorOpt);
|
|
1037
|
-
try {
|
|
1038
|
-
const stack = targetObject && targetObject.stack;
|
|
1039
|
-
if (typeof stack === 'string') {
|
|
1040
|
-
Object.defineProperty(targetObject, 'stack', {
|
|
1041
|
-
value: sanitizeStack(stack),
|
|
1042
|
-
configurable: true,
|
|
1043
|
-
writable: true,
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
} catch {}
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
})();`;
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* contentWindow on cross-origin iframes: Playwright sometimes returns null
|
|
1053
|
-
* where real browsers return a (restricted) Window object.
|
|
1054
|
-
*/
|
|
1055
|
-
function patchWindowDimensions() {
|
|
1056
|
-
return `(function(){
|
|
1057
|
-
const widthDelta = 12;
|
|
1058
|
-
const heightDelta = 74;
|
|
1059
|
-
const patchWidth =
|
|
1060
|
-
!Number.isFinite(window.outerWidth) ||
|
|
1061
|
-
window.outerWidth === 0 ||
|
|
1062
|
-
Math.abs(window.outerWidth - window.innerWidth) <= 1;
|
|
1063
|
-
const patchHeight =
|
|
1064
|
-
!Number.isFinite(window.outerHeight) ||
|
|
1065
|
-
window.outerHeight === 0 ||
|
|
1066
|
-
Math.abs(window.outerHeight - window.innerHeight) <= 1;
|
|
1067
|
-
if (patchWidth) {
|
|
1068
|
-
try {
|
|
1069
|
-
Object.defineProperty(window, 'outerWidth', {
|
|
1070
|
-
get: () => Math.max(window.innerWidth + widthDelta, window.innerWidth),
|
|
1071
|
-
configurable: true,
|
|
1072
|
-
});
|
|
1073
|
-
} catch {}
|
|
1074
|
-
}
|
|
1075
|
-
if (patchHeight) {
|
|
1076
|
-
try {
|
|
1077
|
-
Object.defineProperty(window, 'outerHeight', {
|
|
1078
|
-
get: () => Math.max(window.innerHeight + heightDelta, window.innerHeight),
|
|
1079
|
-
configurable: true,
|
|
1080
|
-
});
|
|
1081
|
-
} catch {}
|
|
1082
|
-
}
|
|
1083
|
-
const patchScreenPosition =
|
|
1084
|
-
(!Number.isFinite(window.screenX) || !Number.isFinite(window.screenY)) ||
|
|
1085
|
-
(window.screenX === 0 && window.screenY === 0 && (patchWidth || patchHeight));
|
|
1086
|
-
if (patchScreenPosition) {
|
|
1087
|
-
try {
|
|
1088
|
-
Object.defineProperty(window, 'screenX', {
|
|
1089
|
-
get: () => 16,
|
|
1090
|
-
configurable: true,
|
|
1091
|
-
});
|
|
1092
|
-
Object.defineProperty(window, 'screenY', {
|
|
1093
|
-
get: () => 72,
|
|
1094
|
-
configurable: true,
|
|
1095
|
-
});
|
|
1096
|
-
Object.defineProperty(window, 'screenLeft', {
|
|
1097
|
-
get: () => 16,
|
|
1098
|
-
configurable: true,
|
|
1099
|
-
});
|
|
1100
|
-
Object.defineProperty(window, 'screenTop', {
|
|
1101
|
-
get: () => 72,
|
|
1102
|
-
configurable: true,
|
|
1103
|
-
});
|
|
1104
|
-
} catch {}
|
|
1105
|
-
}
|
|
1106
|
-
})();`;
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Make Screen avail* values look like a desktop with taskbar/menu bar reserved space.
|
|
1110
|
-
*/
|
|
1111
|
-
function patchScreenAvailability() {
|
|
1112
|
-
return `(function(){
|
|
1113
|
-
const patchNumber = (target, key, value) => {
|
|
1114
|
-
try {
|
|
1115
|
-
Object.defineProperty(target, key, {
|
|
1116
|
-
get: () => value,
|
|
1117
|
-
configurable: true,
|
|
1118
|
-
});
|
|
1119
|
-
} catch {}
|
|
1120
|
-
};
|
|
1121
|
-
const availWidth = Number(screen.availWidth);
|
|
1122
|
-
const availHeight = Number(screen.availHeight);
|
|
1123
|
-
const width = Number(screen.width);
|
|
1124
|
-
const height = Number(screen.height);
|
|
1125
|
-
if (Number.isFinite(width) && Number.isFinite(availWidth) && availWidth >= width) {
|
|
1126
|
-
patchNumber(screen, 'availWidth', Math.max(width - 8, 0));
|
|
1127
|
-
}
|
|
1128
|
-
if (Number.isFinite(height) && Number.isFinite(availHeight) && availHeight >= height) {
|
|
1129
|
-
patchNumber(screen, 'availHeight', Math.max(height - 40, 0));
|
|
1130
|
-
}
|
|
1131
|
-
if (Number.isFinite(screen.availLeft) && screen.availLeft === 0) {
|
|
1132
|
-
patchNumber(screen, 'availLeft', 0);
|
|
1133
|
-
}
|
|
1134
|
-
if (Number.isFinite(screen.availTop) && screen.availTop === 0) {
|
|
1135
|
-
patchNumber(screen, 'availTop', 24);
|
|
1136
|
-
}
|
|
1137
|
-
})();`;
|
|
1138
|
-
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Avoid screen == viewport fingerprints common in headless defaults.
|
|
1141
|
-
*/
|
|
1142
|
-
function patchScreenDimensions() {
|
|
1143
|
-
return `(function(){
|
|
1144
|
-
const patchNumber = (target, key, value) => {
|
|
1145
|
-
try {
|
|
1146
|
-
Object.defineProperty(target, key, {
|
|
1147
|
-
get: () => value,
|
|
1148
|
-
configurable: true,
|
|
1149
|
-
});
|
|
1150
|
-
} catch {}
|
|
1151
|
-
};
|
|
1152
|
-
const width = Number(screen.width);
|
|
1153
|
-
const height = Number(screen.height);
|
|
1154
|
-
const innerWidth = Number(window.innerWidth);
|
|
1155
|
-
const innerHeight = Number(window.innerHeight);
|
|
1156
|
-
if (
|
|
1157
|
-
Number.isFinite(width) &&
|
|
1158
|
-
Number.isFinite(height) &&
|
|
1159
|
-
Number.isFinite(innerWidth) &&
|
|
1160
|
-
Number.isFinite(innerHeight) &&
|
|
1161
|
-
width === innerWidth &&
|
|
1162
|
-
height === innerHeight
|
|
1163
|
-
) {
|
|
1164
|
-
patchNumber(screen, 'width', Math.max(innerWidth + 86, 1366));
|
|
1165
|
-
patchNumber(screen, 'height', Math.max(innerHeight + 48, 768));
|
|
1166
|
-
}
|
|
1167
|
-
})();`;
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* navigator.hardwareConcurrency: headless often reports 2 (CI);
|
|
1171
|
-
* real desktops typically have >= 4 cores.
|
|
1172
|
-
*/
|
|
1173
|
-
function patchNavigatorHardwareConcurrency() {
|
|
1174
|
-
return `(function(){
|
|
1175
|
-
if (navigator.hardwareConcurrency < 4) {
|
|
1176
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
|
1177
|
-
get: () => 4,
|
|
1178
|
-
configurable: true,
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
})();`;
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Keep notifications in "default" state instead of denied-by-default headless behavior.
|
|
1185
|
-
*/
|
|
1186
|
-
function patchNotificationPermission() {
|
|
1187
|
-
return `(function(){
|
|
1188
|
-
if (typeof Notification === 'undefined') return;
|
|
1189
|
-
const current = Notification.permission;
|
|
1190
|
-
if (current === 'granted') return;
|
|
1191
|
-
try {
|
|
1192
|
-
Object.defineProperty(Notification, 'permission', {
|
|
1193
|
-
get: () => 'default',
|
|
1194
|
-
configurable: true,
|
|
1195
|
-
});
|
|
1196
|
-
} catch {}
|
|
1197
|
-
})();`;
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* CreepJS probes `background-color: ActiveText` and flags Chromium when the
|
|
1201
|
-
* computed value resolves to `rgb(255, 0, 0)`. Rewrite only that exact probe.
|
|
1202
|
-
*/
|
|
1203
|
-
function patchActiveTextColorHeuristic() {
|
|
1204
|
-
return `(function(){
|
|
1205
|
-
if (typeof Element === 'undefined' || !Element.prototype) return;
|
|
1206
|
-
const nativeSetAttribute = Element.prototype.setAttribute;
|
|
1207
|
-
if (typeof nativeSetAttribute !== 'function') return;
|
|
1208
|
-
const normalize = (value) => String(value).replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
1209
|
-
const probeStyle = 'background-color: activetext';
|
|
1210
|
-
const replacement = 'background-color: rgb(0, 0, 0)';
|
|
1211
|
-
const patchedSetAttribute = function(name, value) {
|
|
1212
|
-
if (String(name).toLowerCase() === 'style' && normalize(value) === probeStyle) {
|
|
1213
|
-
return nativeSetAttribute.call(this, name, replacement);
|
|
1214
|
-
}
|
|
1215
|
-
return nativeSetAttribute.call(this, name, value);
|
|
1216
|
-
};
|
|
1217
|
-
try {
|
|
1218
|
-
Object.defineProperty(patchedSetAttribute, 'name', {
|
|
1219
|
-
value: 'setAttribute',
|
|
1220
|
-
configurable: true,
|
|
1221
|
-
});
|
|
1222
|
-
Object.defineProperty(patchedSetAttribute, 'toString', {
|
|
1223
|
-
value: () => nativeSetAttribute.toString(),
|
|
1224
|
-
configurable: true,
|
|
1225
|
-
});
|
|
1226
|
-
} catch {}
|
|
1227
|
-
try {
|
|
1228
|
-
Object.defineProperty(Element.prototype, 'setAttribute', {
|
|
1229
|
-
value: patchedSetAttribute,
|
|
1230
|
-
configurable: true,
|
|
1231
|
-
writable: true,
|
|
1232
|
-
});
|
|
1233
|
-
} catch {
|
|
1234
|
-
try { Element.prototype.setAttribute = patchedSetAttribute; } catch {}
|
|
1235
|
-
}
|
|
1236
|
-
})();`;
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Add missing connection.downlinkMax in Chromium headless environments.
|
|
1240
|
-
*/
|
|
1241
|
-
function patchNavigatorConnection() {
|
|
1242
|
-
return `(function(){
|
|
1243
|
-
if (!navigator.connection) return;
|
|
1244
|
-
const conn = navigator.connection;
|
|
1245
|
-
if (typeof conn.downlinkMax === 'number') return;
|
|
1246
|
-
const defineDownlinkMax = (target) => {
|
|
1247
|
-
if (!target) return false;
|
|
1248
|
-
try {
|
|
1249
|
-
Object.defineProperty(target, 'downlinkMax', {
|
|
1250
|
-
get: () => 10,
|
|
1251
|
-
configurable: true,
|
|
1252
|
-
});
|
|
1253
|
-
return true;
|
|
1254
|
-
} catch {
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
try {
|
|
1259
|
-
const proto = Object.getPrototypeOf(conn);
|
|
1260
|
-
if (defineDownlinkMax(proto)) {
|
|
1261
|
-
try { delete conn.downlinkMax; } catch {}
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
} catch {}
|
|
1265
|
-
defineDownlinkMax(conn);
|
|
1266
|
-
})();`;
|
|
1267
|
-
}
|
|
1268
|
-
/**
|
|
1269
|
-
* Ensure same-origin dedicated workers expose navigator.connection.downlinkMax too.
|
|
1270
|
-
* Skip cross-origin worker URLs to avoid breaking anti-bot challenge workers.
|
|
1271
|
-
*/
|
|
1272
|
-
function patchWorkerConnection() {
|
|
1273
|
-
return `(function(){
|
|
1274
|
-
if (typeof Worker !== 'function') return;
|
|
1275
|
-
const isCloudflareChallengeRuntime = (() => {
|
|
1276
|
-
try {
|
|
1277
|
-
const host = String(location.hostname || '').toLowerCase();
|
|
1278
|
-
const path = String(location.pathname || '');
|
|
1279
|
-
if (host === 'challenges.cloudflare.com') return true;
|
|
1280
|
-
return /\\/cdn-cgi\\/challenge-platform\\//.test(path);
|
|
1281
|
-
} catch {
|
|
1282
|
-
return false;
|
|
1283
|
-
}
|
|
1284
|
-
})();
|
|
1285
|
-
// Cloudflare challenge workers are sensitive to constructor wrapping.
|
|
1286
|
-
// Keep native Worker behavior in this runtime to avoid importScripts(blob) failures.
|
|
1287
|
-
if (isCloudflareChallengeRuntime) return;
|
|
1288
|
-
const NativeWorker = Worker;
|
|
1289
|
-
const workerPrelude = \`
|
|
1290
|
-
(() => {
|
|
1291
|
-
try {
|
|
1292
|
-
if (!navigator || !navigator.connection) return;
|
|
1293
|
-
const conn = navigator.connection;
|
|
1294
|
-
if (typeof conn.downlinkMax === 'number') return;
|
|
1295
|
-
const defineDownlinkMax = (target) => {
|
|
1296
|
-
if (!target) return false;
|
|
1297
|
-
try {
|
|
1298
|
-
Object.defineProperty(target, 'downlinkMax', {
|
|
1299
|
-
get: () => 10,
|
|
1300
|
-
configurable: true,
|
|
1301
|
-
});
|
|
1302
|
-
return true;
|
|
1303
|
-
} catch {
|
|
1304
|
-
return false;
|
|
1305
|
-
}
|
|
1306
|
-
};
|
|
1307
|
-
try {
|
|
1308
|
-
const proto = Object.getPrototypeOf(conn);
|
|
1309
|
-
if (defineDownlinkMax(proto)) {
|
|
1310
|
-
try { delete conn.downlinkMax; } catch {}
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
} catch {}
|
|
1314
|
-
defineDownlinkMax(conn);
|
|
1315
|
-
} catch {}
|
|
1316
|
-
})();
|
|
1317
|
-
\`;
|
|
1318
|
-
const buildPatchedScript = (url, options) => {
|
|
1319
|
-
const scriptUrl = String(url);
|
|
1320
|
-
const isModule = options && options.type === 'module';
|
|
1321
|
-
const loader = isModule
|
|
1322
|
-
? \`import \${JSON.stringify(scriptUrl)};\`
|
|
1323
|
-
: \`importScripts(\${JSON.stringify(scriptUrl)});\`;
|
|
1324
|
-
return \`\${workerPrelude}\\n\${loader}\`;
|
|
1325
|
-
};
|
|
1326
|
-
const resolveWorkerUrl = (value) => {
|
|
1327
|
-
try {
|
|
1328
|
-
return new URL(String(value), location.href);
|
|
1329
|
-
} catch {
|
|
1330
|
-
return null;
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
|
-
const shouldPatchWorker = (value) => {
|
|
1334
|
-
const resolved = resolveWorkerUrl(value);
|
|
1335
|
-
if (!resolved) return false;
|
|
1336
|
-
if (resolved.protocol === 'blob:') return resolved.origin === location.origin;
|
|
1337
|
-
if (resolved.protocol === 'http:' || resolved.protocol === 'https:') {
|
|
1338
|
-
return resolved.origin === location.origin;
|
|
1339
|
-
}
|
|
1340
|
-
if (resolved.protocol === 'file:') return location.protocol === 'file:';
|
|
1341
|
-
return false;
|
|
1342
|
-
};
|
|
1343
|
-
const WrappedWorker = function(scriptURL, options) {
|
|
1344
|
-
if (!shouldPatchWorker(scriptURL)) {
|
|
1345
|
-
return new NativeWorker(scriptURL, options);
|
|
1346
|
-
}
|
|
1347
|
-
try {
|
|
1348
|
-
const source = buildPatchedScript(scriptURL, options);
|
|
1349
|
-
const blob = new Blob([source], { type: 'application/javascript' });
|
|
1350
|
-
const patchedUrl = URL.createObjectURL(blob);
|
|
1351
|
-
const worker = new NativeWorker(patchedUrl, options);
|
|
1352
|
-
try {
|
|
1353
|
-
setTimeout(() => URL.revokeObjectURL(patchedUrl), 0);
|
|
1354
|
-
} catch {}
|
|
1355
|
-
return worker;
|
|
1356
|
-
} catch {
|
|
1357
|
-
return new NativeWorker(scriptURL, options);
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
WrappedWorker.prototype = NativeWorker.prototype;
|
|
1361
|
-
try {
|
|
1362
|
-
Object.setPrototypeOf(WrappedWorker, NativeWorker);
|
|
1363
|
-
} catch {}
|
|
1364
|
-
try {
|
|
1365
|
-
Object.defineProperty(WrappedWorker, 'name', { value: 'Worker', configurable: true });
|
|
1366
|
-
} catch {}
|
|
1367
|
-
try {
|
|
1368
|
-
Object.defineProperty(WrappedWorker, 'toString', {
|
|
1369
|
-
value: () => NativeWorker.toString(),
|
|
1370
|
-
configurable: true,
|
|
1371
|
-
});
|
|
1372
|
-
} catch {}
|
|
1373
|
-
try {
|
|
1374
|
-
Object.defineProperty(window, 'Worker', {
|
|
1375
|
-
value: WrappedWorker,
|
|
1376
|
-
configurable: true,
|
|
1377
|
-
writable: true,
|
|
1378
|
-
});
|
|
1379
|
-
} catch {}
|
|
1380
|
-
})();`;
|
|
1381
|
-
}
|
|
1382
|
-
/**
|
|
1383
|
-
* CreepJS marks light-scheme defaults as a weak headless signal. Keep
|
|
1384
|
-
* `(prefers-color-scheme: light)` neutral without affecting other media queries.
|
|
1385
|
-
*/
|
|
1386
|
-
function patchPrefersColorSchemeHeuristic() {
|
|
1387
|
-
return `(function(){
|
|
1388
|
-
if (typeof window.matchMedia !== 'function') return;
|
|
1389
|
-
const nativeMatchMedia = window.matchMedia.bind(window);
|
|
1390
|
-
const normalize = (query) => String(query).replace(/\\s+/g, ' ').trim().toLowerCase();
|
|
1391
|
-
const prefersLight = '(prefers-color-scheme: light)';
|
|
1392
|
-
const patchMediaQueryList = (mql) => {
|
|
1393
|
-
if (!mql || typeof mql !== 'object') return mql;
|
|
1394
|
-
return new Proxy(mql, {
|
|
1395
|
-
get(target, prop) {
|
|
1396
|
-
if (prop === 'matches') return false;
|
|
1397
|
-
const value = Reflect.get(target, prop, target);
|
|
1398
|
-
if (typeof value === 'function') {
|
|
1399
|
-
return value.bind(target);
|
|
1400
|
-
}
|
|
1401
|
-
return value;
|
|
1402
|
-
},
|
|
1403
|
-
});
|
|
1404
|
-
};
|
|
1405
|
-
const patchedMatchMedia = function(query) {
|
|
1406
|
-
const mql = nativeMatchMedia(query);
|
|
1407
|
-
if (normalize(query) === prefersLight) {
|
|
1408
|
-
return patchMediaQueryList(mql);
|
|
1409
|
-
}
|
|
1410
|
-
return mql;
|
|
1411
|
-
};
|
|
1412
|
-
try {
|
|
1413
|
-
Object.defineProperty(patchedMatchMedia, 'name', { value: 'matchMedia', configurable: true });
|
|
1414
|
-
Object.defineProperty(patchedMatchMedia, 'toString', {
|
|
1415
|
-
value: () => nativeMatchMedia.toString(),
|
|
1416
|
-
configurable: true,
|
|
1417
|
-
});
|
|
1418
|
-
} catch {}
|
|
1419
|
-
try {
|
|
1420
|
-
Object.defineProperty(window, 'matchMedia', {
|
|
1421
|
-
value: patchedMatchMedia,
|
|
1422
|
-
configurable: true,
|
|
1423
|
-
writable: true,
|
|
1424
|
-
});
|
|
1425
|
-
} catch {
|
|
1426
|
-
try { window.matchMedia = patchedMatchMedia; } catch {}
|
|
1427
|
-
}
|
|
1428
|
-
})();`;
|
|
1429
|
-
}
|
|
1430
|
-
/**
|
|
1431
|
-
* Add share/canShare APIs expected on modern Chromium desktop.
|
|
1432
|
-
*/
|
|
1433
|
-
function patchNavigatorShare() {
|
|
1434
|
-
return `(function(){
|
|
1435
|
-
if (typeof navigator.share !== 'function') {
|
|
1436
|
-
try {
|
|
1437
|
-
Object.defineProperty(navigator, 'share', {
|
|
1438
|
-
value: async () => undefined,
|
|
1439
|
-
configurable: true,
|
|
1440
|
-
});
|
|
1441
|
-
} catch {}
|
|
1442
|
-
}
|
|
1443
|
-
if (typeof navigator.canShare !== 'function') {
|
|
1444
|
-
try {
|
|
1445
|
-
Object.defineProperty(navigator, 'canShare', {
|
|
1446
|
-
value: () => true,
|
|
1447
|
-
configurable: true,
|
|
1448
|
-
});
|
|
1449
|
-
} catch {}
|
|
1450
|
-
}
|
|
1451
|
-
})();`;
|
|
1452
|
-
}
|
|
1453
|
-
/**
|
|
1454
|
-
* Add Contacts Manager stub to avoid "missing contacts manager" signals.
|
|
1455
|
-
*/
|
|
1456
|
-
function patchNavigatorContacts() {
|
|
1457
|
-
return `(function(){
|
|
1458
|
-
const ContactsCtor = typeof ContactsManager === 'function'
|
|
1459
|
-
? ContactsManager
|
|
1460
|
-
: function ContactsManager() {};
|
|
1461
|
-
try {
|
|
1462
|
-
Object.defineProperty(window, 'ContactsManager', {
|
|
1463
|
-
value: ContactsCtor,
|
|
1464
|
-
configurable: true,
|
|
1465
|
-
});
|
|
1466
|
-
} catch {}
|
|
1467
|
-
const manager = Object.create(ContactsCtor.prototype || Object.prototype);
|
|
1468
|
-
if (typeof manager.select !== 'function') {
|
|
1469
|
-
manager.select = async () => [];
|
|
1470
|
-
}
|
|
1471
|
-
if (typeof manager.getProperties !== 'function') {
|
|
1472
|
-
manager.getProperties = () => ['name', 'email', 'tel', 'address', 'icon'];
|
|
1473
|
-
}
|
|
1474
|
-
const defineContacts = (target) => {
|
|
1475
|
-
if (!target) return false;
|
|
1476
|
-
try {
|
|
1477
|
-
Object.defineProperty(target, 'contacts', {
|
|
1478
|
-
get: () => manager,
|
|
1479
|
-
configurable: true,
|
|
1480
|
-
});
|
|
1481
|
-
return true;
|
|
1482
|
-
} catch {
|
|
1483
|
-
return false;
|
|
1484
|
-
}
|
|
1485
|
-
};
|
|
1486
|
-
if (defineContacts(navigator)) return;
|
|
1487
|
-
try {
|
|
1488
|
-
defineContacts(Object.getPrototypeOf(navigator));
|
|
1489
|
-
} catch {}
|
|
1490
|
-
})();`;
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Expose ContentIndex APIs expected on modern Chromium.
|
|
1494
|
-
*/
|
|
1495
|
-
function patchContentIndex() {
|
|
1496
|
-
return `(function(){
|
|
1497
|
-
const ContentIndexCtor = typeof ContentIndex === 'function'
|
|
1498
|
-
? ContentIndex
|
|
1499
|
-
: function ContentIndex() {};
|
|
1500
|
-
try {
|
|
1501
|
-
Object.defineProperty(window, 'ContentIndex', {
|
|
1502
|
-
value: ContentIndexCtor,
|
|
1503
|
-
configurable: true,
|
|
1504
|
-
});
|
|
1505
|
-
} catch {}
|
|
1506
|
-
const index = Object.create(ContentIndexCtor.prototype || Object.prototype);
|
|
1507
|
-
if (typeof index.add !== 'function') {
|
|
1508
|
-
index.add = async () => undefined;
|
|
1509
|
-
}
|
|
1510
|
-
if (typeof index.delete !== 'function') {
|
|
1511
|
-
index.delete = async () => undefined;
|
|
1512
|
-
}
|
|
1513
|
-
if (typeof index.getAll !== 'function') {
|
|
1514
|
-
index.getAll = async () => [];
|
|
1515
|
-
}
|
|
1516
|
-
if (typeof ServiceWorkerRegistration === 'undefined') return;
|
|
1517
|
-
const defineIndex = (key) => {
|
|
1518
|
-
try {
|
|
1519
|
-
Object.defineProperty(ServiceWorkerRegistration.prototype, key, {
|
|
1520
|
-
get: () => index,
|
|
1521
|
-
configurable: true,
|
|
1522
|
-
});
|
|
1523
|
-
return true;
|
|
1524
|
-
} catch {
|
|
1525
|
-
return false;
|
|
1526
|
-
}
|
|
1527
|
-
};
|
|
1528
|
-
if (!('contentIndex' in ServiceWorkerRegistration.prototype)) {
|
|
1529
|
-
defineIndex('contentIndex');
|
|
1530
|
-
}
|
|
1531
|
-
if (!('index' in ServiceWorkerRegistration.prototype)) {
|
|
1532
|
-
defineIndex('index');
|
|
1533
|
-
}
|
|
1534
|
-
})();`;
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Chromium exposes navigator.pdfViewerEnabled=true in normal browsing mode.
|
|
1538
|
-
*/
|
|
1539
|
-
function patchPdfViewerEnabled() {
|
|
1540
|
-
return `(function(){
|
|
1541
|
-
if (navigator.pdfViewerEnabled === true) return;
|
|
1542
|
-
try {
|
|
1543
|
-
Object.defineProperty(navigator, 'pdfViewerEnabled', {
|
|
1544
|
-
get: () => true,
|
|
1545
|
-
configurable: true,
|
|
1546
|
-
});
|
|
1547
|
-
} catch {}
|
|
1548
|
-
})();`;
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* Chromium headless can under-report support for common media codecs.
|
|
1552
|
-
* Patch canPlayType for a narrow set of high-signal probes.
|
|
1553
|
-
*/
|
|
1554
|
-
function patchMediaCodecs() {
|
|
1555
|
-
return `(function(){
|
|
1556
|
-
if (typeof HTMLMediaElement === 'undefined' || !HTMLMediaElement.prototype) return;
|
|
1557
|
-
const nativeCanPlayType = HTMLMediaElement.prototype.canPlayType;
|
|
1558
|
-
if (typeof nativeCanPlayType !== 'function') return;
|
|
1559
|
-
const parseInput = (value) => {
|
|
1560
|
-
const input = String(value || '').trim();
|
|
1561
|
-
const [mimePart, codecPart] = input.split(';');
|
|
1562
|
-
const mime = String(mimePart || '').trim().toLowerCase();
|
|
1563
|
-
const codecs = [];
|
|
1564
|
-
if (codecPart && codecPart.includes('codecs=')) {
|
|
1565
|
-
const normalized = codecPart
|
|
1566
|
-
.replace(/^[^=]*=/, '')
|
|
1567
|
-
.replace(/^\\s*["']?/, '')
|
|
1568
|
-
.replace(/["']?\\s*$/, '');
|
|
1569
|
-
normalized
|
|
1570
|
-
.split(',')
|
|
1571
|
-
.map((codec) => codec.trim().toLowerCase())
|
|
1572
|
-
.filter(Boolean)
|
|
1573
|
-
.forEach((codec) => codecs.push(codec));
|
|
1574
|
-
}
|
|
1575
|
-
return { mime, codecs };
|
|
1576
|
-
};
|
|
1577
|
-
const patchedCanPlayType = function(type) {
|
|
1578
|
-
const { mime, codecs } = parseInput(type);
|
|
1579
|
-
if (mime === 'video/mp4' && codecs.includes('avc1.42e01e')) {
|
|
1580
|
-
return 'probably';
|
|
1581
|
-
}
|
|
1582
|
-
if (mime === 'audio/x-m4a' && codecs.length === 0) {
|
|
1583
|
-
return 'maybe';
|
|
1584
|
-
}
|
|
1585
|
-
if (mime === 'audio/aac' && codecs.length === 0) {
|
|
1586
|
-
return 'probably';
|
|
1587
|
-
}
|
|
1588
|
-
return nativeCanPlayType.call(this, type);
|
|
1589
|
-
};
|
|
1590
|
-
try {
|
|
1591
|
-
Object.defineProperty(patchedCanPlayType, 'name', {
|
|
1592
|
-
value: 'canPlayType',
|
|
1593
|
-
configurable: true,
|
|
1594
|
-
});
|
|
1595
|
-
Object.defineProperty(patchedCanPlayType, 'toString', {
|
|
1596
|
-
value: () => nativeCanPlayType.toString(),
|
|
1597
|
-
configurable: true,
|
|
1598
|
-
});
|
|
1599
|
-
} catch {}
|
|
1600
|
-
try {
|
|
1601
|
-
Object.defineProperty(HTMLMediaElement.prototype, 'canPlayType', {
|
|
1602
|
-
value: patchedCanPlayType,
|
|
1603
|
-
configurable: true,
|
|
1604
|
-
writable: true,
|
|
1605
|
-
});
|
|
1606
|
-
} catch {
|
|
1607
|
-
try { HTMLMediaElement.prototype.canPlayType = patchedCanPlayType; } catch {}
|
|
1608
|
-
}
|
|
1609
|
-
})();`;
|
|
1610
|
-
}
|
|
1611
|
-
/**
|
|
1612
|
-
* navigator.mediaDevices.enumerateDevices should return at least some devices
|
|
1613
|
-
* instead of an empty array (headless default).
|
|
1614
|
-
*/
|
|
1615
|
-
function patchMediaDevices() {
|
|
1616
|
-
return `(function(){
|
|
1617
|
-
if (!navigator.mediaDevices) return;
|
|
1618
|
-
const orig = navigator.mediaDevices.enumerateDevices;
|
|
1619
|
-
if (!orig) return;
|
|
1620
|
-
navigator.mediaDevices.enumerateDevices = async function() {
|
|
1621
|
-
const devices = await orig.call(navigator.mediaDevices);
|
|
1622
|
-
if (devices.length === 0) {
|
|
1623
|
-
return [
|
|
1624
|
-
{ deviceId: 'default', kind: 'audioinput', label: '', groupId: 'default' },
|
|
1625
|
-
{ deviceId: 'default', kind: 'videoinput', label: '', groupId: 'default' },
|
|
1626
|
-
{ deviceId: 'default', kind: 'audiooutput', label: '', groupId: 'default' },
|
|
1627
|
-
];
|
|
1628
|
-
}
|
|
1629
|
-
return devices;
|
|
1630
|
-
};
|
|
1631
|
-
})();`;
|
|
1632
|
-
}
|
|
1633
|
-
/**
|
|
1634
|
-
* Replace "HeadlessChrome" with "Chrome" in navigator.userAgent so
|
|
1635
|
-
* UA-based detection is bypassed at the JavaScript level.
|
|
1636
|
-
*/
|
|
1637
|
-
function patchUserAgent() {
|
|
1638
|
-
return `(function(){
|
|
1639
|
-
const ua = navigator.userAgent;
|
|
1640
|
-
if (ua.includes('HeadlessChrome')) {
|
|
1641
|
-
const patched = ua.replace(/HeadlessChrome/g, 'Chrome');
|
|
1642
|
-
Object.defineProperty(navigator, 'userAgent', {
|
|
1643
|
-
get: () => patched,
|
|
1644
|
-
configurable: true,
|
|
1645
|
-
});
|
|
1646
|
-
Object.defineProperty(navigator, 'appVersion', {
|
|
1647
|
-
get: () => patched.replace('Mozilla/', ''),
|
|
1648
|
-
configurable: true,
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
})();`;
|
|
1652
|
-
}
|
|
1653
|
-
/**
|
|
1654
|
-
* Ensure userAgentData does not expose "HeadlessChrome" brand tokens.
|
|
1655
|
-
*/
|
|
1656
|
-
function patchUserAgentData() {
|
|
1657
|
-
return `(function(){
|
|
1658
|
-
const uaData = navigator.userAgentData;
|
|
1659
|
-
if (!uaData) return;
|
|
1660
|
-
const sanitizeBrand = (brand) => {
|
|
1661
|
-
if (typeof brand !== 'string') return brand;
|
|
1662
|
-
return brand.replace(/HeadlessChrome/gi, 'Google Chrome');
|
|
1663
|
-
};
|
|
1664
|
-
const patchBrandList = (value) => {
|
|
1665
|
-
if (!Array.isArray(value)) return value;
|
|
1666
|
-
return value.map((entry) => ({
|
|
1667
|
-
...entry,
|
|
1668
|
-
brand: sanitizeBrand(entry.brand),
|
|
1669
|
-
}));
|
|
1670
|
-
};
|
|
1671
|
-
const patched = Object.create(Object.getPrototypeOf(uaData));
|
|
1672
|
-
Object.defineProperties(patched, {
|
|
1673
|
-
brands: {
|
|
1674
|
-
get: () => patchBrandList(uaData.brands),
|
|
1675
|
-
enumerable: true,
|
|
1676
|
-
},
|
|
1677
|
-
mobile: {
|
|
1678
|
-
get: () => uaData.mobile,
|
|
1679
|
-
enumerable: true,
|
|
1680
|
-
},
|
|
1681
|
-
platform: {
|
|
1682
|
-
get: () => uaData.platform,
|
|
1683
|
-
enumerable: true,
|
|
1684
|
-
},
|
|
1685
|
-
});
|
|
1686
|
-
patched.toJSON = () => ({
|
|
1687
|
-
brands: patchBrandList(uaData.brands),
|
|
1688
|
-
mobile: uaData.mobile,
|
|
1689
|
-
platform: uaData.platform,
|
|
1690
|
-
});
|
|
1691
|
-
patched.getHighEntropyValues = async (hints) => {
|
|
1692
|
-
const values = await uaData.getHighEntropyValues(hints);
|
|
1693
|
-
if (values && typeof values === 'object') {
|
|
1694
|
-
if ('brands' in values) values.brands = patchBrandList(values.brands);
|
|
1695
|
-
if ('fullVersionList' in values) {
|
|
1696
|
-
values.fullVersionList = patchBrandList(values.fullVersionList);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
return values;
|
|
1700
|
-
};
|
|
1701
|
-
try {
|
|
1702
|
-
Object.defineProperty(navigator, 'userAgentData', {
|
|
1703
|
-
get: () => patched,
|
|
1704
|
-
configurable: true,
|
|
1705
|
-
});
|
|
1706
|
-
} catch {}
|
|
1707
|
-
})();`;
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Provide a fake performance.memory (Chrome-only, non-standard).
|
|
1711
|
-
* Headless Chrome omits this; some detectors check for its presence.
|
|
1712
|
-
*/
|
|
1713
|
-
function patchPerformanceMemory() {
|
|
1714
|
-
return `(function(){
|
|
1715
|
-
if (!performance.memory) {
|
|
1716
|
-
Object.defineProperty(performance, 'memory', {
|
|
1717
|
-
get: () => ({
|
|
1718
|
-
jsHeapSizeLimit: 2172649472,
|
|
1719
|
-
totalJSHeapSize: 35839739,
|
|
1720
|
-
usedJSHeapSize: 22592767,
|
|
1721
|
-
}),
|
|
1722
|
-
configurable: true,
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
})();`;
|
|
1726
|
-
}
|
|
1727
|
-
/**
|
|
1728
|
-
* Headless Chrome has a transparent default background (rgba(0,0,0,0)).
|
|
1729
|
-
* Real browsers have an opaque white background. Set it early to avoid
|
|
1730
|
-
* the "hasKnownBgColor" detection.
|
|
1731
|
-
*/
|
|
1732
|
-
function patchDefaultBackgroundColor() {
|
|
1733
|
-
return `(function(){
|
|
1734
|
-
if (document.documentElement) {
|
|
1735
|
-
const style = getComputedStyle(document.documentElement);
|
|
1736
|
-
const bg = style.backgroundColor;
|
|
1737
|
-
if (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') {
|
|
1738
|
-
document.documentElement.style.backgroundColor = 'rgb(255, 255, 255)';
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
})();`;
|
|
1742
|
-
}
|
|
1743
|
-
//# sourceMappingURL=stealth.js.map
|