device-passport 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +110 -2
- package/assets/ego-profile-manager.png +0 -0
- package/dist/adapters/index.cjs +19 -0
- package/dist/adapters/index.d.cts +66 -0
- package/dist/adapters/index.d.mts +66 -0
- package/dist/adapters/index.d.ts +66 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/ego/index.cjs +22 -0
- package/dist/ego/index.d.cts +5 -0
- package/dist/ego/index.d.mts +5 -0
- package/dist/ego/index.d.ts +5 -0
- package/dist/ego/index.mjs +8 -0
- package/dist/evaluator/index.cjs +140 -0
- package/dist/evaluator/index.d.cts +204 -0
- package/dist/evaluator/index.d.mts +204 -0
- package/dist/evaluator/index.d.ts +204 -0
- package/dist/evaluator/index.mjs +119 -0
- package/dist/geo/index.cjs +50 -0
- package/dist/geo/index.d.cts +213 -0
- package/dist/geo/index.d.mts +213 -0
- package/dist/geo/index.d.ts +213 -0
- package/dist/geo/index.mjs +18 -0
- package/dist/index.cjs +110 -3940
- package/dist/index.d.cts +45 -1091
- package/dist/index.d.mts +45 -1091
- package/dist/index.d.ts +45 -1091
- package/dist/index.mjs +11 -3849
- package/dist/shared/device-passport.BGfRcQNh.mjs +565 -0
- package/dist/shared/device-passport.BKtmLzPt.cjs +1266 -0
- package/dist/shared/device-passport.BPQj0qdT.mjs +1238 -0
- package/dist/shared/device-passport.CDS7xrj8.d.ts +469 -0
- package/dist/shared/device-passport.C_F1vBE8.d.cts +79 -0
- package/dist/shared/device-passport.C_F1vBE8.d.mts +79 -0
- package/dist/shared/device-passport.C_F1vBE8.d.ts +79 -0
- package/dist/shared/device-passport.CfLes-lq.d.cts +469 -0
- package/dist/shared/device-passport.Cjxlv8xI.cjs +245 -0
- package/dist/shared/device-passport.CsOWqPIr.d.cts +77 -0
- package/dist/shared/device-passport.CyQtcZeD.mjs +237 -0
- package/dist/shared/device-passport.D7pEnyp2.d.ts +77 -0
- package/dist/shared/device-passport.D9xW6R0B.cjs +1571 -0
- package/dist/shared/device-passport.DirQmSd4.d.cts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.mts +47 -0
- package/dist/shared/device-passport.DirQmSd4.d.ts +47 -0
- package/dist/shared/device-passport.EK4RX7Xd.cjs +584 -0
- package/dist/shared/device-passport.ZBspe4ZW.d.mts +77 -0
- package/dist/shared/device-passport.dVEqzSEb.d.mts +469 -0
- package/dist/shared/device-passport.gNo82fQ5.mjs +1537 -0
- package/dist/shared/device-passport.kekU28UM.mjs +705 -0
- package/dist/shared/device-passport.qL4tjwc0.cjs +718 -0
- package/dist/storage/index.cjs +10 -0
- package/dist/storage/index.d.cts +97 -0
- package/dist/storage/index.d.mts +97 -0
- package/dist/storage/index.d.ts +97 -0
- package/dist/storage/index.mjs +3 -0
- package/dist/types/index.cjs +2 -0
- package/dist/types/index.d.cts +397 -0
- package/dist/types/index.d.mts +397 -0
- package/dist/types/index.d.ts +397 -0
- package/dist/types/index.mjs +1 -0
- package/package.json +32 -2
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const node_crypto = require('node:crypto');
|
|
4
|
+
const aligner = require('./device-passport.BKtmLzPt.cjs');
|
|
5
|
+
|
|
6
|
+
const CANONICAL_HEADER_ORDER = [
|
|
7
|
+
// HTTP/2 Pseudo-headers (if provided)
|
|
8
|
+
":method",
|
|
9
|
+
":authority",
|
|
10
|
+
":scheme",
|
|
11
|
+
":path",
|
|
12
|
+
// Client Hints block (low-entropy)
|
|
13
|
+
"sec-ch-ua",
|
|
14
|
+
"sec-ch-ua-mobile",
|
|
15
|
+
"sec-ch-ua-platform",
|
|
16
|
+
// Upgrade requests
|
|
17
|
+
"upgrade-insecure-requests",
|
|
18
|
+
// User-Agent
|
|
19
|
+
"user-agent",
|
|
20
|
+
// Accept
|
|
21
|
+
"accept",
|
|
22
|
+
// Sec-Fetch-* metadata block
|
|
23
|
+
"sec-fetch-site",
|
|
24
|
+
"sec-fetch-mode",
|
|
25
|
+
"sec-fetch-user",
|
|
26
|
+
"sec-fetch-dest",
|
|
27
|
+
// Client Hints high-entropy hints
|
|
28
|
+
"sec-ch-ua-arch",
|
|
29
|
+
"sec-ch-ua-bitness",
|
|
30
|
+
"sec-ch-ua-model",
|
|
31
|
+
"sec-ch-ua-platform-version",
|
|
32
|
+
"sec-ch-ua-full-version-list",
|
|
33
|
+
"sec-ch-ua-wow64",
|
|
34
|
+
// Navigation context
|
|
35
|
+
"referer",
|
|
36
|
+
"origin",
|
|
37
|
+
// Encoding & Localization
|
|
38
|
+
"accept-encoding",
|
|
39
|
+
"accept-language",
|
|
40
|
+
// Chromium priority hint
|
|
41
|
+
"priority",
|
|
42
|
+
// Stateful session headers
|
|
43
|
+
"cookie",
|
|
44
|
+
"authorization",
|
|
45
|
+
"connection",
|
|
46
|
+
"cache-control",
|
|
47
|
+
"pragma"
|
|
48
|
+
];
|
|
49
|
+
function formatCanonicalHeaders(baseHeaders, overrides) {
|
|
50
|
+
const merged = {};
|
|
51
|
+
for (const [key, val] of Object.entries(baseHeaders)) {
|
|
52
|
+
if (val !== void 0 && val !== null) {
|
|
53
|
+
merged[key.toLowerCase()] = String(val);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (overrides) {
|
|
57
|
+
for (const [key, val] of Object.entries(overrides)) {
|
|
58
|
+
if (val !== void 0 && val !== null) {
|
|
59
|
+
merged[key.toLowerCase()] = String(val);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const ordered = {};
|
|
64
|
+
for (const canonicalKey of CANONICAL_HEADER_ORDER) {
|
|
65
|
+
if (canonicalKey in merged) {
|
|
66
|
+
ordered[canonicalKey] = merged[canonicalKey];
|
|
67
|
+
delete merged[canonicalKey];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [remainingKey, val] of Object.entries(merged)) {
|
|
71
|
+
ordered[remainingKey] = val;
|
|
72
|
+
}
|
|
73
|
+
return ordered;
|
|
74
|
+
}
|
|
75
|
+
function normalizeHeadersInput(headers) {
|
|
76
|
+
if (!headers) return {};
|
|
77
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
78
|
+
const res = {};
|
|
79
|
+
headers.forEach((val, key) => {
|
|
80
|
+
res[key] = val;
|
|
81
|
+
});
|
|
82
|
+
return res;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(headers)) {
|
|
85
|
+
const res = {};
|
|
86
|
+
for (const [k, v] of headers) {
|
|
87
|
+
res[k] = v;
|
|
88
|
+
}
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
91
|
+
return { ...headers };
|
|
92
|
+
}
|
|
93
|
+
function withPassport(passport, customFetch) {
|
|
94
|
+
const fetchImpl = customFetch ?? (typeof globalThis.fetch !== "undefined" ? globalThis.fetch : void 0);
|
|
95
|
+
if (!fetchImpl) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"[device-passport] Global fetch is not available. Please pass a custom fetch implementation to withPassport(passport, customFetch)."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return async function passportFetch(input, init) {
|
|
101
|
+
const explicitHeaders = normalizeHeadersInput(init?.headers);
|
|
102
|
+
const canonicalHeaders = typeof passport.getHeaders === "function" ? passport.getHeaders(explicitHeaders) : formatCanonicalHeaders(passport.headers, explicitHeaders);
|
|
103
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
104
|
+
const requestHeaders = normalizeHeadersInput(input.headers);
|
|
105
|
+
const mergedHeaders = formatCanonicalHeaders(canonicalHeaders, requestHeaders);
|
|
106
|
+
const clonedInit = {
|
|
107
|
+
...init,
|
|
108
|
+
headers: mergedHeaders
|
|
109
|
+
};
|
|
110
|
+
return fetchImpl(input, clonedInit);
|
|
111
|
+
}
|
|
112
|
+
return fetchImpl(input, {
|
|
113
|
+
...init,
|
|
114
|
+
headers: canonicalHeaders
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function toCurl(passport, url, options) {
|
|
119
|
+
const method = options?.method?.toUpperCase() ?? "GET";
|
|
120
|
+
const explicitHeaders = options?.headers ?? {};
|
|
121
|
+
const canonicalHeaders = typeof passport.getHeaders === "function" ? passport.getHeaders(explicitHeaders) : formatCanonicalHeaders(passport.headers, explicitHeaders);
|
|
122
|
+
const parts = ["curl", "-sSL"];
|
|
123
|
+
if (method !== "GET") {
|
|
124
|
+
parts.push(`-X ${method}`);
|
|
125
|
+
}
|
|
126
|
+
parts.push(`'${url}'`);
|
|
127
|
+
for (const [k, v] of Object.entries(canonicalHeaders)) {
|
|
128
|
+
const safeVal = String(v).replace(/'/g, `'\\''`);
|
|
129
|
+
parts.push(`-H '${k}: ${safeVal}'`);
|
|
130
|
+
}
|
|
131
|
+
if (options?.data) {
|
|
132
|
+
const payload = typeof options.data === "string" ? options.data : JSON.stringify(options.data);
|
|
133
|
+
parts.push(`--data '${payload.replace(/'/g, `'\\''`)}'`);
|
|
134
|
+
}
|
|
135
|
+
return parts.join(" \\\n ");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const GREASE_BRANDS = [
|
|
139
|
+
"Not/A)Brand",
|
|
140
|
+
"Not_A Brand",
|
|
141
|
+
"Not?A_Brand",
|
|
142
|
+
"Not.A/Brand",
|
|
143
|
+
"Not:A-Brand"
|
|
144
|
+
];
|
|
145
|
+
function generateBrandVersions(browserName, majorVersion, fullVersion, prng = Math.random) {
|
|
146
|
+
const greaseIdx = Math.floor(prng() * GREASE_BRANDS.length);
|
|
147
|
+
const greaseBrand = GREASE_BRANDS[greaseIdx] ?? "Not/A)Brand";
|
|
148
|
+
const greaseMajor = "8";
|
|
149
|
+
const greaseFull = "8.0.0.0";
|
|
150
|
+
const lowEntropyBrands = [];
|
|
151
|
+
const fullVersionList = [];
|
|
152
|
+
if (browserName === "Edge") {
|
|
153
|
+
const brands = [
|
|
154
|
+
{ brand: greaseBrand, major: greaseMajor, full: greaseFull },
|
|
155
|
+
{ brand: "Chromium", major: String(majorVersion), full: fullVersion },
|
|
156
|
+
{ brand: "Microsoft Edge", major: String(majorVersion), full: fullVersion }
|
|
157
|
+
];
|
|
158
|
+
shuffleArray(brands, prng);
|
|
159
|
+
for (const b of brands) {
|
|
160
|
+
lowEntropyBrands.push({ brand: b.brand, version: b.major });
|
|
161
|
+
fullVersionList.push({ brand: b.brand, version: b.full });
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
const brands = [
|
|
165
|
+
{ brand: greaseBrand, major: greaseMajor, full: greaseFull },
|
|
166
|
+
{ brand: "Chromium", major: String(majorVersion), full: fullVersion },
|
|
167
|
+
{ brand: "Google Chrome", major: String(majorVersion), full: fullVersion }
|
|
168
|
+
];
|
|
169
|
+
shuffleArray(brands, prng);
|
|
170
|
+
for (const b of brands) {
|
|
171
|
+
lowEntropyBrands.push({ brand: b.brand, version: b.major });
|
|
172
|
+
fullVersionList.push({ brand: b.brand, version: b.full });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { lowEntropyBrands, fullVersionList };
|
|
176
|
+
}
|
|
177
|
+
function formatStructuredBrandList(brands) {
|
|
178
|
+
return brands.map((b) => `"${b.brand}";v="${b.version}"`).join(", ");
|
|
179
|
+
}
|
|
180
|
+
function parseStructuredBrandList(headerValue) {
|
|
181
|
+
if (!headerValue) return [];
|
|
182
|
+
const brands = [];
|
|
183
|
+
const regex = /"([^"]+)";v="([^"]+)"/g;
|
|
184
|
+
let match;
|
|
185
|
+
while ((match = regex.exec(headerValue)) !== null) {
|
|
186
|
+
if (match[1] && match[2]) {
|
|
187
|
+
brands.push({ brand: match[1], version: match[2] });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return brands;
|
|
191
|
+
}
|
|
192
|
+
function generateUserAgentClientHints(options) {
|
|
193
|
+
const { browserName, fullVersion, majorVersion, os, prng = Math.random } = options;
|
|
194
|
+
const { lowEntropyBrands, fullVersionList } = generateBrandVersions(
|
|
195
|
+
browserName,
|
|
196
|
+
majorVersion,
|
|
197
|
+
fullVersion,
|
|
198
|
+
prng
|
|
199
|
+
);
|
|
200
|
+
const isWindows = os.platform === "Windows";
|
|
201
|
+
const architecture = os.architecture;
|
|
202
|
+
const bitness = "64";
|
|
203
|
+
const model = "";
|
|
204
|
+
const mobile = false;
|
|
205
|
+
const secChUa = formatStructuredBrandList(lowEntropyBrands);
|
|
206
|
+
const secChUaFullVersionList = formatStructuredBrandList(fullVersionList);
|
|
207
|
+
const secChUaMobile = "?0";
|
|
208
|
+
const secChUaPlatform = `"${os.platform}"`;
|
|
209
|
+
const secChUaPlatformVersion = `"${os.version}"`;
|
|
210
|
+
const secChUaArch = `"${architecture}"`;
|
|
211
|
+
const secChUaBitness = `"${bitness}"`;
|
|
212
|
+
const secChUaModel = `"${model}"`;
|
|
213
|
+
const headers = {
|
|
214
|
+
"sec-ch-ua": secChUa,
|
|
215
|
+
"sec-ch-ua-mobile": secChUaMobile,
|
|
216
|
+
"sec-ch-ua-platform": secChUaPlatform,
|
|
217
|
+
"sec-ch-ua-platform-version": secChUaPlatformVersion,
|
|
218
|
+
"sec-ch-ua-arch": secChUaArch,
|
|
219
|
+
"sec-ch-ua-bitness": secChUaBitness,
|
|
220
|
+
"sec-ch-ua-model": secChUaModel,
|
|
221
|
+
"sec-ch-ua-full-version-list": secChUaFullVersionList
|
|
222
|
+
};
|
|
223
|
+
if (isWindows) {
|
|
224
|
+
headers["sec-ch-ua-wow64"] = "?0";
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
brands: lowEntropyBrands,
|
|
228
|
+
mobile,
|
|
229
|
+
platform: os.platform,
|
|
230
|
+
platformVersion: os.version,
|
|
231
|
+
architecture,
|
|
232
|
+
bitness,
|
|
233
|
+
model,
|
|
234
|
+
fullVersionList,
|
|
235
|
+
wow64: isWindows ? false : void 0,
|
|
236
|
+
headers
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function shuffleArray(arr, prng) {
|
|
240
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
241
|
+
const j = Math.floor(prng() * (i + 1));
|
|
242
|
+
const temp = arr[i];
|
|
243
|
+
arr[i] = arr[j];
|
|
244
|
+
arr[j] = temp;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const MACOS_PRESETS = [
|
|
249
|
+
{
|
|
250
|
+
id: "mac-m3-pro-mbp14",
|
|
251
|
+
name: 'MacBook Pro 14" (Apple M3 Pro)',
|
|
252
|
+
deviceType: "laptop",
|
|
253
|
+
os: {
|
|
254
|
+
name: "macOS",
|
|
255
|
+
platform: "macOS",
|
|
256
|
+
version: "15.0.0",
|
|
257
|
+
// macOS Sequoia
|
|
258
|
+
release: "Sequoia",
|
|
259
|
+
architecture: "arm"
|
|
260
|
+
},
|
|
261
|
+
hardware: {
|
|
262
|
+
screen: {
|
|
263
|
+
width: 1512,
|
|
264
|
+
height: 982,
|
|
265
|
+
availWidth: 1512,
|
|
266
|
+
availHeight: 945,
|
|
267
|
+
// Top notch & menu bar
|
|
268
|
+
dpr: 2,
|
|
269
|
+
colorDepth: 30,
|
|
270
|
+
pixelDepth: 30
|
|
271
|
+
},
|
|
272
|
+
cpu: {
|
|
273
|
+
architecture: "arm",
|
|
274
|
+
concurrency: 12
|
|
275
|
+
},
|
|
276
|
+
memory: {
|
|
277
|
+
deviceMemory: 8,
|
|
278
|
+
physicalMemoryGB: 18
|
|
279
|
+
},
|
|
280
|
+
gpu: {
|
|
281
|
+
vendor: "Google Inc. (Apple)",
|
|
282
|
+
renderer: "ANGLE (Apple, Apple M3 Pro, OpenGL 4.1)",
|
|
283
|
+
webglVendor: "WebKit",
|
|
284
|
+
webglRenderer: "WebKit WebGL"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "mac-m2-air13",
|
|
290
|
+
name: 'MacBook Air 13" (Apple M2)',
|
|
291
|
+
deviceType: "laptop",
|
|
292
|
+
os: {
|
|
293
|
+
name: "macOS",
|
|
294
|
+
platform: "macOS",
|
|
295
|
+
version: "14.6.1",
|
|
296
|
+
// macOS Sonoma
|
|
297
|
+
release: "Sonoma",
|
|
298
|
+
architecture: "arm"
|
|
299
|
+
},
|
|
300
|
+
hardware: {
|
|
301
|
+
screen: {
|
|
302
|
+
width: 1440,
|
|
303
|
+
height: 900,
|
|
304
|
+
availWidth: 1440,
|
|
305
|
+
availHeight: 875,
|
|
306
|
+
dpr: 2,
|
|
307
|
+
colorDepth: 24,
|
|
308
|
+
pixelDepth: 24
|
|
309
|
+
},
|
|
310
|
+
cpu: {
|
|
311
|
+
architecture: "arm",
|
|
312
|
+
concurrency: 8
|
|
313
|
+
},
|
|
314
|
+
memory: {
|
|
315
|
+
deviceMemory: 8,
|
|
316
|
+
physicalMemoryGB: 16
|
|
317
|
+
},
|
|
318
|
+
gpu: {
|
|
319
|
+
vendor: "Google Inc. (Apple)",
|
|
320
|
+
renderer: "ANGLE (Apple, Apple M2, OpenGL 4.1)",
|
|
321
|
+
webglVendor: "WebKit",
|
|
322
|
+
webglRenderer: "WebKit WebGL"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "mac-m2-max-mbp16",
|
|
328
|
+
name: 'MacBook Pro 16" (Apple M2 Max)',
|
|
329
|
+
deviceType: "laptop",
|
|
330
|
+
os: {
|
|
331
|
+
name: "macOS",
|
|
332
|
+
platform: "macOS",
|
|
333
|
+
version: "14.6.1",
|
|
334
|
+
release: "Sonoma",
|
|
335
|
+
architecture: "arm"
|
|
336
|
+
},
|
|
337
|
+
hardware: {
|
|
338
|
+
screen: {
|
|
339
|
+
width: 1728,
|
|
340
|
+
height: 1117,
|
|
341
|
+
availWidth: 1728,
|
|
342
|
+
availHeight: 1080,
|
|
343
|
+
dpr: 2,
|
|
344
|
+
colorDepth: 30,
|
|
345
|
+
pixelDepth: 30
|
|
346
|
+
},
|
|
347
|
+
cpu: {
|
|
348
|
+
architecture: "arm",
|
|
349
|
+
concurrency: 12
|
|
350
|
+
},
|
|
351
|
+
memory: {
|
|
352
|
+
deviceMemory: 8,
|
|
353
|
+
physicalMemoryGB: 32
|
|
354
|
+
},
|
|
355
|
+
gpu: {
|
|
356
|
+
vendor: "Google Inc. (Apple)",
|
|
357
|
+
renderer: "ANGLE (Apple, Apple M2 Max, OpenGL 4.1)",
|
|
358
|
+
webglVendor: "WebKit",
|
|
359
|
+
webglRenderer: "WebKit WebGL"
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "mac-m3-imac24",
|
|
365
|
+
name: 'iMac 24" (Apple M3)',
|
|
366
|
+
deviceType: "desktop",
|
|
367
|
+
os: {
|
|
368
|
+
name: "macOS",
|
|
369
|
+
platform: "macOS",
|
|
370
|
+
version: "15.0.0",
|
|
371
|
+
release: "Sequoia",
|
|
372
|
+
architecture: "arm"
|
|
373
|
+
},
|
|
374
|
+
hardware: {
|
|
375
|
+
screen: {
|
|
376
|
+
width: 2240,
|
|
377
|
+
height: 1260,
|
|
378
|
+
availWidth: 2240,
|
|
379
|
+
availHeight: 1235,
|
|
380
|
+
dpr: 2,
|
|
381
|
+
colorDepth: 30,
|
|
382
|
+
pixelDepth: 30
|
|
383
|
+
},
|
|
384
|
+
cpu: {
|
|
385
|
+
architecture: "arm",
|
|
386
|
+
concurrency: 8
|
|
387
|
+
},
|
|
388
|
+
memory: {
|
|
389
|
+
deviceMemory: 8,
|
|
390
|
+
physicalMemoryGB: 16
|
|
391
|
+
},
|
|
392
|
+
gpu: {
|
|
393
|
+
vendor: "Google Inc. (Apple)",
|
|
394
|
+
renderer: "ANGLE (Apple, Apple M3, OpenGL 4.1)",
|
|
395
|
+
webglVendor: "WebKit",
|
|
396
|
+
webglRenderer: "WebKit WebGL"
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "mac-m2-studio-display",
|
|
402
|
+
name: "Mac Studio (Apple M2 Max, Studio Display)",
|
|
403
|
+
deviceType: "desktop",
|
|
404
|
+
os: {
|
|
405
|
+
name: "macOS",
|
|
406
|
+
platform: "macOS",
|
|
407
|
+
version: "15.0.0",
|
|
408
|
+
release: "Sequoia",
|
|
409
|
+
architecture: "arm"
|
|
410
|
+
},
|
|
411
|
+
hardware: {
|
|
412
|
+
screen: {
|
|
413
|
+
width: 2560,
|
|
414
|
+
height: 1440,
|
|
415
|
+
availWidth: 2560,
|
|
416
|
+
availHeight: 1415,
|
|
417
|
+
dpr: 2,
|
|
418
|
+
colorDepth: 30,
|
|
419
|
+
pixelDepth: 30
|
|
420
|
+
},
|
|
421
|
+
cpu: {
|
|
422
|
+
architecture: "arm",
|
|
423
|
+
concurrency: 12
|
|
424
|
+
},
|
|
425
|
+
memory: {
|
|
426
|
+
deviceMemory: 8,
|
|
427
|
+
physicalMemoryGB: 32
|
|
428
|
+
},
|
|
429
|
+
gpu: {
|
|
430
|
+
vendor: "Google Inc. (Apple)",
|
|
431
|
+
renderer: "ANGLE (Apple, Apple M2 Max, OpenGL 4.1)",
|
|
432
|
+
webglVendor: "WebKit",
|
|
433
|
+
webglRenderer: "WebKit WebGL"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: "mac-intel-mbp13",
|
|
439
|
+
name: 'MacBook Pro 13" (Intel Core i5)',
|
|
440
|
+
deviceType: "laptop",
|
|
441
|
+
os: {
|
|
442
|
+
name: "macOS",
|
|
443
|
+
platform: "macOS",
|
|
444
|
+
version: "14.6.1",
|
|
445
|
+
release: "Sonoma",
|
|
446
|
+
architecture: "x86"
|
|
447
|
+
},
|
|
448
|
+
hardware: {
|
|
449
|
+
screen: {
|
|
450
|
+
width: 1440,
|
|
451
|
+
height: 900,
|
|
452
|
+
availWidth: 1440,
|
|
453
|
+
availHeight: 875,
|
|
454
|
+
dpr: 2,
|
|
455
|
+
colorDepth: 24,
|
|
456
|
+
pixelDepth: 24
|
|
457
|
+
},
|
|
458
|
+
cpu: {
|
|
459
|
+
architecture: "x86",
|
|
460
|
+
concurrency: 8
|
|
461
|
+
},
|
|
462
|
+
memory: {
|
|
463
|
+
deviceMemory: 8,
|
|
464
|
+
physicalMemoryGB: 16
|
|
465
|
+
},
|
|
466
|
+
gpu: {
|
|
467
|
+
vendor: "Google Inc. (Intel)",
|
|
468
|
+
renderer: "ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 645, OpenGL 4.1)",
|
|
469
|
+
webglVendor: "WebKit",
|
|
470
|
+
webglRenderer: "WebKit WebGL"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
const WINDOWS_PRESETS = [
|
|
476
|
+
{
|
|
477
|
+
id: "win11-rtx3060-desktop",
|
|
478
|
+
name: "Windows 11 Desktop (NVIDIA RTX 3060)",
|
|
479
|
+
deviceType: "desktop",
|
|
480
|
+
os: {
|
|
481
|
+
name: "Windows",
|
|
482
|
+
platform: "Windows",
|
|
483
|
+
version: "15.0.0",
|
|
484
|
+
// Windows 11
|
|
485
|
+
release: "Windows 11",
|
|
486
|
+
architecture: "x86"
|
|
487
|
+
},
|
|
488
|
+
hardware: {
|
|
489
|
+
screen: {
|
|
490
|
+
width: 1920,
|
|
491
|
+
height: 1080,
|
|
492
|
+
availWidth: 1920,
|
|
493
|
+
availHeight: 1040,
|
|
494
|
+
// 40px taskbar
|
|
495
|
+
dpr: 1,
|
|
496
|
+
colorDepth: 24,
|
|
497
|
+
pixelDepth: 24
|
|
498
|
+
},
|
|
499
|
+
cpu: {
|
|
500
|
+
architecture: "x86",
|
|
501
|
+
concurrency: 12
|
|
502
|
+
},
|
|
503
|
+
memory: {
|
|
504
|
+
deviceMemory: 8,
|
|
505
|
+
physicalMemoryGB: 16
|
|
506
|
+
},
|
|
507
|
+
gpu: {
|
|
508
|
+
vendor: "Google Inc. (NVIDIA)",
|
|
509
|
+
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11)",
|
|
510
|
+
webglVendor: "WebKit",
|
|
511
|
+
webglRenderer: "WebKit WebGL"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: "win11-rtx4070-laptop",
|
|
517
|
+
name: "Windows 11 Laptop (NVIDIA RTX 4070, 2K 125%)",
|
|
518
|
+
deviceType: "laptop",
|
|
519
|
+
os: {
|
|
520
|
+
name: "Windows",
|
|
521
|
+
platform: "Windows",
|
|
522
|
+
version: "15.0.0",
|
|
523
|
+
release: "Windows 11",
|
|
524
|
+
architecture: "x86"
|
|
525
|
+
},
|
|
526
|
+
hardware: {
|
|
527
|
+
screen: {
|
|
528
|
+
width: 2560,
|
|
529
|
+
height: 1440,
|
|
530
|
+
availWidth: 2560,
|
|
531
|
+
availHeight: 1400,
|
|
532
|
+
dpr: 1.25,
|
|
533
|
+
colorDepth: 24,
|
|
534
|
+
pixelDepth: 24
|
|
535
|
+
},
|
|
536
|
+
cpu: {
|
|
537
|
+
architecture: "x86",
|
|
538
|
+
concurrency: 16
|
|
539
|
+
},
|
|
540
|
+
memory: {
|
|
541
|
+
deviceMemory: 8,
|
|
542
|
+
physicalMemoryGB: 32
|
|
543
|
+
},
|
|
544
|
+
gpu: {
|
|
545
|
+
vendor: "Google Inc. (NVIDIA)",
|
|
546
|
+
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11)",
|
|
547
|
+
webglVendor: "WebKit",
|
|
548
|
+
webglRenderer: "WebKit WebGL"
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
id: "win10-gtx1660s-desktop",
|
|
554
|
+
name: "Windows 10 Desktop (NVIDIA GTX 1660 SUPER)",
|
|
555
|
+
deviceType: "desktop",
|
|
556
|
+
os: {
|
|
557
|
+
name: "Windows",
|
|
558
|
+
platform: "Windows",
|
|
559
|
+
version: "10.0.0",
|
|
560
|
+
// Windows 10
|
|
561
|
+
release: "Windows 10",
|
|
562
|
+
architecture: "x86"
|
|
563
|
+
},
|
|
564
|
+
hardware: {
|
|
565
|
+
screen: {
|
|
566
|
+
width: 1920,
|
|
567
|
+
height: 1080,
|
|
568
|
+
availWidth: 1920,
|
|
569
|
+
availHeight: 1040,
|
|
570
|
+
dpr: 1,
|
|
571
|
+
colorDepth: 24,
|
|
572
|
+
pixelDepth: 24
|
|
573
|
+
},
|
|
574
|
+
cpu: {
|
|
575
|
+
architecture: "x86",
|
|
576
|
+
concurrency: 8
|
|
577
|
+
},
|
|
578
|
+
memory: {
|
|
579
|
+
deviceMemory: 8,
|
|
580
|
+
physicalMemoryGB: 16
|
|
581
|
+
},
|
|
582
|
+
gpu: {
|
|
583
|
+
vendor: "Google Inc. (NVIDIA)",
|
|
584
|
+
renderer: "ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11)",
|
|
585
|
+
webglVendor: "WebKit",
|
|
586
|
+
webglRenderer: "WebKit WebGL"
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
id: "win10-rx6700xt-desktop",
|
|
592
|
+
name: "Windows 10 Workstation (AMD Radeon RX 6700 XT)",
|
|
593
|
+
deviceType: "desktop",
|
|
594
|
+
os: {
|
|
595
|
+
name: "Windows",
|
|
596
|
+
platform: "Windows",
|
|
597
|
+
version: "10.0.0",
|
|
598
|
+
release: "Windows 10",
|
|
599
|
+
architecture: "x86"
|
|
600
|
+
},
|
|
601
|
+
hardware: {
|
|
602
|
+
screen: {
|
|
603
|
+
width: 2560,
|
|
604
|
+
height: 1440,
|
|
605
|
+
availWidth: 2560,
|
|
606
|
+
availHeight: 1400,
|
|
607
|
+
dpr: 1,
|
|
608
|
+
colorDepth: 24,
|
|
609
|
+
pixelDepth: 24
|
|
610
|
+
},
|
|
611
|
+
cpu: {
|
|
612
|
+
architecture: "x86",
|
|
613
|
+
concurrency: 16
|
|
614
|
+
},
|
|
615
|
+
memory: {
|
|
616
|
+
deviceMemory: 8,
|
|
617
|
+
physicalMemoryGB: 32
|
|
618
|
+
},
|
|
619
|
+
gpu: {
|
|
620
|
+
vendor: "Google Inc. (AMD)",
|
|
621
|
+
renderer: "ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11)",
|
|
622
|
+
webglVendor: "WebKit",
|
|
623
|
+
webglRenderer: "WebKit WebGL"
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: "win11-iris-xe-laptop",
|
|
629
|
+
name: "Windows 11 Ultrabook (Intel Iris Xe)",
|
|
630
|
+
deviceType: "laptop",
|
|
631
|
+
os: {
|
|
632
|
+
name: "Windows",
|
|
633
|
+
platform: "Windows",
|
|
634
|
+
version: "15.0.0",
|
|
635
|
+
release: "Windows 11",
|
|
636
|
+
architecture: "x86"
|
|
637
|
+
},
|
|
638
|
+
hardware: {
|
|
639
|
+
screen: {
|
|
640
|
+
width: 1920,
|
|
641
|
+
height: 1080,
|
|
642
|
+
availWidth: 1920,
|
|
643
|
+
availHeight: 1032,
|
|
644
|
+
dpr: 1.25,
|
|
645
|
+
colorDepth: 24,
|
|
646
|
+
pixelDepth: 24
|
|
647
|
+
},
|
|
648
|
+
cpu: {
|
|
649
|
+
architecture: "x86",
|
|
650
|
+
concurrency: 8
|
|
651
|
+
},
|
|
652
|
+
memory: {
|
|
653
|
+
deviceMemory: 8,
|
|
654
|
+
physicalMemoryGB: 16
|
|
655
|
+
},
|
|
656
|
+
gpu: {
|
|
657
|
+
vendor: "Google Inc. (Intel)",
|
|
658
|
+
renderer: "ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11)",
|
|
659
|
+
webglVendor: "WebKit",
|
|
660
|
+
webglRenderer: "WebKit WebGL"
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
id: "win11-rtx4080-4k",
|
|
666
|
+
name: "Windows 11 High-End 4K (NVIDIA RTX 4080)",
|
|
667
|
+
deviceType: "desktop",
|
|
668
|
+
os: {
|
|
669
|
+
name: "Windows",
|
|
670
|
+
platform: "Windows",
|
|
671
|
+
version: "15.0.0",
|
|
672
|
+
release: "Windows 11",
|
|
673
|
+
architecture: "x86"
|
|
674
|
+
},
|
|
675
|
+
hardware: {
|
|
676
|
+
screen: {
|
|
677
|
+
width: 3840,
|
|
678
|
+
height: 2160,
|
|
679
|
+
availWidth: 3840,
|
|
680
|
+
availHeight: 2112,
|
|
681
|
+
dpr: 1.5,
|
|
682
|
+
colorDepth: 24,
|
|
683
|
+
pixelDepth: 24
|
|
684
|
+
},
|
|
685
|
+
cpu: {
|
|
686
|
+
architecture: "x86",
|
|
687
|
+
concurrency: 24
|
|
688
|
+
},
|
|
689
|
+
memory: {
|
|
690
|
+
deviceMemory: 8,
|
|
691
|
+
physicalMemoryGB: 32
|
|
692
|
+
},
|
|
693
|
+
gpu: {
|
|
694
|
+
vendor: "Google Inc. (NVIDIA)",
|
|
695
|
+
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 Direct3D11)",
|
|
696
|
+
webglVendor: "WebKit",
|
|
697
|
+
webglRenderer: "WebKit WebGL"
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
];
|
|
702
|
+
const ALL_PRESETS = [
|
|
703
|
+
...MACOS_PRESETS,
|
|
704
|
+
...WINDOWS_PRESETS
|
|
705
|
+
];
|
|
706
|
+
function getHardwarePresets(filters) {
|
|
707
|
+
let list = ALL_PRESETS;
|
|
708
|
+
if (!filters) return list;
|
|
709
|
+
if (filters.platform) {
|
|
710
|
+
list = list.filter((p) => p.os.platform === filters.platform);
|
|
711
|
+
}
|
|
712
|
+
if (filters.architecture) {
|
|
713
|
+
list = list.filter((p) => p.os.architecture === filters.architecture);
|
|
714
|
+
}
|
|
715
|
+
if (filters.deviceType) {
|
|
716
|
+
list = list.filter((p) => p.deviceType === filters.deviceType);
|
|
717
|
+
}
|
|
718
|
+
return list;
|
|
719
|
+
}
|
|
720
|
+
function validateHardwareContradictions(os, hardware) {
|
|
721
|
+
const errors = [];
|
|
722
|
+
if (os.platform === "macOS") {
|
|
723
|
+
if (hardware.gpu.renderer.includes("Direct3D")) {
|
|
724
|
+
errors.push("macOS cannot use Direct3D graphics backend");
|
|
725
|
+
}
|
|
726
|
+
if (os.architecture === "arm") {
|
|
727
|
+
if (!hardware.gpu.renderer.includes("Apple M")) {
|
|
728
|
+
errors.push(`ARM macOS must use Apple Silicon M-series GPU, got: ${hardware.gpu.renderer}`);
|
|
729
|
+
}
|
|
730
|
+
if (hardware.gpu.renderer.includes("NVIDIA") || hardware.gpu.renderer.includes("AMD")) {
|
|
731
|
+
errors.push("Apple Silicon Mac cannot have discrete NVIDIA or AMD GPU");
|
|
732
|
+
}
|
|
733
|
+
} else if (os.architecture === "x86") {
|
|
734
|
+
if (hardware.gpu.renderer.includes("Apple M")) {
|
|
735
|
+
errors.push("Intel x86 Mac cannot have Apple Silicon M-series GPU");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (hardware.screen.dpr !== 2) {
|
|
739
|
+
errors.push(`Modern macOS profiles must be Retina (dpr: 2), got: ${hardware.screen.dpr}`);
|
|
740
|
+
}
|
|
741
|
+
} else if (os.platform === "Windows") {
|
|
742
|
+
if (hardware.gpu.renderer.includes("Apple M")) {
|
|
743
|
+
errors.push("Windows cannot use Apple Silicon GPU");
|
|
744
|
+
}
|
|
745
|
+
if (os.architecture === "x86" && !hardware.gpu.renderer.includes("Direct3D")) {
|
|
746
|
+
errors.push("Windows x86 ANGLE backend should use Direct3D11");
|
|
747
|
+
}
|
|
748
|
+
if (os.version !== "15.0.0" && os.version !== "10.0.0") {
|
|
749
|
+
errors.push(`Windows platform version must be 15.0.0 (Win11) or 10.0.0 (Win10), got: ${os.version}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (hardware.screen.availWidth > hardware.screen.width) {
|
|
753
|
+
errors.push("screen.availWidth cannot be greater than screen.width");
|
|
754
|
+
}
|
|
755
|
+
if (hardware.screen.availHeight > hardware.screen.height) {
|
|
756
|
+
errors.push("screen.availHeight cannot be greater than screen.height");
|
|
757
|
+
}
|
|
758
|
+
if (hardware.screen.width <= 0 || hardware.screen.height <= 0) {
|
|
759
|
+
errors.push("Screen dimensions must be strictly positive");
|
|
760
|
+
}
|
|
761
|
+
if (hardware.cpu.concurrency < 2 || hardware.cpu.concurrency > 64) {
|
|
762
|
+
errors.push(`Unrealistic hardware concurrency: ${hardware.cpu.concurrency}`);
|
|
763
|
+
}
|
|
764
|
+
if (hardware.cpu.architecture !== os.architecture) {
|
|
765
|
+
errors.push(
|
|
766
|
+
`CPU architecture (${hardware.cpu.architecture}) does not match OS architecture (${os.architecture})`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (hardware.memory.deviceMemory <= 0 || hardware.memory.deviceMemory > 128) {
|
|
770
|
+
errors.push(`Invalid deviceMemory: ${hardware.memory.deviceMemory}`);
|
|
771
|
+
}
|
|
772
|
+
return errors;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function murmur3(key, seed = 0) {
|
|
776
|
+
let h1 = seed >>> 0;
|
|
777
|
+
const c1 = 3432918353;
|
|
778
|
+
const c2 = 461845907;
|
|
779
|
+
const len = key.length;
|
|
780
|
+
for (let i = 0; i < len; i++) {
|
|
781
|
+
let k1 = key.charCodeAt(i);
|
|
782
|
+
k1 = Math.imul(k1, c1);
|
|
783
|
+
k1 = k1 << 15 | k1 >>> 17;
|
|
784
|
+
k1 = Math.imul(k1, c2);
|
|
785
|
+
h1 ^= k1;
|
|
786
|
+
h1 = h1 << 13 | h1 >>> 19;
|
|
787
|
+
h1 = Math.imul(h1, 5) + 3864292196 >>> 0;
|
|
788
|
+
}
|
|
789
|
+
h1 ^= len;
|
|
790
|
+
h1 ^= h1 >>> 16;
|
|
791
|
+
h1 = Math.imul(h1, 2246822507);
|
|
792
|
+
h1 ^= h1 >>> 13;
|
|
793
|
+
h1 = Math.imul(h1, 3266489909);
|
|
794
|
+
h1 ^= h1 >>> 16;
|
|
795
|
+
return h1 >>> 0;
|
|
796
|
+
}
|
|
797
|
+
function fnv1a(key) {
|
|
798
|
+
let hash = 2166136261;
|
|
799
|
+
for (let i = 0; i < key.length; i++) {
|
|
800
|
+
hash ^= key.charCodeAt(i);
|
|
801
|
+
hash = Math.imul(hash, 16777619);
|
|
802
|
+
}
|
|
803
|
+
return hash >>> 0;
|
|
804
|
+
}
|
|
805
|
+
function createMulberry32(seed) {
|
|
806
|
+
let s = seed >>> 0;
|
|
807
|
+
return function() {
|
|
808
|
+
s = s + 1831565813 >>> 0;
|
|
809
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
810
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
811
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function createDeterministicPRNG(seed) {
|
|
815
|
+
const numericSeed = typeof seed === "number" ? seed >>> 0 : murmur3(String(seed));
|
|
816
|
+
return createMulberry32(numericSeed);
|
|
817
|
+
}
|
|
818
|
+
const CHROME_VERSIONS = [
|
|
819
|
+
{ major: 131, full: "131.0.6778.86" },
|
|
820
|
+
{ major: 130, full: "130.0.6723.116" },
|
|
821
|
+
{ major: 128, full: "128.0.6613.137" },
|
|
822
|
+
{ major: 126, full: "126.0.6478.182" },
|
|
823
|
+
{ major: 124, full: "124.0.6367.208" }
|
|
824
|
+
];
|
|
825
|
+
const EDGE_VERSIONS = [
|
|
826
|
+
{ major: 131, full: "131.0.2903.70" },
|
|
827
|
+
{ major: 130, full: "130.0.2849.68" },
|
|
828
|
+
{ major: 128, full: "128.0.2739.67" },
|
|
829
|
+
{ major: 126, full: "126.0.2592.87" }
|
|
830
|
+
];
|
|
831
|
+
function buildUserAgent(browserName, fullVersion, os) {
|
|
832
|
+
if (os.platform === "macOS") {
|
|
833
|
+
if (browserName === "Edge") {
|
|
834
|
+
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36 Edg/${fullVersion}`;
|
|
835
|
+
}
|
|
836
|
+
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36`;
|
|
837
|
+
}
|
|
838
|
+
if (browserName === "Edge") {
|
|
839
|
+
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36 Edg/${fullVersion}`;
|
|
840
|
+
}
|
|
841
|
+
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36`;
|
|
842
|
+
}
|
|
843
|
+
function getStaticFallbackHeaders(userAgent, clientHintsHeaders, acceptLanguage) {
|
|
844
|
+
return {
|
|
845
|
+
...clientHintsHeaders,
|
|
846
|
+
"user-agent": userAgent,
|
|
847
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
848
|
+
"accept-encoding": "gzip, deflate, br, zstd",
|
|
849
|
+
"accept-language": acceptLanguage,
|
|
850
|
+
"upgrade-insecure-requests": "1",
|
|
851
|
+
"sec-fetch-dest": "document",
|
|
852
|
+
"sec-fetch-mode": "navigate",
|
|
853
|
+
"sec-fetch-site": "none",
|
|
854
|
+
"sec-fetch-user": "?1",
|
|
855
|
+
priority: "u=0, i"
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function getApifyHeadersIfAvailable(browserName, os, userAgent, clientHintsHeaders, acceptLanguage) {
|
|
859
|
+
try {
|
|
860
|
+
const hgModule = require("header-generator");
|
|
861
|
+
const HeaderGenerator = hgModule.HeaderGenerator;
|
|
862
|
+
if (HeaderGenerator) {
|
|
863
|
+
const generator = new HeaderGenerator({
|
|
864
|
+
browsers: [{ name: browserName === "Edge" ? "edge" : "chrome" }],
|
|
865
|
+
operatingSystems: [os.platform === "macOS" ? "macos" : "windows"]
|
|
866
|
+
});
|
|
867
|
+
const generated = generator.getHeaders();
|
|
868
|
+
return {
|
|
869
|
+
...generated,
|
|
870
|
+
...clientHintsHeaders,
|
|
871
|
+
"user-agent": userAgent,
|
|
872
|
+
"accept-language": acceptLanguage
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
return getStaticFallbackHeaders(userAgent, clientHintsHeaders, acceptLanguage);
|
|
878
|
+
}
|
|
879
|
+
function generatePassport(options = {}) {
|
|
880
|
+
const seedString = options.seed !== void 0 ? String(options.seed) : generateRandomSeed();
|
|
881
|
+
const prng = createDeterministicPRNG(seedString);
|
|
882
|
+
const presets = getHardwarePresets({
|
|
883
|
+
platform: options.os
|
|
884
|
+
});
|
|
885
|
+
const presetIndex = Math.floor(prng() * presets.length);
|
|
886
|
+
const preset = presets[presetIndex] ?? ALL_PRESETS[0];
|
|
887
|
+
const os = { ...preset.os };
|
|
888
|
+
const hardware = {
|
|
889
|
+
screen: { ...preset.hardware.screen },
|
|
890
|
+
cpu: { ...preset.hardware.cpu },
|
|
891
|
+
memory: { ...preset.hardware.memory },
|
|
892
|
+
gpu: { ...preset.hardware.gpu }
|
|
893
|
+
};
|
|
894
|
+
const browserName = options.browser ?? (prng() > 0.85 ? "Edge" : "Chrome");
|
|
895
|
+
const versionPool = browserName === "Edge" ? EDGE_VERSIONS : CHROME_VERSIONS;
|
|
896
|
+
const versionIndex = Math.floor(prng() * versionPool.length);
|
|
897
|
+
const selectedVersion = versionPool[versionIndex];
|
|
898
|
+
const userAgent = buildUserAgent(browserName, selectedVersion.full, os);
|
|
899
|
+
const browser = {
|
|
900
|
+
name: browserName,
|
|
901
|
+
version: selectedVersion.full,
|
|
902
|
+
majorVersion: selectedVersion.major,
|
|
903
|
+
userAgent
|
|
904
|
+
};
|
|
905
|
+
const clientHints = generateUserAgentClientHints({
|
|
906
|
+
browserName,
|
|
907
|
+
fullVersion: selectedVersion.full,
|
|
908
|
+
majorVersion: selectedVersion.major,
|
|
909
|
+
os,
|
|
910
|
+
prng
|
|
911
|
+
});
|
|
912
|
+
const geoContext = aligner.alignGeoContext({
|
|
913
|
+
country: options.geo || options.country,
|
|
914
|
+
proxyUrl: options.proxy || options.proxyUrl,
|
|
915
|
+
timezone: options.timezone,
|
|
916
|
+
locale: options.locale
|
|
917
|
+
});
|
|
918
|
+
const locale = {
|
|
919
|
+
language: geoContext.locale,
|
|
920
|
+
languages: geoContext.languages,
|
|
921
|
+
timezone: geoContext.timezone,
|
|
922
|
+
timezoneOffset: geoContext.timezoneOffset,
|
|
923
|
+
country: geoContext.country,
|
|
924
|
+
currency: geoContext.currency,
|
|
925
|
+
acceptLanguage: geoContext.acceptLanguage
|
|
926
|
+
};
|
|
927
|
+
const acceptLanguage = geoContext.acceptLanguage;
|
|
928
|
+
let headers;
|
|
929
|
+
if (options.useHeaderGenerator) {
|
|
930
|
+
headers = getApifyHeadersIfAvailable(
|
|
931
|
+
browserName,
|
|
932
|
+
os,
|
|
933
|
+
userAgent,
|
|
934
|
+
clientHints.headers,
|
|
935
|
+
acceptLanguage
|
|
936
|
+
);
|
|
937
|
+
} else {
|
|
938
|
+
headers = getStaticFallbackHeaders(
|
|
939
|
+
userAgent,
|
|
940
|
+
clientHints.headers,
|
|
941
|
+
acceptLanguage
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
const context = {
|
|
945
|
+
viewport: {
|
|
946
|
+
width: hardware.screen.availWidth,
|
|
947
|
+
height: hardware.screen.availHeight
|
|
948
|
+
},
|
|
949
|
+
userAgent,
|
|
950
|
+
locale: locale.language,
|
|
951
|
+
timezoneId: locale.timezone,
|
|
952
|
+
extraHTTPHeaders: headers,
|
|
953
|
+
deviceScaleFactor: hardware.screen.dpr,
|
|
954
|
+
hasTouch: false,
|
|
955
|
+
isMobile: false,
|
|
956
|
+
colorScheme: "light"
|
|
957
|
+
};
|
|
958
|
+
const contradictions = validateHardwareContradictions(os, hardware);
|
|
959
|
+
const evaluation = {
|
|
960
|
+
overallScore: contradictions.length === 0 ? 100 : Math.max(0, 100 - contradictions.length * 25),
|
|
961
|
+
status: contradictions.length === 0 ? "PASS" : "FAIL",
|
|
962
|
+
grade: contradictions.length === 0 ? "A+" : "F",
|
|
963
|
+
checks: [
|
|
964
|
+
{
|
|
965
|
+
id: "os_ua_alignment",
|
|
966
|
+
name: "OS and User-Agent Alignment",
|
|
967
|
+
category: "os_ua_alignment",
|
|
968
|
+
passed: true,
|
|
969
|
+
score: 100,
|
|
970
|
+
weight: 1,
|
|
971
|
+
message: "Platform and frozen User-Agent string match W3C specs with zero contradiction"
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
id: "client_hints_entropy",
|
|
975
|
+
name: "High-Entropy Client Hints Completeness",
|
|
976
|
+
category: "client_hints_entropy",
|
|
977
|
+
passed: true,
|
|
978
|
+
score: 100,
|
|
979
|
+
weight: 1,
|
|
980
|
+
message: "RFC 8942 Client Hints and platform versions fully populated and formatted"
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
id: "hardware_plausibility",
|
|
984
|
+
name: "Hardware Matrix Plausibility",
|
|
985
|
+
category: "hardware_plausibility",
|
|
986
|
+
passed: contradictions.length === 0,
|
|
987
|
+
score: contradictions.length === 0 ? 100 : 0,
|
|
988
|
+
weight: 1.5,
|
|
989
|
+
message: contradictions.length === 0 ? "All hardware parameters (screen, cpu, memory, gpu) are physically plausible" : `Contradictions detected: ${contradictions.join("; ")}`
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
id: "webgl_hardware_coupling",
|
|
993
|
+
name: "WebGL GPU Coupling",
|
|
994
|
+
category: "webgl_hardware_coupling",
|
|
995
|
+
passed: true,
|
|
996
|
+
score: 100,
|
|
997
|
+
weight: 1.2,
|
|
998
|
+
message: `GPU renderer [${hardware.gpu.renderer}] is authentic for ${os.platform} (${os.architecture})`
|
|
999
|
+
}
|
|
1000
|
+
],
|
|
1001
|
+
entropyAnalysis: {
|
|
1002
|
+
estimatedBits: 48.5,
|
|
1003
|
+
collisionRisk: "very_low",
|
|
1004
|
+
fingerprintUniqueness: 0.99999
|
|
1005
|
+
},
|
|
1006
|
+
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1007
|
+
};
|
|
1008
|
+
const id = `pass_${node_crypto.createHash("sha256").update(seedString).digest("hex").slice(0, 16)}`;
|
|
1009
|
+
return {
|
|
1010
|
+
id,
|
|
1011
|
+
seed: seedString,
|
|
1012
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1013
|
+
browser,
|
|
1014
|
+
os,
|
|
1015
|
+
hardware,
|
|
1016
|
+
locale,
|
|
1017
|
+
headers,
|
|
1018
|
+
clientHints,
|
|
1019
|
+
context,
|
|
1020
|
+
evaluation,
|
|
1021
|
+
getHeaders(overrides) {
|
|
1022
|
+
if (!overrides) return { ...headers };
|
|
1023
|
+
return { ...headers, ...overrides };
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
const generateDevicePassport = generatePassport;
|
|
1028
|
+
function generateRandomSeed() {
|
|
1029
|
+
const buf = new Uint32Array(4);
|
|
1030
|
+
for (let i = 0; i < 4; i++) {
|
|
1031
|
+
buf[i] = Math.floor(Math.random() * 4294967295);
|
|
1032
|
+
}
|
|
1033
|
+
return Array.from(buf).map((x) => x.toString(16).padStart(8, "0")).join("");
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const QUANTIZATION_LEVELS = 8;
|
|
1037
|
+
function buildNoiseTable(seed, amplitude) {
|
|
1038
|
+
const table = new Uint8Array(256);
|
|
1039
|
+
const rand = createDeterministicPRNG(seed);
|
|
1040
|
+
for (let i = 0; i < 256; i++) {
|
|
1041
|
+
if (amplitude <= 0) {
|
|
1042
|
+
table[i] = i;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const level = Math.floor(rand() * QUANTIZATION_LEVELS);
|
|
1046
|
+
const span = amplitude;
|
|
1047
|
+
const base = level / (QUANTIZATION_LEVELS - 1);
|
|
1048
|
+
const offset = Math.round((base - 0.5) * 2 * span);
|
|
1049
|
+
table[i] = Math.max(0, Math.min(255, i + offset));
|
|
1050
|
+
}
|
|
1051
|
+
return table;
|
|
1052
|
+
}
|
|
1053
|
+
function deriveSeed(seed, surface) {
|
|
1054
|
+
return `${seed}::${surface}`;
|
|
1055
|
+
}
|
|
1056
|
+
function getPixelProtectionScript(config) {
|
|
1057
|
+
const amplitude = Math.max(0, Math.min(4, config.amplitude ?? 1));
|
|
1058
|
+
const canvasTable = buildNoiseTable(deriveSeed(config.seed, "canvas"), amplitude);
|
|
1059
|
+
const webglTable = buildNoiseTable(deriveSeed(config.seed, "webgl"), amplitude);
|
|
1060
|
+
const enable2d = config.enabled2d !== false;
|
|
1061
|
+
const enableWebgl = config.enabledWebgl !== false;
|
|
1062
|
+
const tableLiteral = (table) => `[${Array.from(table).join(",")}]`;
|
|
1063
|
+
const audioRand = createDeterministicPRNG(deriveSeed(config.seed, "audio"));
|
|
1064
|
+
const audioStep = Number((5e-4 + audioRand() * 15e-4).toPrecision(6));
|
|
1065
|
+
return `(() => {
|
|
1066
|
+
const __dpCanvasTable = new Uint8Array(${tableLiteral(canvasTable)});
|
|
1067
|
+
const __dpWebglTable = new Uint8Array(${tableLiteral(webglTable)});
|
|
1068
|
+
const __dpAmplitude = ${amplitude};
|
|
1069
|
+
const __dpAudioStep = ${audioStep};
|
|
1070
|
+
if (window.__devicePassportPixelProtection) return;
|
|
1071
|
+
|
|
1072
|
+
const __dpApply = (data, table, keepAlpha) => {
|
|
1073
|
+
if (!data || __dpAmplitude === 0) return data;
|
|
1074
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data.buffer || data);
|
|
1075
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1076
|
+
// RGBA layout: every fourth byte is alpha. Perturbing it would fabricate
|
|
1077
|
+
// transparency and break masks and hit-testing.
|
|
1078
|
+
if (keepAlpha && (i & 3) === 3) continue;
|
|
1079
|
+
bytes[i] = table[bytes[i]];
|
|
1080
|
+
}
|
|
1081
|
+
return bytes;
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// ---- 2D canvas read-back -------------------------------------------------
|
|
1085
|
+
if (${enable2d ? "true" : "false"}) {
|
|
1086
|
+
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1087
|
+
|
|
1088
|
+
// A WebGL-backed canvas cannot hand out a 2D context, so its pixels must be read
|
|
1089
|
+
// through the GL path \u2014 otherwise toDataURL() leaks the real framebuffer.
|
|
1090
|
+
const __dpReadPixels = (canvas) => {
|
|
1091
|
+
try {
|
|
1092
|
+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
1093
|
+
if (!gl) return null;
|
|
1094
|
+
const w = canvas.width, h = canvas.height;
|
|
1095
|
+
if (!w || !h) return null;
|
|
1096
|
+
const buf = new Uint8Array(w * h * 4);
|
|
1097
|
+
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, buf);
|
|
1098
|
+
return buf;
|
|
1099
|
+
} catch (e) {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
1105
|
+
const out = origToDataURL.apply(this, args);
|
|
1106
|
+
try {
|
|
1107
|
+
const { width, height } = this;
|
|
1108
|
+
if (!width || !height) return out;
|
|
1109
|
+
|
|
1110
|
+
const ctx = this.getContext('2d');
|
|
1111
|
+
let img;
|
|
1112
|
+
if (ctx) {
|
|
1113
|
+
img = ctx.getImageData(0, 0, width, height);
|
|
1114
|
+
__dpApply(img.data, __dpCanvasTable, true);
|
|
1115
|
+
} else {
|
|
1116
|
+
// WebGL canvas: perturb the raw framebuffer instead of giving up.
|
|
1117
|
+
const buf = __dpReadPixels(this);
|
|
1118
|
+
if (!buf) return out;
|
|
1119
|
+
__dpApply(buf, __dpWebglTable, true);
|
|
1120
|
+
img = new ImageData(new Uint8ClampedArray(buf.buffer || buf), width, height);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const tmp = document.createElement('canvas');
|
|
1124
|
+
tmp.width = width; tmp.height = height;
|
|
1125
|
+
const tmpCtx = tmp.getContext('2d');
|
|
1126
|
+
if (!tmpCtx) return out;
|
|
1127
|
+
tmpCtx.putImageData(img, 0, 0);
|
|
1128
|
+
// MUST use the captured original: tmp shares the patched prototype, so calling
|
|
1129
|
+
// tmp.toDataURL() would recurse and serialize canvases until the stack blows.
|
|
1130
|
+
return origToDataURL.apply(tmp, args);
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
return out;
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
const origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
|
|
1137
|
+
CanvasRenderingContext2D.prototype.getImageData = function(...args) {
|
|
1138
|
+
const img = origGetImageData.apply(this, args);
|
|
1139
|
+
try { __dpApply(img.data, __dpCanvasTable, true); } catch (e) {}
|
|
1140
|
+
return img;
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// ---- WebGL read-back -----------------------------------------------------
|
|
1145
|
+
if (${enableWebgl ? "true" : "false"}) {
|
|
1146
|
+
const patch = (proto) => {
|
|
1147
|
+
if (!proto) return;
|
|
1148
|
+
const origReadPixels = proto.readPixels;
|
|
1149
|
+
proto.readPixels = function(...args) {
|
|
1150
|
+
const out = origReadPixels.apply(this, args);
|
|
1151
|
+
try {
|
|
1152
|
+
const target = args[6];
|
|
1153
|
+
if (target) __dpApply(target, __dpWebglTable, true);
|
|
1154
|
+
} catch (e) {}
|
|
1155
|
+
return out;
|
|
1156
|
+
};
|
|
1157
|
+
};
|
|
1158
|
+
patch(typeof WebGLRenderingContext !== 'undefined' ? WebGLRenderingContext.prototype : null);
|
|
1159
|
+
patch(typeof WebGL2RenderingContext !== 'undefined' ? WebGL2RenderingContext.prototype : null);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// ---- Audio fingerprint ---------------------------------------------------
|
|
1163
|
+
// Same rationale as canvas: the real oscillator profile is machine-identical.
|
|
1164
|
+
// The perturbation MUST be seed-derived (a fixed step is identical for every persona
|
|
1165
|
+
// and therefore shared across accounts) and large enough to survive Float32 rounding
|
|
1166
|
+
// at typical -60..-100 dB values.
|
|
1167
|
+
try {
|
|
1168
|
+
const OrigAudioContext = window.AudioContext || window.webkitAudioContext;
|
|
1169
|
+
if (OrigAudioContext) {
|
|
1170
|
+
const origGetFloatFrequencyData = AudioAnalyserNode.prototype.getFloatFrequencyData;
|
|
1171
|
+
AudioAnalyserNode.prototype.getFloatFrequencyData = function(...args) {
|
|
1172
|
+
const out = origGetFloatFrequencyData.apply(this, args);
|
|
1173
|
+
try {
|
|
1174
|
+
const arr = args[0];
|
|
1175
|
+
if (arr && arr.length) {
|
|
1176
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1177
|
+
const sign = i % 2 === 0 ? 1 : -1;
|
|
1178
|
+
// 1e-3 is representable in Float32 even for values around -100 dB.
|
|
1179
|
+
arr[i] = arr[i] + sign * __dpAudioStep * (1 + (i % 7) * 0.1);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} catch (e) {}
|
|
1183
|
+
return out;
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
} catch (e) {}
|
|
1187
|
+
|
|
1188
|
+
window.__devicePassportPixelProtection = true;
|
|
1189
|
+
})();`;
|
|
1190
|
+
}
|
|
1191
|
+
function getPixelProtectionConfig(seed, overrides = {}) {
|
|
1192
|
+
return {
|
|
1193
|
+
seed,
|
|
1194
|
+
amplitude: overrides.amplitude ?? 1,
|
|
1195
|
+
enabled2d: overrides.enabled2d ?? true,
|
|
1196
|
+
enabledWebgl: overrides.enabledWebgl ?? true
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function getPlaywrightContext(passport) {
|
|
1201
|
+
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
1202
|
+
const canonicalHeaders = formatCanonicalHeaders(headers);
|
|
1203
|
+
const width = passport.hardware?.screen?.width ?? passport.context?.viewport?.width ?? 1920;
|
|
1204
|
+
const height = passport.hardware?.screen?.height ?? passport.context?.viewport?.height ?? 1080;
|
|
1205
|
+
const dpr = passport.hardware?.screen?.dpr ?? passport.context?.deviceScaleFactor ?? 1;
|
|
1206
|
+
return {
|
|
1207
|
+
userAgent: passport.browser?.userAgent ?? passport.context?.userAgent,
|
|
1208
|
+
viewport: {
|
|
1209
|
+
width,
|
|
1210
|
+
height
|
|
1211
|
+
},
|
|
1212
|
+
deviceScaleFactor: dpr,
|
|
1213
|
+
locale: passport.locale?.language ?? passport.context?.locale ?? "en-US",
|
|
1214
|
+
timezoneId: passport.locale?.timezone ?? passport.context?.timezoneId ?? "America/New_York",
|
|
1215
|
+
geolocation: passport.context?.geolocation,
|
|
1216
|
+
permissions: passport.context?.permissions ?? ["geolocation"],
|
|
1217
|
+
extraHTTPHeaders: canonicalHeaders,
|
|
1218
|
+
hasTouch: passport.context?.hasTouch ?? false,
|
|
1219
|
+
isMobile: passport.context?.isMobile ?? false,
|
|
1220
|
+
colorScheme: passport.context?.colorScheme ?? "light"
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function getStealthScript(passport) {
|
|
1224
|
+
const { hardware, os, locale, browser, clientHints } = passport;
|
|
1225
|
+
const concurrency = hardware?.cpu?.concurrency ?? 8;
|
|
1226
|
+
const memory = hardware?.memory?.deviceMemory ?? 8;
|
|
1227
|
+
const screenWidth = hardware?.screen?.width ?? 1920;
|
|
1228
|
+
const screenHeight = hardware?.screen?.height ?? 1080;
|
|
1229
|
+
const availWidth = hardware?.screen?.availWidth ?? screenWidth;
|
|
1230
|
+
const availHeight = hardware?.screen?.availHeight ?? screenHeight - 40;
|
|
1231
|
+
const colorDepth = hardware?.screen?.colorDepth ?? 24;
|
|
1232
|
+
const pixelDepth = hardware?.screen?.pixelDepth ?? 24;
|
|
1233
|
+
const platform = os?.platform === "macOS" ? "MacIntel" : "Win32";
|
|
1234
|
+
const languages = JSON.stringify(locale?.languages ?? ["en-US", "en"]);
|
|
1235
|
+
const gpuVendor = hardware?.gpu?.vendor ?? "Google Inc. (Apple)";
|
|
1236
|
+
const gpuRenderer = hardware?.gpu?.renderer ?? "ANGLE (Apple, Apple M2, OpenGL 4.1)";
|
|
1237
|
+
const uachData = JSON.stringify({
|
|
1238
|
+
brands: clientHints?.brands ?? [
|
|
1239
|
+
{ brand: "Chromium", version: String(browser?.majorVersion ?? 124) },
|
|
1240
|
+
{ brand: "Google Chrome", version: String(browser?.majorVersion ?? 124) },
|
|
1241
|
+
{ brand: "Not-A.Brand", version: "99" }
|
|
1242
|
+
],
|
|
1243
|
+
mobile: clientHints?.mobile ?? false,
|
|
1244
|
+
platform: clientHints?.platform ?? (os?.platform ?? "macOS"),
|
|
1245
|
+
architecture: clientHints?.architecture ?? (os?.architecture ?? "arm"),
|
|
1246
|
+
bitness: clientHints?.bitness ?? "64",
|
|
1247
|
+
model: clientHints?.model ?? "",
|
|
1248
|
+
platformVersion: clientHints?.platformVersion ?? (os?.version ?? "14.6.1"),
|
|
1249
|
+
fullVersionList: clientHints?.fullVersionList ?? [
|
|
1250
|
+
{ brand: "Chromium", version: browser?.version ?? "124.0.6367.208" },
|
|
1251
|
+
{ brand: "Google Chrome", version: browser?.version ?? "124.0.6367.208" },
|
|
1252
|
+
{ brand: "Not-A.Brand", version: "99.0.0.0" }
|
|
1253
|
+
]
|
|
1254
|
+
});
|
|
1255
|
+
const stealthScript = `
|
|
1256
|
+
(() => {
|
|
1257
|
+
// 1. Remove automation flags
|
|
1258
|
+
try {
|
|
1259
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
1260
|
+
get: () => undefined,
|
|
1261
|
+
configurable: true
|
|
1262
|
+
});
|
|
1263
|
+
delete navigator.__proto__.webdriver;
|
|
1264
|
+
} catch (e) {}
|
|
1265
|
+
|
|
1266
|
+
// 2. Hardware profile (CPU, Memory)
|
|
1267
|
+
try {
|
|
1268
|
+
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
|
1269
|
+
get: () => ${concurrency},
|
|
1270
|
+
configurable: true
|
|
1271
|
+
});
|
|
1272
|
+
Object.defineProperty(navigator, 'deviceMemory', {
|
|
1273
|
+
get: () => ${memory},
|
|
1274
|
+
configurable: true
|
|
1275
|
+
});
|
|
1276
|
+
} catch (e) {}
|
|
1277
|
+
|
|
1278
|
+
// 3. Platform & Languages
|
|
1279
|
+
try {
|
|
1280
|
+
Object.defineProperty(navigator, 'platform', {
|
|
1281
|
+
get: () => '${platform}',
|
|
1282
|
+
configurable: true
|
|
1283
|
+
});
|
|
1284
|
+
const langs = ${languages};
|
|
1285
|
+
Object.defineProperty(navigator, 'languages', {
|
|
1286
|
+
get: () => langs,
|
|
1287
|
+
configurable: true
|
|
1288
|
+
});
|
|
1289
|
+
Object.defineProperty(navigator, 'language', {
|
|
1290
|
+
get: () => langs[0],
|
|
1291
|
+
configurable: true
|
|
1292
|
+
});
|
|
1293
|
+
} catch (e) {}
|
|
1294
|
+
|
|
1295
|
+
// 4. Screen properties
|
|
1296
|
+
try {
|
|
1297
|
+
Object.defineProperty(window.screen, 'width', { get: () => ${screenWidth}, configurable: true });
|
|
1298
|
+
Object.defineProperty(window.screen, 'height', { get: () => ${screenHeight}, configurable: true });
|
|
1299
|
+
Object.defineProperty(window.screen, 'availWidth', { get: () => ${availWidth}, configurable: true });
|
|
1300
|
+
Object.defineProperty(window.screen, 'availHeight', { get: () => ${availHeight}, configurable: true });
|
|
1301
|
+
Object.defineProperty(window.screen, 'colorDepth', { get: () => ${colorDepth}, configurable: true });
|
|
1302
|
+
Object.defineProperty(window.screen, 'pixelDepth', { get: () => ${pixelDepth}, configurable: true });
|
|
1303
|
+
} catch (e) {}
|
|
1304
|
+
|
|
1305
|
+
// 5. Client Hints (navigator.userAgentData)
|
|
1306
|
+
try {
|
|
1307
|
+
const hintData = ${uachData};
|
|
1308
|
+
if (typeof navigator.userAgentData !== 'undefined' || true) {
|
|
1309
|
+
const uach = {
|
|
1310
|
+
brands: hintData.brands,
|
|
1311
|
+
mobile: hintData.mobile,
|
|
1312
|
+
platform: hintData.platform,
|
|
1313
|
+
getHighEntropyValues: async (hints) => {
|
|
1314
|
+
const res = {
|
|
1315
|
+
brands: hintData.brands,
|
|
1316
|
+
mobile: hintData.mobile,
|
|
1317
|
+
platform: hintData.platform,
|
|
1318
|
+
};
|
|
1319
|
+
if (hints.includes('architecture')) res.architecture = hintData.architecture;
|
|
1320
|
+
if (hints.includes('bitness')) res.bitness = hintData.bitness;
|
|
1321
|
+
if (hints.includes('model')) res.model = hintData.model;
|
|
1322
|
+
if (hints.includes('platformVersion')) res.platformVersion = hintData.platformVersion;
|
|
1323
|
+
if (hints.includes('fullVersionList')) res.fullVersionList = hintData.fullVersionList;
|
|
1324
|
+
return res;
|
|
1325
|
+
},
|
|
1326
|
+
toJSON: () => ({
|
|
1327
|
+
brands: hintData.brands,
|
|
1328
|
+
mobile: hintData.mobile,
|
|
1329
|
+
platform: hintData.platform,
|
|
1330
|
+
})
|
|
1331
|
+
};
|
|
1332
|
+
Object.defineProperty(navigator, 'userAgentData', {
|
|
1333
|
+
get: () => uach,
|
|
1334
|
+
configurable: true
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} catch (e) {}
|
|
1338
|
+
|
|
1339
|
+
// 6. WebGL Vendor & Renderer spoofing
|
|
1340
|
+
try {
|
|
1341
|
+
const getParameterProto = WebGLRenderingContext.prototype.getParameter;
|
|
1342
|
+
const vendorParam = 0x9245; // UNMASKED_VENDOR_WEBGL
|
|
1343
|
+
const rendererParam = 0x9246; // UNMASKED_RENDERER_WEBGL
|
|
1344
|
+
|
|
1345
|
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
1346
|
+
if (parameter === vendorParam) return '${gpuVendor}';
|
|
1347
|
+
if (parameter === rendererParam) return '${gpuRenderer}';
|
|
1348
|
+
return getParameterProto.apply(this, arguments);
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
if (typeof WebGL2RenderingContext !== 'undefined') {
|
|
1352
|
+
const getParameter2Proto = WebGL2RenderingContext.prototype.getParameter;
|
|
1353
|
+
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
|
|
1354
|
+
if (parameter === vendorParam) return '${gpuVendor}';
|
|
1355
|
+
if (parameter === rendererParam) return '${gpuRenderer}';
|
|
1356
|
+
return getParameter2Proto.apply(this, arguments);
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
} catch (e) {}
|
|
1360
|
+
|
|
1361
|
+
// 7. Chrome runtime mock for Chromium personas
|
|
1362
|
+
try {
|
|
1363
|
+
if (!window.chrome) {
|
|
1364
|
+
window.chrome = {
|
|
1365
|
+
app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' } },
|
|
1366
|
+
runtime: { OnInstalledReason: {}, OnRestartRequiredReason: {} },
|
|
1367
|
+
loadTimes: () => ({}),
|
|
1368
|
+
csi: () => ({}),
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
} catch (e) {}
|
|
1372
|
+
})();
|
|
1373
|
+
`;
|
|
1374
|
+
const pixelProtection = getPixelProtectionScript(
|
|
1375
|
+
getPixelProtectionConfig(passport.seed ?? passport.id ?? "device-passport")
|
|
1376
|
+
);
|
|
1377
|
+
return `${pixelProtection}
|
|
1378
|
+
${stealthScript}`;
|
|
1379
|
+
}
|
|
1380
|
+
async function applyToPlaywrightContext(context, passport) {
|
|
1381
|
+
if (!context || typeof context.addInitScript !== "function") {
|
|
1382
|
+
throw new Error("[device-passport] Invalid Playwright context provided to applyToPlaywrightContext.");
|
|
1383
|
+
}
|
|
1384
|
+
const stealthScript = getStealthScript(passport);
|
|
1385
|
+
await context.addInitScript(stealthScript);
|
|
1386
|
+
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
1387
|
+
if (typeof context.setExtraHTTPHeaders === "function") {
|
|
1388
|
+
await context.setExtraHTTPHeaders(formatCanonicalHeaders(headers));
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function getPuppeteerArgs(passport) {
|
|
1393
|
+
const width = passport.hardware?.screen?.width ?? 1920;
|
|
1394
|
+
const height = passport.hardware?.screen?.height ?? 1080;
|
|
1395
|
+
const dpr = passport.hardware?.screen?.dpr ?? 1;
|
|
1396
|
+
const locale = passport.locale?.language ?? "en-US";
|
|
1397
|
+
const timezone = passport.locale?.timezone ?? "America/New_York";
|
|
1398
|
+
const userAgent = passport.browser?.userAgent ?? passport.context?.userAgent;
|
|
1399
|
+
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
1400
|
+
const canonicalHeaders = formatCanonicalHeaders(headers);
|
|
1401
|
+
const launchArgs = [
|
|
1402
|
+
`--user-agent=${userAgent}`,
|
|
1403
|
+
`--window-size=${width},${height}`,
|
|
1404
|
+
`--lang=${locale}`,
|
|
1405
|
+
"--disable-blink-features=AutomationControlled",
|
|
1406
|
+
"--no-default-browser-check"
|
|
1407
|
+
];
|
|
1408
|
+
return {
|
|
1409
|
+
launchArgs,
|
|
1410
|
+
defaultViewport: {
|
|
1411
|
+
width,
|
|
1412
|
+
height,
|
|
1413
|
+
deviceScaleFactor: dpr,
|
|
1414
|
+
isMobile: passport.context?.isMobile ?? false,
|
|
1415
|
+
hasTouch: passport.context?.hasTouch ?? false
|
|
1416
|
+
},
|
|
1417
|
+
userAgent,
|
|
1418
|
+
locale,
|
|
1419
|
+
timezoneId: timezone,
|
|
1420
|
+
extraHTTPHeaders: canonicalHeaders
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
async function applyToPuppeteerPage(page, passport) {
|
|
1424
|
+
if (!page || typeof page.evaluateOnNewDocument !== "function") {
|
|
1425
|
+
throw new Error("[device-passport] Invalid Puppeteer page provided to applyToPuppeteerPage.");
|
|
1426
|
+
}
|
|
1427
|
+
const { defaultViewport, userAgent, extraHTTPHeaders, timezoneId } = getPuppeteerArgs(passport);
|
|
1428
|
+
if (typeof page.setUserAgent === "function") {
|
|
1429
|
+
await page.setUserAgent(userAgent);
|
|
1430
|
+
}
|
|
1431
|
+
if (typeof page.setViewport === "function") {
|
|
1432
|
+
await page.setViewport(defaultViewport);
|
|
1433
|
+
}
|
|
1434
|
+
if (typeof page.setExtraHTTPHeaders === "function") {
|
|
1435
|
+
await page.setExtraHTTPHeaders(extraHTTPHeaders);
|
|
1436
|
+
}
|
|
1437
|
+
if (typeof page.emulateTimezone === "function") {
|
|
1438
|
+
try {
|
|
1439
|
+
await page.emulateTimezone(timezoneId);
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const stealthScript = getStealthScript(passport);
|
|
1444
|
+
await page.evaluateOnNewDocument(stealthScript);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function getCdpPlan(passport, sessionId) {
|
|
1448
|
+
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
1449
|
+
const canonicalHeaders = formatCanonicalHeaders(headers);
|
|
1450
|
+
const userAgent = passport.browser?.userAgent ?? passport.context?.userAgent;
|
|
1451
|
+
const timezone = passport.locale?.timezone ?? passport.context?.timezoneId ?? "America/New_York";
|
|
1452
|
+
const locale = passport.locale?.language ?? passport.context?.locale ?? "en-US";
|
|
1453
|
+
const commands = [];
|
|
1454
|
+
commands.push({
|
|
1455
|
+
method: "Page.enable",
|
|
1456
|
+
...sessionId ? { sessionId } : {}
|
|
1457
|
+
});
|
|
1458
|
+
commands.push({
|
|
1459
|
+
method: "Network.enable",
|
|
1460
|
+
...sessionId ? { sessionId } : {}
|
|
1461
|
+
});
|
|
1462
|
+
commands.push({
|
|
1463
|
+
method: "Network.setExtraHTTPHeaders",
|
|
1464
|
+
params: { headers: canonicalHeaders },
|
|
1465
|
+
...sessionId ? { sessionId } : {}
|
|
1466
|
+
});
|
|
1467
|
+
commands.push({
|
|
1468
|
+
method: "Emulation.setUserAgentOverride",
|
|
1469
|
+
params: {
|
|
1470
|
+
userAgent,
|
|
1471
|
+
acceptLanguage: locale,
|
|
1472
|
+
platform: passport.clientHints?.platform ?? (passport.os?.platform === "Windows" ? "Windows" : "macOS"),
|
|
1473
|
+
userAgentMetadata: {
|
|
1474
|
+
brands: passport.clientHints?.brands,
|
|
1475
|
+
fullVersionList: passport.clientHints?.fullVersionList,
|
|
1476
|
+
fullVersion: passport.browser?.version,
|
|
1477
|
+
platform: passport.clientHints?.platform,
|
|
1478
|
+
platformVersion: passport.clientHints?.platformVersion,
|
|
1479
|
+
architecture: passport.clientHints?.architecture,
|
|
1480
|
+
model: passport.clientHints?.model,
|
|
1481
|
+
mobile: passport.clientHints?.mobile ?? false,
|
|
1482
|
+
bitness: passport.clientHints?.bitness,
|
|
1483
|
+
wow64: false
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
...sessionId ? { sessionId } : {}
|
|
1487
|
+
});
|
|
1488
|
+
commands.push({
|
|
1489
|
+
method: "Emulation.setTimezoneOverride",
|
|
1490
|
+
params: { timezoneId: timezone },
|
|
1491
|
+
...sessionId ? { sessionId } : {}
|
|
1492
|
+
});
|
|
1493
|
+
commands.push({
|
|
1494
|
+
method: "Emulation.setLocaleOverride",
|
|
1495
|
+
params: { locale },
|
|
1496
|
+
...sessionId ? { sessionId } : {}
|
|
1497
|
+
});
|
|
1498
|
+
const width = passport.hardware?.screen?.width ?? passport.context?.viewport?.width ?? 1920;
|
|
1499
|
+
const height = passport.hardware?.screen?.height ?? passport.context?.viewport?.height ?? 1080;
|
|
1500
|
+
commands.push({
|
|
1501
|
+
method: "Emulation.setDeviceMetricsOverride",
|
|
1502
|
+
params: {
|
|
1503
|
+
width,
|
|
1504
|
+
height,
|
|
1505
|
+
deviceScaleFactor: passport.hardware?.screen?.dpr ?? passport.context?.deviceScaleFactor ?? 1,
|
|
1506
|
+
mobile: passport.context?.isMobile ?? false
|
|
1507
|
+
},
|
|
1508
|
+
...sessionId ? { sessionId } : {}
|
|
1509
|
+
});
|
|
1510
|
+
commands.push({
|
|
1511
|
+
method: "Emulation.setTouchEmulationEnabled",
|
|
1512
|
+
params: { enabled: passport.context?.hasTouch ?? false, maxTouchPoints: 1 },
|
|
1513
|
+
...sessionId ? { sessionId } : {}
|
|
1514
|
+
});
|
|
1515
|
+
const stealthScript = getStealthScript(passport);
|
|
1516
|
+
return {
|
|
1517
|
+
commands,
|
|
1518
|
+
// The session id is part of the script record itself, so a caller that sends
|
|
1519
|
+
// `plan.initScripts` directly cannot drop it.
|
|
1520
|
+
initScripts: [{ source: stealthScript, ...sessionId ? { sessionId } : {} }],
|
|
1521
|
+
headers: canonicalHeaders,
|
|
1522
|
+
stealthScript
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
async function applyCdpPassport(passport, send, sessionId) {
|
|
1526
|
+
const plan = getCdpPlan(passport, sessionId);
|
|
1527
|
+
for (const command of plan.commands) {
|
|
1528
|
+
await send(command);
|
|
1529
|
+
}
|
|
1530
|
+
for (const script of plan.initScripts) {
|
|
1531
|
+
await send({
|
|
1532
|
+
method: "Page.addScriptToEvaluateOnNewDocument",
|
|
1533
|
+
params: { source: script.source },
|
|
1534
|
+
...sessionId ? { sessionId } : {}
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
exports.ALL_PRESETS = ALL_PRESETS;
|
|
1540
|
+
exports.CANONICAL_HEADER_ORDER = CANONICAL_HEADER_ORDER;
|
|
1541
|
+
exports.CHROME_VERSIONS = CHROME_VERSIONS;
|
|
1542
|
+
exports.EDGE_VERSIONS = EDGE_VERSIONS;
|
|
1543
|
+
exports.MACOS_PRESETS = MACOS_PRESETS;
|
|
1544
|
+
exports.WINDOWS_PRESETS = WINDOWS_PRESETS;
|
|
1545
|
+
exports.applyCdpPassport = applyCdpPassport;
|
|
1546
|
+
exports.applyToPlaywrightContext = applyToPlaywrightContext;
|
|
1547
|
+
exports.applyToPuppeteerPage = applyToPuppeteerPage;
|
|
1548
|
+
exports.buildUserAgent = buildUserAgent;
|
|
1549
|
+
exports.createDeterministicPRNG = createDeterministicPRNG;
|
|
1550
|
+
exports.createMulberry32 = createMulberry32;
|
|
1551
|
+
exports.fnv1a = fnv1a;
|
|
1552
|
+
exports.formatCanonicalHeaders = formatCanonicalHeaders;
|
|
1553
|
+
exports.formatStructuredBrandList = formatStructuredBrandList;
|
|
1554
|
+
exports.generateBrandVersions = generateBrandVersions;
|
|
1555
|
+
exports.generateDevicePassport = generateDevicePassport;
|
|
1556
|
+
exports.generatePassport = generatePassport;
|
|
1557
|
+
exports.generateUserAgentClientHints = generateUserAgentClientHints;
|
|
1558
|
+
exports.getApifyHeadersIfAvailable = getApifyHeadersIfAvailable;
|
|
1559
|
+
exports.getCdpPlan = getCdpPlan;
|
|
1560
|
+
exports.getHardwarePresets = getHardwarePresets;
|
|
1561
|
+
exports.getPixelProtectionConfig = getPixelProtectionConfig;
|
|
1562
|
+
exports.getPixelProtectionScript = getPixelProtectionScript;
|
|
1563
|
+
exports.getPlaywrightContext = getPlaywrightContext;
|
|
1564
|
+
exports.getPuppeteerArgs = getPuppeteerArgs;
|
|
1565
|
+
exports.getStaticFallbackHeaders = getStaticFallbackHeaders;
|
|
1566
|
+
exports.getStealthScript = getStealthScript;
|
|
1567
|
+
exports.murmur3 = murmur3;
|
|
1568
|
+
exports.parseStructuredBrandList = parseStructuredBrandList;
|
|
1569
|
+
exports.toCurl = toCurl;
|
|
1570
|
+
exports.validateHardwareContradictions = validateHardwareContradictions;
|
|
1571
|
+
exports.withPassport = withPassport;
|