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
package/dist/index.cjs
CHANGED
|
@@ -1,3858 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// HTTP/2 Pseudo-headers (if provided)
|
|
14
|
-
":method",
|
|
15
|
-
":authority",
|
|
16
|
-
":scheme",
|
|
17
|
-
":path",
|
|
18
|
-
// Client Hints block (low-entropy)
|
|
19
|
-
"sec-ch-ua",
|
|
20
|
-
"sec-ch-ua-mobile",
|
|
21
|
-
"sec-ch-ua-platform",
|
|
22
|
-
// Upgrade requests
|
|
23
|
-
"upgrade-insecure-requests",
|
|
24
|
-
// User-Agent
|
|
25
|
-
"user-agent",
|
|
26
|
-
// Accept
|
|
27
|
-
"accept",
|
|
28
|
-
// Sec-Fetch-* metadata block
|
|
29
|
-
"sec-fetch-site",
|
|
30
|
-
"sec-fetch-mode",
|
|
31
|
-
"sec-fetch-user",
|
|
32
|
-
"sec-fetch-dest",
|
|
33
|
-
// Client Hints high-entropy hints
|
|
34
|
-
"sec-ch-ua-arch",
|
|
35
|
-
"sec-ch-ua-bitness",
|
|
36
|
-
"sec-ch-ua-model",
|
|
37
|
-
"sec-ch-ua-platform-version",
|
|
38
|
-
"sec-ch-ua-full-version-list",
|
|
39
|
-
"sec-ch-ua-wow64",
|
|
40
|
-
// Navigation context
|
|
41
|
-
"referer",
|
|
42
|
-
"origin",
|
|
43
|
-
// Encoding & Localization
|
|
44
|
-
"accept-encoding",
|
|
45
|
-
"accept-language",
|
|
46
|
-
// Chromium priority hint
|
|
47
|
-
"priority",
|
|
48
|
-
// Stateful session headers
|
|
49
|
-
"cookie",
|
|
50
|
-
"authorization",
|
|
51
|
-
"connection",
|
|
52
|
-
"cache-control",
|
|
53
|
-
"pragma"
|
|
54
|
-
];
|
|
55
|
-
function formatCanonicalHeaders(baseHeaders, overrides) {
|
|
56
|
-
const merged = {};
|
|
57
|
-
for (const [key, val] of Object.entries(baseHeaders)) {
|
|
58
|
-
if (val !== void 0 && val !== null) {
|
|
59
|
-
merged[key.toLowerCase()] = String(val);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (overrides) {
|
|
63
|
-
for (const [key, val] of Object.entries(overrides)) {
|
|
64
|
-
if (val !== void 0 && val !== null) {
|
|
65
|
-
merged[key.toLowerCase()] = String(val);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const ordered = {};
|
|
70
|
-
for (const canonicalKey of CANONICAL_HEADER_ORDER) {
|
|
71
|
-
if (canonicalKey in merged) {
|
|
72
|
-
ordered[canonicalKey] = merged[canonicalKey];
|
|
73
|
-
delete merged[canonicalKey];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
for (const [remainingKey, val] of Object.entries(merged)) {
|
|
77
|
-
ordered[remainingKey] = val;
|
|
78
|
-
}
|
|
79
|
-
return ordered;
|
|
80
|
-
}
|
|
81
|
-
function normalizeHeadersInput(headers) {
|
|
82
|
-
if (!headers) return {};
|
|
83
|
-
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
84
|
-
const res = {};
|
|
85
|
-
headers.forEach((val, key) => {
|
|
86
|
-
res[key] = val;
|
|
87
|
-
});
|
|
88
|
-
return res;
|
|
89
|
-
}
|
|
90
|
-
if (Array.isArray(headers)) {
|
|
91
|
-
const res = {};
|
|
92
|
-
for (const [k, v] of headers) {
|
|
93
|
-
res[k] = v;
|
|
94
|
-
}
|
|
95
|
-
return res;
|
|
96
|
-
}
|
|
97
|
-
return { ...headers };
|
|
98
|
-
}
|
|
99
|
-
function withPassport(passport, customFetch) {
|
|
100
|
-
const fetchImpl = customFetch ?? (typeof globalThis.fetch !== "undefined" ? globalThis.fetch : void 0);
|
|
101
|
-
if (!fetchImpl) {
|
|
102
|
-
throw new Error(
|
|
103
|
-
"[device-passport] Global fetch is not available. Please pass a custom fetch implementation to withPassport(passport, customFetch)."
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
return async function passportFetch(input, init) {
|
|
107
|
-
const explicitHeaders = normalizeHeadersInput(init?.headers);
|
|
108
|
-
const canonicalHeaders = typeof passport.getHeaders === "function" ? passport.getHeaders(explicitHeaders) : formatCanonicalHeaders(passport.headers, explicitHeaders);
|
|
109
|
-
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
110
|
-
const requestHeaders = normalizeHeadersInput(input.headers);
|
|
111
|
-
const mergedHeaders = formatCanonicalHeaders(canonicalHeaders, requestHeaders);
|
|
112
|
-
const clonedInit = {
|
|
113
|
-
...init,
|
|
114
|
-
headers: mergedHeaders
|
|
115
|
-
};
|
|
116
|
-
return fetchImpl(input, clonedInit);
|
|
117
|
-
}
|
|
118
|
-
return fetchImpl(input, {
|
|
119
|
-
...init,
|
|
120
|
-
headers: canonicalHeaders
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
function toCurl(passport, url, options) {
|
|
125
|
-
const method = options?.method?.toUpperCase() ?? "GET";
|
|
126
|
-
const explicitHeaders = options?.headers ?? {};
|
|
127
|
-
const canonicalHeaders = typeof passport.getHeaders === "function" ? passport.getHeaders(explicitHeaders) : formatCanonicalHeaders(passport.headers, explicitHeaders);
|
|
128
|
-
const parts = ["curl", "-sSL"];
|
|
129
|
-
if (method !== "GET") {
|
|
130
|
-
parts.push(`-X ${method}`);
|
|
131
|
-
}
|
|
132
|
-
parts.push(`'${url}'`);
|
|
133
|
-
for (const [k, v] of Object.entries(canonicalHeaders)) {
|
|
134
|
-
const safeVal = String(v).replace(/'/g, `'\\''`);
|
|
135
|
-
parts.push(`-H '${k}: ${safeVal}'`);
|
|
136
|
-
}
|
|
137
|
-
if (options?.data) {
|
|
138
|
-
const payload = typeof options.data === "string" ? options.data : JSON.stringify(options.data);
|
|
139
|
-
parts.push(`--data '${payload.replace(/'/g, `'\\''`)}'`);
|
|
140
|
-
}
|
|
141
|
-
return parts.join(" \\\n ");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function getPlaywrightContext(passport) {
|
|
145
|
-
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
146
|
-
const canonicalHeaders = formatCanonicalHeaders(headers);
|
|
147
|
-
const width = passport.hardware?.screen?.width ?? passport.context?.viewport?.width ?? 1920;
|
|
148
|
-
const height = passport.hardware?.screen?.height ?? passport.context?.viewport?.height ?? 1080;
|
|
149
|
-
const dpr = passport.hardware?.screen?.dpr ?? passport.context?.deviceScaleFactor ?? 1;
|
|
150
|
-
return {
|
|
151
|
-
userAgent: passport.browser?.userAgent ?? passport.context?.userAgent,
|
|
152
|
-
viewport: {
|
|
153
|
-
width,
|
|
154
|
-
height
|
|
155
|
-
},
|
|
156
|
-
deviceScaleFactor: dpr,
|
|
157
|
-
locale: passport.locale?.language ?? passport.context?.locale ?? "en-US",
|
|
158
|
-
timezoneId: passport.locale?.timezone ?? passport.context?.timezoneId ?? "America/New_York",
|
|
159
|
-
geolocation: passport.context?.geolocation,
|
|
160
|
-
permissions: passport.context?.permissions ?? ["geolocation"],
|
|
161
|
-
extraHTTPHeaders: canonicalHeaders,
|
|
162
|
-
hasTouch: passport.context?.hasTouch ?? false,
|
|
163
|
-
isMobile: passport.context?.isMobile ?? false,
|
|
164
|
-
colorScheme: passport.context?.colorScheme ?? "light"
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function getStealthScript(passport) {
|
|
168
|
-
const { hardware, os, locale, browser, clientHints } = passport;
|
|
169
|
-
const concurrency = hardware?.cpu?.concurrency ?? 8;
|
|
170
|
-
const memory = hardware?.memory?.deviceMemory ?? 8;
|
|
171
|
-
const screenWidth = hardware?.screen?.width ?? 1920;
|
|
172
|
-
const screenHeight = hardware?.screen?.height ?? 1080;
|
|
173
|
-
const availWidth = hardware?.screen?.availWidth ?? screenWidth;
|
|
174
|
-
const availHeight = hardware?.screen?.availHeight ?? screenHeight - 40;
|
|
175
|
-
const colorDepth = hardware?.screen?.colorDepth ?? 24;
|
|
176
|
-
const pixelDepth = hardware?.screen?.pixelDepth ?? 24;
|
|
177
|
-
const platform = os?.platform === "macOS" ? "MacIntel" : "Win32";
|
|
178
|
-
const languages = JSON.stringify(locale?.languages ?? ["en-US", "en"]);
|
|
179
|
-
const gpuVendor = hardware?.gpu?.vendor ?? "Google Inc. (Apple)";
|
|
180
|
-
const gpuRenderer = hardware?.gpu?.renderer ?? "ANGLE (Apple, Apple M2, OpenGL 4.1)";
|
|
181
|
-
const uachData = JSON.stringify({
|
|
182
|
-
brands: clientHints?.brands ?? [
|
|
183
|
-
{ brand: "Chromium", version: String(browser?.majorVersion ?? 124) },
|
|
184
|
-
{ brand: "Google Chrome", version: String(browser?.majorVersion ?? 124) },
|
|
185
|
-
{ brand: "Not-A.Brand", version: "99" }
|
|
186
|
-
],
|
|
187
|
-
mobile: clientHints?.mobile ?? false,
|
|
188
|
-
platform: clientHints?.platform ?? (os?.platform ?? "macOS"),
|
|
189
|
-
architecture: clientHints?.architecture ?? (os?.architecture ?? "arm"),
|
|
190
|
-
bitness: clientHints?.bitness ?? "64",
|
|
191
|
-
model: clientHints?.model ?? "",
|
|
192
|
-
platformVersion: clientHints?.platformVersion ?? (os?.version ?? "14.6.1"),
|
|
193
|
-
fullVersionList: clientHints?.fullVersionList ?? [
|
|
194
|
-
{ brand: "Chromium", version: browser?.version ?? "124.0.6367.208" },
|
|
195
|
-
{ brand: "Google Chrome", version: browser?.version ?? "124.0.6367.208" },
|
|
196
|
-
{ brand: "Not-A.Brand", version: "99.0.0.0" }
|
|
197
|
-
]
|
|
198
|
-
});
|
|
199
|
-
return `
|
|
200
|
-
(() => {
|
|
201
|
-
// 1. Remove automation flags
|
|
202
|
-
try {
|
|
203
|
-
Object.defineProperty(navigator, 'webdriver', {
|
|
204
|
-
get: () => undefined,
|
|
205
|
-
configurable: true
|
|
206
|
-
});
|
|
207
|
-
delete (navigator as any).__proto__.webdriver;
|
|
208
|
-
} catch (e) {}
|
|
209
|
-
|
|
210
|
-
// 2. Hardware profile (CPU, Memory)
|
|
211
|
-
try {
|
|
212
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
|
213
|
-
get: () => ${concurrency},
|
|
214
|
-
configurable: true
|
|
215
|
-
});
|
|
216
|
-
Object.defineProperty(navigator, 'deviceMemory', {
|
|
217
|
-
get: () => ${memory},
|
|
218
|
-
configurable: true
|
|
219
|
-
});
|
|
220
|
-
} catch (e) {}
|
|
221
|
-
|
|
222
|
-
// 3. Platform & Languages
|
|
223
|
-
try {
|
|
224
|
-
Object.defineProperty(navigator, 'platform', {
|
|
225
|
-
get: () => '${platform}',
|
|
226
|
-
configurable: true
|
|
227
|
-
});
|
|
228
|
-
const langs = ${languages};
|
|
229
|
-
Object.defineProperty(navigator, 'languages', {
|
|
230
|
-
get: () => langs,
|
|
231
|
-
configurable: true
|
|
232
|
-
});
|
|
233
|
-
Object.defineProperty(navigator, 'language', {
|
|
234
|
-
get: () => langs[0],
|
|
235
|
-
configurable: true
|
|
236
|
-
});
|
|
237
|
-
} catch (e) {}
|
|
238
|
-
|
|
239
|
-
// 4. Screen properties
|
|
240
|
-
try {
|
|
241
|
-
Object.defineProperty(window.screen, 'width', { get: () => ${screenWidth}, configurable: true });
|
|
242
|
-
Object.defineProperty(window.screen, 'height', { get: () => ${screenHeight}, configurable: true });
|
|
243
|
-
Object.defineProperty(window.screen, 'availWidth', { get: () => ${availWidth}, configurable: true });
|
|
244
|
-
Object.defineProperty(window.screen, 'availHeight', { get: () => ${availHeight}, configurable: true });
|
|
245
|
-
Object.defineProperty(window.screen, 'colorDepth', { get: () => ${colorDepth}, configurable: true });
|
|
246
|
-
Object.defineProperty(window.screen, 'pixelDepth', { get: () => ${pixelDepth}, configurable: true });
|
|
247
|
-
} catch (e) {}
|
|
248
|
-
|
|
249
|
-
// 5. Client Hints (navigator.userAgentData)
|
|
250
|
-
try {
|
|
251
|
-
const hintData = ${uachData};
|
|
252
|
-
if (typeof (navigator as any).userAgentData !== 'undefined' || true) {
|
|
253
|
-
const uach = {
|
|
254
|
-
brands: hintData.brands,
|
|
255
|
-
mobile: hintData.mobile,
|
|
256
|
-
platform: hintData.platform,
|
|
257
|
-
getHighEntropyValues: async (hints) => {
|
|
258
|
-
const res = {
|
|
259
|
-
brands: hintData.brands,
|
|
260
|
-
mobile: hintData.mobile,
|
|
261
|
-
platform: hintData.platform,
|
|
262
|
-
};
|
|
263
|
-
if (hints.includes('architecture')) res.architecture = hintData.architecture;
|
|
264
|
-
if (hints.includes('bitness')) res.bitness = hintData.bitness;
|
|
265
|
-
if (hints.includes('model')) res.model = hintData.model;
|
|
266
|
-
if (hints.includes('platformVersion')) res.platformVersion = hintData.platformVersion;
|
|
267
|
-
if (hints.includes('fullVersionList')) res.fullVersionList = hintData.fullVersionList;
|
|
268
|
-
return res;
|
|
269
|
-
},
|
|
270
|
-
toJSON: () => ({
|
|
271
|
-
brands: hintData.brands,
|
|
272
|
-
mobile: hintData.mobile,
|
|
273
|
-
platform: hintData.platform,
|
|
274
|
-
})
|
|
275
|
-
};
|
|
276
|
-
Object.defineProperty(navigator, 'userAgentData', {
|
|
277
|
-
get: () => uach,
|
|
278
|
-
configurable: true
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
} catch (e) {}
|
|
282
|
-
|
|
283
|
-
// 6. WebGL Vendor & Renderer spoofing
|
|
284
|
-
try {
|
|
285
|
-
const getParameterProto = WebGLRenderingContext.prototype.getParameter;
|
|
286
|
-
const vendorParam = 0x9245; // UNMASKED_VENDOR_WEBGL
|
|
287
|
-
const rendererParam = 0x9246; // UNMASKED_RENDERER_WEBGL
|
|
288
|
-
|
|
289
|
-
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
290
|
-
if (parameter === vendorParam) return '${gpuVendor}';
|
|
291
|
-
if (parameter === rendererParam) return '${gpuRenderer}';
|
|
292
|
-
return getParameterProto.apply(this, arguments);
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
if (typeof WebGL2RenderingContext !== 'undefined') {
|
|
296
|
-
const getParameter2Proto = WebGL2RenderingContext.prototype.getParameter;
|
|
297
|
-
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
|
|
298
|
-
if (parameter === vendorParam) return '${gpuVendor}';
|
|
299
|
-
if (parameter === rendererParam) return '${gpuRenderer}';
|
|
300
|
-
return getParameter2Proto.apply(this, arguments);
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
} catch (e) {}
|
|
304
|
-
|
|
305
|
-
// 7. Chrome runtime mock for Chromium personas
|
|
306
|
-
try {
|
|
307
|
-
if (!window.chrome) {
|
|
308
|
-
(window as any).chrome = {
|
|
309
|
-
app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' } },
|
|
310
|
-
runtime: { OnInstalledReason: {}, OnRestartRequiredReason: {} },
|
|
311
|
-
loadTimes: () => ({}),
|
|
312
|
-
csi: () => ({}),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
} catch (e) {}
|
|
316
|
-
})();
|
|
317
|
-
`;
|
|
318
|
-
}
|
|
319
|
-
async function applyToPlaywrightContext(context, passport) {
|
|
320
|
-
if (!context || typeof context.addInitScript !== "function") {
|
|
321
|
-
throw new Error("[device-passport] Invalid Playwright context provided to applyToPlaywrightContext.");
|
|
322
|
-
}
|
|
323
|
-
const stealthScript = getStealthScript(passport);
|
|
324
|
-
await context.addInitScript(stealthScript);
|
|
325
|
-
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
326
|
-
if (typeof context.setExtraHTTPHeaders === "function") {
|
|
327
|
-
await context.setExtraHTTPHeaders(formatCanonicalHeaders(headers));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function getPuppeteerArgs(passport) {
|
|
332
|
-
const width = passport.hardware?.screen?.width ?? 1920;
|
|
333
|
-
const height = passport.hardware?.screen?.height ?? 1080;
|
|
334
|
-
const dpr = passport.hardware?.screen?.dpr ?? 1;
|
|
335
|
-
const locale = passport.locale?.language ?? "en-US";
|
|
336
|
-
const timezone = passport.locale?.timezone ?? "America/New_York";
|
|
337
|
-
const userAgent = passport.browser?.userAgent ?? passport.context?.userAgent;
|
|
338
|
-
const headers = passport.context?.extraHTTPHeaders ?? passport.headers ?? {};
|
|
339
|
-
const canonicalHeaders = formatCanonicalHeaders(headers);
|
|
340
|
-
const launchArgs = [
|
|
341
|
-
`--user-agent=${userAgent}`,
|
|
342
|
-
`--window-size=${width},${height}`,
|
|
343
|
-
`--lang=${locale}`,
|
|
344
|
-
"--disable-blink-features=AutomationControlled",
|
|
345
|
-
"--no-default-browser-check"
|
|
346
|
-
];
|
|
347
|
-
return {
|
|
348
|
-
launchArgs,
|
|
349
|
-
defaultViewport: {
|
|
350
|
-
width,
|
|
351
|
-
height,
|
|
352
|
-
deviceScaleFactor: dpr,
|
|
353
|
-
isMobile: passport.context?.isMobile ?? false,
|
|
354
|
-
hasTouch: passport.context?.hasTouch ?? false
|
|
355
|
-
},
|
|
356
|
-
userAgent,
|
|
357
|
-
locale,
|
|
358
|
-
timezoneId: timezone,
|
|
359
|
-
extraHTTPHeaders: canonicalHeaders
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
async function applyToPuppeteerPage(page, passport) {
|
|
363
|
-
if (!page || typeof page.evaluateOnNewDocument !== "function") {
|
|
364
|
-
throw new Error("[device-passport] Invalid Puppeteer page provided to applyToPuppeteerPage.");
|
|
365
|
-
}
|
|
366
|
-
const { defaultViewport, userAgent, extraHTTPHeaders, timezoneId } = getPuppeteerArgs(passport);
|
|
367
|
-
if (typeof page.setUserAgent === "function") {
|
|
368
|
-
await page.setUserAgent(userAgent);
|
|
369
|
-
}
|
|
370
|
-
if (typeof page.setViewport === "function") {
|
|
371
|
-
await page.setViewport(defaultViewport);
|
|
372
|
-
}
|
|
373
|
-
if (typeof page.setExtraHTTPHeaders === "function") {
|
|
374
|
-
await page.setExtraHTTPHeaders(extraHTTPHeaders);
|
|
375
|
-
}
|
|
376
|
-
if (typeof page.emulateTimezone === "function") {
|
|
377
|
-
try {
|
|
378
|
-
await page.emulateTimezone(timezoneId);
|
|
379
|
-
} catch {
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const stealthScript = getStealthScript(passport);
|
|
383
|
-
await page.evaluateOnNewDocument(stealthScript);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const COUNTRY_TIMEZONE_MAP = {
|
|
387
|
-
US: [
|
|
388
|
-
"America/New_York",
|
|
389
|
-
"America/Chicago",
|
|
390
|
-
"America/Denver",
|
|
391
|
-
"America/Los_Angeles",
|
|
392
|
-
"America/Anchorage",
|
|
393
|
-
"America/Phoenix",
|
|
394
|
-
"America/Detroit",
|
|
395
|
-
"America/Indiana/Indianapolis",
|
|
396
|
-
"Pacific/Honolulu"
|
|
397
|
-
],
|
|
398
|
-
CA: [
|
|
399
|
-
"America/Toronto",
|
|
400
|
-
"America/Vancouver",
|
|
401
|
-
"America/Montreal",
|
|
402
|
-
"America/Edmonton",
|
|
403
|
-
"America/Winnipeg",
|
|
404
|
-
"America/Halifax",
|
|
405
|
-
"America/St_Johns"
|
|
406
|
-
],
|
|
407
|
-
GB: ["Europe/London"],
|
|
408
|
-
UK: ["Europe/London"],
|
|
409
|
-
DE: ["Europe/Berlin"],
|
|
410
|
-
FR: ["Europe/Paris"],
|
|
411
|
-
JP: ["Asia/Tokyo"],
|
|
412
|
-
CN: ["Asia/Shanghai", "Asia/Chongqing", "Asia/Urumqi", "Asia/Harbin"],
|
|
413
|
-
AU: [
|
|
414
|
-
"Australia/Sydney",
|
|
415
|
-
"Australia/Melbourne",
|
|
416
|
-
"Australia/Brisbane",
|
|
417
|
-
"Australia/Perth",
|
|
418
|
-
"Australia/Adelaide"
|
|
419
|
-
],
|
|
420
|
-
SG: ["Asia/Singapore"],
|
|
421
|
-
HK: ["Asia/Hong_Kong"],
|
|
422
|
-
TW: ["Asia/Taipei"],
|
|
423
|
-
KR: ["Asia/Seoul"],
|
|
424
|
-
NL: ["Europe/Amsterdam"],
|
|
425
|
-
SE: ["Europe/Stockholm"],
|
|
426
|
-
CH: ["Europe/Zurich"],
|
|
427
|
-
BR: ["America/Sao_Paulo", "America/Manaus", "America/Fortaleza"],
|
|
428
|
-
IN: ["Asia/Kolkata"],
|
|
429
|
-
RU: ["Europe/Moscow", "Asia/Yekaterinburg", "Asia/Novosibirsk", "Asia/Vladivostok"]
|
|
430
|
-
};
|
|
431
|
-
const COUNTRY_PRIMARY_LANGUAGES = {
|
|
432
|
-
US: ["en", "en-US"],
|
|
433
|
-
CA: ["en", "en-CA", "fr", "fr-CA"],
|
|
434
|
-
GB: ["en", "en-GB"],
|
|
435
|
-
UK: ["en", "en-GB"],
|
|
436
|
-
DE: ["de", "de-DE"],
|
|
437
|
-
FR: ["fr", "fr-FR"],
|
|
438
|
-
JP: ["ja", "ja-JP"],
|
|
439
|
-
CN: ["zh", "zh-CN", "zh-Hans"],
|
|
440
|
-
TW: ["zh", "zh-TW", "zh-Hant"],
|
|
441
|
-
HK: ["zh", "zh-HK", "en"],
|
|
442
|
-
KR: ["ko", "ko-KR"],
|
|
443
|
-
SG: ["en", "en-SG", "zh", "zh-SG"],
|
|
444
|
-
AU: ["en", "en-AU"],
|
|
445
|
-
NL: ["nl", "nl-NL", "en"],
|
|
446
|
-
SE: ["sv", "sv-SE", "en"],
|
|
447
|
-
CH: ["de", "de-CH", "fr", "fr-CH", "it", "it-CH"],
|
|
448
|
-
BR: ["pt", "pt-BR"],
|
|
449
|
-
IN: ["en", "en-IN", "hi", "hi-IN"],
|
|
450
|
-
RU: ["ru", "ru-RU"]
|
|
451
|
-
};
|
|
452
|
-
function detectOSBrowserContradictions(passport) {
|
|
453
|
-
const contradictions = [];
|
|
454
|
-
const { browser, os, headers, clientHints } = passport;
|
|
455
|
-
if (browser.name === "Safari" && os.platform !== "macOS") {
|
|
456
|
-
contradictions.push({
|
|
457
|
-
id: "ERR_SAFARI_NON_APPLE_PLATFORM",
|
|
458
|
-
category: "os_browser",
|
|
459
|
-
severity: "fatal",
|
|
460
|
-
description: `Safari modern releases are exclusive to macOS/iOS; cannot run on ${os.platform}`,
|
|
461
|
-
fields: ["browser.name", "os.platform"],
|
|
462
|
-
actual: { browser: browser.name, platform: os.platform },
|
|
463
|
-
expected: "macOS",
|
|
464
|
-
impactPenalty: 35
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
if (browser.name === "Chrome" || browser.name === "Edge") {
|
|
468
|
-
if (browser.majorVersion >= 110 && os.release) {
|
|
469
|
-
const isLegacyWin = /Windows (7|8|XP|Vista)/i.test(os.release);
|
|
470
|
-
if (isLegacyWin) {
|
|
471
|
-
contradictions.push({
|
|
472
|
-
id: "ERR_CHROME_LEGACY_WIN_UNSUPPORTED",
|
|
473
|
-
category: "os_browser",
|
|
474
|
-
severity: "fatal",
|
|
475
|
-
description: `Chromium ${browser.majorVersion} requires Windows 10 or later; unsupported on ${os.release}`,
|
|
476
|
-
fields: ["browser.majorVersion", "os.release"],
|
|
477
|
-
actual: { browserVersion: browser.majorVersion, osRelease: os.release },
|
|
478
|
-
expected: "Windows 10 or Windows 11",
|
|
479
|
-
impactPenalty: 30
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (browser.majorVersion >= 120 && os.platform === "macOS") {
|
|
484
|
-
const match = os.version.match(/^(\d+)/);
|
|
485
|
-
if (match && parseInt(match[1], 10) < 11) {
|
|
486
|
-
contradictions.push({
|
|
487
|
-
id: "ERR_CHROME_LEGACY_MACOS_UNSUPPORTED",
|
|
488
|
-
category: "os_browser",
|
|
489
|
-
severity: "high",
|
|
490
|
-
description: `Chromium ${browser.majorVersion} requires macOS 11.0 (Big Sur) or higher; got macOS ${os.version}`,
|
|
491
|
-
fields: ["browser.majorVersion", "os.version"],
|
|
492
|
-
actual: os.version,
|
|
493
|
-
expected: ">= 11.0.0",
|
|
494
|
-
impactPenalty: 25
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
const ua = browser.userAgent || headers["user-agent"] || "";
|
|
500
|
-
if (os.platform === "macOS" && !/Macintosh|Mac OS X/i.test(ua)) {
|
|
501
|
-
contradictions.push({
|
|
502
|
-
id: "ERR_UA_OS_MACOS_MISMATCH",
|
|
503
|
-
category: "os_browser",
|
|
504
|
-
severity: "fatal",
|
|
505
|
-
description: "Operating system is macOS but User-Agent string does not indicate Macintosh architecture",
|
|
506
|
-
fields: ["os.platform", "browser.userAgent"],
|
|
507
|
-
actual: ua,
|
|
508
|
-
expected: 'Containing "Macintosh; Intel Mac OS X..."',
|
|
509
|
-
impactPenalty: 35
|
|
510
|
-
});
|
|
511
|
-
} else if (os.platform === "Windows" && !/Windows NT/i.test(ua)) {
|
|
512
|
-
contradictions.push({
|
|
513
|
-
id: "ERR_UA_OS_WINDOWS_MISMATCH",
|
|
514
|
-
category: "os_browser",
|
|
515
|
-
severity: "fatal",
|
|
516
|
-
description: "Operating system is Windows but User-Agent string does not contain Windows NT tag",
|
|
517
|
-
fields: ["os.platform", "browser.userAgent"],
|
|
518
|
-
actual: ua,
|
|
519
|
-
expected: 'Containing "Windows NT..."',
|
|
520
|
-
impactPenalty: 35
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
if (clientHints?.platformVersion && os.platform === "Windows") {
|
|
524
|
-
const pvMajor = parseInt(clientHints.platformVersion.split(".")[0], 10);
|
|
525
|
-
const isWin11 = /Windows 11/i.test(os.release);
|
|
526
|
-
const isWin10 = /Windows 10/i.test(os.release);
|
|
527
|
-
if (isWin11 && pvMajor < 13) {
|
|
528
|
-
contradictions.push({
|
|
529
|
-
id: "ERR_WIN11_PLATFORM_VERSION_MISMATCH",
|
|
530
|
-
category: "os_browser",
|
|
531
|
-
severity: "high",
|
|
532
|
-
description: `Windows 11 reports Client-Hints platformVersion >= 13.0.0 (e.g. 15.0.0), but received "${clientHints.platformVersion}"`,
|
|
533
|
-
fields: ["os.release", "clientHints.platformVersion"],
|
|
534
|
-
actual: clientHints.platformVersion,
|
|
535
|
-
expected: ">= 13.0.0 (typically 14.0.0 or 15.0.0)",
|
|
536
|
-
impactPenalty: 20
|
|
537
|
-
});
|
|
538
|
-
} else if (isWin10 && pvMajor >= 13) {
|
|
539
|
-
contradictions.push({
|
|
540
|
-
id: "ERR_WIN10_PLATFORM_VERSION_MISMATCH",
|
|
541
|
-
category: "os_browser",
|
|
542
|
-
severity: "high",
|
|
543
|
-
description: `Windows 10 reports Client-Hints platformVersion <= 10.0.0, but received Windows 11 platformVersion "${clientHints.platformVersion}"`,
|
|
544
|
-
fields: ["os.release", "clientHints.platformVersion"],
|
|
545
|
-
actual: clientHints.platformVersion,
|
|
546
|
-
expected: "10.0.0 (or 1.0.0 - 10.0.0)",
|
|
547
|
-
impactPenalty: 20
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return contradictions;
|
|
552
|
-
}
|
|
553
|
-
function detectHardwareContradictions(passport) {
|
|
554
|
-
const contradictions = [];
|
|
555
|
-
const { hardware, os, clientHints } = passport;
|
|
556
|
-
if (os.platform === "macOS") {
|
|
557
|
-
const isAppleSiliconGpu = /Apple M\d/i.test(hardware.gpu.renderer);
|
|
558
|
-
if (isAppleSiliconGpu && hardware.cpu.architecture === "x86" && clientHints?.architecture === "x86") {
|
|
559
|
-
contradictions.push({
|
|
560
|
-
id: "ERR_APPLE_SILICON_X86_CONTRADICTION",
|
|
561
|
-
category: "hardware_system",
|
|
562
|
-
severity: "high",
|
|
563
|
-
description: `Apple Silicon GPU detected (${hardware.gpu.renderer}) but native CPU architecture and Client Hint reported as x86`,
|
|
564
|
-
fields: ["hardware.gpu.renderer", "hardware.cpu.architecture", "clientHints.architecture"],
|
|
565
|
-
actual: { gpu: hardware.gpu.renderer, arch: hardware.cpu.architecture },
|
|
566
|
-
expected: "arm",
|
|
567
|
-
impactPenalty: 20
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const gpuRenderer = hardware.gpu.renderer || "";
|
|
572
|
-
if (os.platform === "macOS") {
|
|
573
|
-
if (/GeForce RTX|GTX 16|RTX 30|RTX 40/i.test(gpuRenderer)) {
|
|
574
|
-
contradictions.push({
|
|
575
|
-
id: "ERR_NVIDIA_MODERN_ON_MACOS",
|
|
576
|
-
category: "hardware_system",
|
|
577
|
-
severity: "fatal",
|
|
578
|
-
description: `NVIDIA modern GPUs (${gpuRenderer}) have no driver support on modern macOS`,
|
|
579
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
580
|
-
actual: gpuRenderer,
|
|
581
|
-
expected: "Apple M-Series GPU or AMD Radeon Pro (legacy Intel Mac)",
|
|
582
|
-
impactPenalty: 35
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
if (/Direct3D|D3D11|D3D12/i.test(gpuRenderer)) {
|
|
586
|
-
contradictions.push({
|
|
587
|
-
id: "ERR_DIRECT3D_ON_MACOS",
|
|
588
|
-
category: "hardware_system",
|
|
589
|
-
severity: "fatal",
|
|
590
|
-
description: `Direct3D/DirectX renderer (${gpuRenderer}) cannot exist on macOS WebGL pipeline`,
|
|
591
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
592
|
-
actual: gpuRenderer,
|
|
593
|
-
expected: "Metal or OpenGL ANGLE backend",
|
|
594
|
-
impactPenalty: 30
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
} else if (os.platform === "Windows") {
|
|
598
|
-
if (/Apple M\d/i.test(gpuRenderer)) {
|
|
599
|
-
contradictions.push({
|
|
600
|
-
id: "ERR_APPLE_GPU_ON_WINDOWS",
|
|
601
|
-
category: "hardware_system",
|
|
602
|
-
severity: "fatal",
|
|
603
|
-
description: `Apple Silicon integrated graphics (${gpuRenderer}) cannot run under Microsoft Windows`,
|
|
604
|
-
fields: ["hardware.gpu.renderer", "os.platform"],
|
|
605
|
-
actual: gpuRenderer,
|
|
606
|
-
expected: "Intel UHD/Iris, NVIDIA GeForce, or AMD Radeon",
|
|
607
|
-
impactPenalty: 35
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
if (clientHints?.architecture && hardware.cpu.architecture) {
|
|
612
|
-
if (clientHints.architecture !== hardware.cpu.architecture) {
|
|
613
|
-
contradictions.push({
|
|
614
|
-
id: "ERR_CH_ARCH_HARDWARE_MISMATCH",
|
|
615
|
-
category: "hardware_system",
|
|
616
|
-
severity: "fatal",
|
|
617
|
-
description: `sec-ch-ua-arch ("${clientHints.architecture}") does not match CPU hardware concurrency profile ("${hardware.cpu.architecture}")`,
|
|
618
|
-
fields: ["clientHints.architecture", "hardware.cpu.architecture"],
|
|
619
|
-
actual: clientHints.architecture,
|
|
620
|
-
expected: hardware.cpu.architecture,
|
|
621
|
-
impactPenalty: 30
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
const dm = hardware.memory.deviceMemory;
|
|
626
|
-
if (dm !== void 0) {
|
|
627
|
-
const validDeviceMemoryValues = [0.25, 0.5, 1, 2, 4, 8];
|
|
628
|
-
if (dm <= 0 || !validDeviceMemoryValues.includes(dm)) {
|
|
629
|
-
if (dm > 8) {
|
|
630
|
-
contradictions.push({
|
|
631
|
-
id: "WARN_DEVICE_MEMORY_UNCAPPED",
|
|
632
|
-
category: "hardware_system",
|
|
633
|
-
severity: "medium",
|
|
634
|
-
description: `navigator.deviceMemory reported as ${dm} GiB; standard Chromium caps deviceMemory at 8 GiB`,
|
|
635
|
-
fields: ["hardware.memory.deviceMemory"],
|
|
636
|
-
actual: dm,
|
|
637
|
-
expected: "Capped at 8 (one of [0.25, 0.5, 1, 2, 4, 8])",
|
|
638
|
-
impactPenalty: 10
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
const { screen } = hardware;
|
|
644
|
-
if (screen.width > 0 && screen.height > 0) {
|
|
645
|
-
if (screen.availWidth > screen.width || screen.availHeight > screen.height) {
|
|
646
|
-
contradictions.push({
|
|
647
|
-
id: "ERR_SCREEN_AVAILABLE_EXCEEDS_TOTAL",
|
|
648
|
-
category: "hardware_system",
|
|
649
|
-
severity: "high",
|
|
650
|
-
description: `Screen available dimensions (${screen.availWidth}x${screen.availHeight}) exceed screen total bounds (${screen.width}x${screen.height})`,
|
|
651
|
-
fields: ["hardware.screen.availWidth", "hardware.screen.width"],
|
|
652
|
-
actual: { avail: `${screen.availWidth}x${screen.availHeight}`, total: `${screen.width}x${screen.height}` },
|
|
653
|
-
expected: "availWidth <= width && availHeight <= height",
|
|
654
|
-
impactPenalty: 20
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
if (passport.context?.viewport) {
|
|
658
|
-
const vp = passport.context.viewport;
|
|
659
|
-
if (vp.width > screen.width || vp.height > screen.height) {
|
|
660
|
-
contradictions.push({
|
|
661
|
-
id: "ERR_VIEWPORT_EXCEEDS_SCREEN",
|
|
662
|
-
category: "hardware_system",
|
|
663
|
-
severity: "fatal",
|
|
664
|
-
description: `Browser viewport (${vp.width}x${vp.height}) exceeds physical display screen (${screen.width}x${screen.height})`,
|
|
665
|
-
fields: ["context.viewport", "hardware.screen"],
|
|
666
|
-
actual: `${vp.width}x${vp.height}`,
|
|
667
|
-
expected: `<= ${screen.width}x${screen.height}`,
|
|
668
|
-
impactPenalty: 30
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return contradictions;
|
|
674
|
-
}
|
|
675
|
-
function detectGeoChronoContradictions(passport) {
|
|
676
|
-
const contradictions = [];
|
|
677
|
-
const { locale, headers } = passport;
|
|
678
|
-
const country = (locale.country || "").toUpperCase();
|
|
679
|
-
const timezone = locale.timezone || "";
|
|
680
|
-
if (country && timezone && COUNTRY_TIMEZONE_MAP[country]) {
|
|
681
|
-
const validTimezones = COUNTRY_TIMEZONE_MAP[country];
|
|
682
|
-
if (!validTimezones.includes(timezone)) {
|
|
683
|
-
contradictions.push({
|
|
684
|
-
id: "ERR_GEO_TIMEZONE_COUNTRY_MISMATCH",
|
|
685
|
-
category: "geo_chrono",
|
|
686
|
-
severity: "fatal",
|
|
687
|
-
description: `Timezone "${timezone}" does not belong to geographic territory "${country}"`,
|
|
688
|
-
fields: ["locale.timezone", "locale.country"],
|
|
689
|
-
actual: { country, timezone },
|
|
690
|
-
expected: validTimezones.join(", "),
|
|
691
|
-
impactPenalty: 40
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
const acceptLangHeader = headers["accept-language"] || "";
|
|
696
|
-
if (country && COUNTRY_PRIMARY_LANGUAGES[country]) {
|
|
697
|
-
const expectedLanguages = COUNTRY_PRIMARY_LANGUAGES[country];
|
|
698
|
-
const primaryLocaleLang = (locale.language || "").toLowerCase().split("-")[0];
|
|
699
|
-
const countryExpectsMatch = expectedLanguages.some(
|
|
700
|
-
(el) => primaryLocaleLang === el.toLowerCase().split("-")[0]
|
|
701
|
-
);
|
|
702
|
-
if (!countryExpectsMatch && primaryLocaleLang) {
|
|
703
|
-
contradictions.push({
|
|
704
|
-
id: "WARN_GEO_PRIMARY_LANGUAGE_MISMATCH",
|
|
705
|
-
category: "geo_chrono",
|
|
706
|
-
severity: "high",
|
|
707
|
-
description: `Primary locale language "${locale.language}" is anomalous for country code "${country}"`,
|
|
708
|
-
fields: ["locale.language", "locale.country"],
|
|
709
|
-
actual: locale.language,
|
|
710
|
-
expected: expectedLanguages.join(", "),
|
|
711
|
-
impactPenalty: 15
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
if (acceptLangHeader) {
|
|
715
|
-
const hasAnyExpectedInHeader = expectedLanguages.some(
|
|
716
|
-
(lang) => acceptLangHeader.toLowerCase().includes(lang.toLowerCase())
|
|
717
|
-
);
|
|
718
|
-
if (!hasAnyExpectedInHeader && !acceptLangHeader.toLowerCase().includes("en")) {
|
|
719
|
-
contradictions.push({
|
|
720
|
-
id: "WARN_ACCEPT_LANGUAGE_GEO_DETACHED",
|
|
721
|
-
category: "geo_chrono",
|
|
722
|
-
severity: "medium",
|
|
723
|
-
description: `Accept-Language header "${acceptLangHeader}" contains neither local language for "${country}" nor global fallback (en)`,
|
|
724
|
-
fields: ["headers.accept-language", "locale.country"],
|
|
725
|
-
actual: acceptLangHeader,
|
|
726
|
-
expected: `Includes one of: ${expectedLanguages.join(", ")} or en`,
|
|
727
|
-
impactPenalty: 12
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
return contradictions;
|
|
733
|
-
}
|
|
734
|
-
function detectClientHintsContradictions(passport) {
|
|
735
|
-
const contradictions = [];
|
|
736
|
-
const { clientHints, os } = passport;
|
|
737
|
-
if (!clientHints) return contradictions;
|
|
738
|
-
if (clientHints.mobile === true && (os.platform === "macOS" || os.platform === "Windows")) {
|
|
739
|
-
contradictions.push({
|
|
740
|
-
id: "ERR_CH_MOBILE_ON_DESKTOP",
|
|
741
|
-
category: "client_hints",
|
|
742
|
-
severity: "fatal",
|
|
743
|
-
description: `Client-Hints indicates mobile=true (?1) on desktop operating system ${os.platform}`,
|
|
744
|
-
fields: ["clientHints.mobile", "os.platform"],
|
|
745
|
-
actual: { mobile: clientHints.mobile, platform: os.platform },
|
|
746
|
-
expected: "mobile: false (?0)",
|
|
747
|
-
impactPenalty: 35
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
if (clientHints.platform) {
|
|
751
|
-
const chPlat = clientHints.platform.toLowerCase();
|
|
752
|
-
const osPlat = os.platform.toLowerCase();
|
|
753
|
-
if (chPlat !== osPlat) {
|
|
754
|
-
contradictions.push({
|
|
755
|
-
id: "ERR_CH_PLATFORM_OS_MISMATCH",
|
|
756
|
-
category: "client_hints",
|
|
757
|
-
severity: "fatal",
|
|
758
|
-
description: `sec-ch-ua-platform "${clientHints.platform}" contradicts OS platform "${os.platform}"`,
|
|
759
|
-
fields: ["clientHints.platform", "os.platform"],
|
|
760
|
-
actual: clientHints.platform,
|
|
761
|
-
expected: os.platform,
|
|
762
|
-
impactPenalty: 35
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
if ((os.platform === "macOS" || os.platform === "Windows") && clientHints.model && clientHints.model.trim() !== "") {
|
|
767
|
-
contradictions.push({
|
|
768
|
-
id: "ERR_CH_NON_EMPTY_DESKTOP_MODEL",
|
|
769
|
-
category: "client_hints",
|
|
770
|
-
severity: "high",
|
|
771
|
-
description: `Desktop Chromium mandates empty sec-ch-ua-model (""), received "${clientHints.model}"`,
|
|
772
|
-
fields: ["clientHints.model", "os.platform"],
|
|
773
|
-
actual: clientHints.model,
|
|
774
|
-
expected: '"" (empty string for desktop)',
|
|
775
|
-
impactPenalty: 25
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
if (clientHints.bitness && clientHints.bitness !== "64") {
|
|
779
|
-
if (os.platform === "macOS") {
|
|
780
|
-
contradictions.push({
|
|
781
|
-
id: "ERR_CH_32BIT_ON_MODERN_MACOS",
|
|
782
|
-
category: "client_hints",
|
|
783
|
-
severity: "fatal",
|
|
784
|
-
description: `macOS has discontinued 32-bit binaries since macOS 10.15; bitness reported as "${clientHints.bitness}"`,
|
|
785
|
-
fields: ["clientHints.bitness", "os.platform"],
|
|
786
|
-
actual: clientHints.bitness,
|
|
787
|
-
expected: '"64"',
|
|
788
|
-
impactPenalty: 30
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
return contradictions;
|
|
793
|
-
}
|
|
794
|
-
function detectAllContradictions(passport) {
|
|
795
|
-
return [
|
|
796
|
-
...detectOSBrowserContradictions(passport),
|
|
797
|
-
...detectHardwareContradictions(passport),
|
|
798
|
-
...detectGeoChronoContradictions(passport),
|
|
799
|
-
...detectClientHintsContradictions(passport)
|
|
800
|
-
];
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
const BROWSER_BASELINE_FREQUENCIES = {
|
|
804
|
-
"sec-ch-ua": 0.12,
|
|
805
|
-
"sec-ch-ua-mobile": 0.04,
|
|
806
|
-
"sec-ch-ua-platform": 0.08,
|
|
807
|
-
"upgrade-insecure-requests": 0.04,
|
|
808
|
-
"user-agent": 0.22,
|
|
809
|
-
"accept": 0.14,
|
|
810
|
-
"sec-fetch-site": 0.05,
|
|
811
|
-
"sec-fetch-mode": 0.05,
|
|
812
|
-
"sec-fetch-user": 0.03,
|
|
813
|
-
"sec-fetch-dest": 0.05,
|
|
814
|
-
"accept-encoding": 0.08,
|
|
815
|
-
"accept-language": 0.1
|
|
816
|
-
};
|
|
817
|
-
const OPTIMAL_CHAR_ENTROPY_MIN = 4.6;
|
|
818
|
-
const OPTIMAL_CHAR_ENTROPY_MAX = 5.6;
|
|
819
|
-
const OPTIMAL_ENTROPY_MIN = OPTIMAL_CHAR_ENTROPY_MIN;
|
|
820
|
-
const OPTIMAL_ENTROPY_MAX = OPTIMAL_CHAR_ENTROPY_MAX;
|
|
821
|
-
const BASELINE_OPTIMAL_ENTROPY = 5.25;
|
|
822
|
-
const MIN_STRUCTURAL_ENTROPY = 3.5;
|
|
823
|
-
function calculateShannonEntropy(data) {
|
|
824
|
-
if (!data || data.length === 0) return 0;
|
|
825
|
-
const frequencies = /* @__PURE__ */ new Map();
|
|
826
|
-
const total = data.length;
|
|
827
|
-
for (let i = 0; i < total; i++) {
|
|
828
|
-
const char = data[i];
|
|
829
|
-
frequencies.set(char, (frequencies.get(char) || 0) + 1);
|
|
830
|
-
}
|
|
831
|
-
let entropy = 0;
|
|
832
|
-
for (const count of frequencies.values()) {
|
|
833
|
-
const p = count / total;
|
|
834
|
-
if (p > 0) {
|
|
835
|
-
entropy -= p * Math.log2(p);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
return Number(entropy.toFixed(4));
|
|
839
|
-
}
|
|
840
|
-
function calculateStructuralEntropy(headers) {
|
|
841
|
-
const normalizedKeys = Object.keys(headers).map((k) => k.toLowerCase());
|
|
842
|
-
const baselineKeys = Object.keys(BROWSER_BASELINE_FREQUENCIES);
|
|
843
|
-
if (normalizedKeys.length === 0) return 0;
|
|
844
|
-
let presentCount = 0;
|
|
845
|
-
for (const key of baselineKeys) {
|
|
846
|
-
if (normalizedKeys.includes(key)) {
|
|
847
|
-
presentCount++;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
const presenceRatio = presentCount / baselineKeys.length;
|
|
851
|
-
const countFactor = Math.min(normalizedKeys.length / 10, 1);
|
|
852
|
-
return Number((presenceRatio * 3 + countFactor * 2).toFixed(4));
|
|
853
|
-
}
|
|
854
|
-
function calculateKLDivergence(headers) {
|
|
855
|
-
const normalizedHeaders = {};
|
|
856
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
857
|
-
normalizedHeaders[k.toLowerCase()] = v;
|
|
858
|
-
}
|
|
859
|
-
const baselineKeys = Object.keys(BROWSER_BASELINE_FREQUENCIES);
|
|
860
|
-
let totalLength = 0;
|
|
861
|
-
const observedLengths = {};
|
|
862
|
-
for (const key of baselineKeys) {
|
|
863
|
-
const val = normalizedHeaders[key] || "";
|
|
864
|
-
const len = val.length + key.length;
|
|
865
|
-
observedLengths[key] = len;
|
|
866
|
-
totalLength += len;
|
|
867
|
-
}
|
|
868
|
-
if (totalLength === 0) return 3.5;
|
|
869
|
-
let klDiv = 0;
|
|
870
|
-
const epsilon = 1e-4;
|
|
871
|
-
for (const key of baselineKeys) {
|
|
872
|
-
const p = (observedLengths[key] || 0) / totalLength + epsilon;
|
|
873
|
-
const q = BROWSER_BASELINE_FREQUENCIES[key] || epsilon;
|
|
874
|
-
klDiv += p * Math.log2(p / q);
|
|
875
|
-
}
|
|
876
|
-
return Number(Math.max(0, klDiv).toFixed(4));
|
|
877
|
-
}
|
|
878
|
-
function analyzeHeaderEntropy(headers) {
|
|
879
|
-
if (!headers || Object.keys(headers).length === 0) {
|
|
880
|
-
return {
|
|
881
|
-
entropyScore: 0,
|
|
882
|
-
estimatedBits: 0,
|
|
883
|
-
klDivergence: 4,
|
|
884
|
-
anomaly: "too_low",
|
|
885
|
-
collisionRisk: "high",
|
|
886
|
-
verdict: "Completely static or empty headers; trivially detectable as automated bot.",
|
|
887
|
-
details: {
|
|
888
|
-
characterEntropy: 0,
|
|
889
|
-
structuralEntropy: 0,
|
|
890
|
-
baselineEntropy: BASELINE_OPTIMAL_ENTROPY,
|
|
891
|
-
deltaFromOptimal: BASELINE_OPTIMAL_ENTROPY
|
|
892
|
-
}
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
const serialized = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k.toLowerCase()}: ${v}`).join("\r\n");
|
|
896
|
-
const charEntropy = calculateShannonEntropy(serialized);
|
|
897
|
-
const structEntropy = calculateStructuralEntropy(headers);
|
|
898
|
-
const klDiv = calculateKLDivergence(headers);
|
|
899
|
-
const headerCount = Object.keys(headers).length;
|
|
900
|
-
let anomaly = "optimal";
|
|
901
|
-
let penalty = 0;
|
|
902
|
-
if (headerCount < 4 || structEntropy < MIN_STRUCTURAL_ENTROPY || charEntropy < OPTIMAL_CHAR_ENTROPY_MIN) {
|
|
903
|
-
anomaly = "too_low";
|
|
904
|
-
const structDeficit = Math.max(0, MIN_STRUCTURAL_ENTROPY - structEntropy);
|
|
905
|
-
penalty += structDeficit * 15;
|
|
906
|
-
const charDeficit = Math.max(0, OPTIMAL_CHAR_ENTROPY_MIN - charEntropy);
|
|
907
|
-
penalty += charDeficit * 25;
|
|
908
|
-
if (headerCount < 4) {
|
|
909
|
-
penalty += (4 - headerCount) * 15;
|
|
910
|
-
}
|
|
911
|
-
} else if (charEntropy > OPTIMAL_CHAR_ENTROPY_MAX) {
|
|
912
|
-
anomaly = "too_high";
|
|
913
|
-
const excess = charEntropy - OPTIMAL_CHAR_ENTROPY_MAX;
|
|
914
|
-
penalty += excess * 30;
|
|
915
|
-
}
|
|
916
|
-
penalty += Math.min(klDiv * 12, 30);
|
|
917
|
-
const rawScore = 100 - penalty;
|
|
918
|
-
const entropyScore = Number(Math.max(0, Math.min(100, rawScore)).toFixed(1));
|
|
919
|
-
let collisionRisk = "very_low";
|
|
920
|
-
let verdict = "Realistic entropy distribution matching authentic browser telemetry.";
|
|
921
|
-
if (anomaly === "too_low") {
|
|
922
|
-
collisionRisk = "high";
|
|
923
|
-
verdict = "Under-randomized or missing essential browser headers; high risk of bot detection.";
|
|
924
|
-
} else if (anomaly === "too_high") {
|
|
925
|
-
collisionRisk = "moderate";
|
|
926
|
-
verdict = "Hyper-randomized noise detected; synthetic fingerprint spoofing risk.";
|
|
927
|
-
} else if (entropyScore >= 85) {
|
|
928
|
-
collisionRisk = "very_low";
|
|
929
|
-
verdict = "Optimal information entropy perfectly consistent with real Chromium/Safari sessions.";
|
|
930
|
-
} else {
|
|
931
|
-
collisionRisk = "low";
|
|
932
|
-
verdict = "Acceptable entropy band with minor structural divergence.";
|
|
933
|
-
}
|
|
934
|
-
return {
|
|
935
|
-
entropyScore,
|
|
936
|
-
estimatedBits: charEntropy,
|
|
937
|
-
klDivergence: klDiv,
|
|
938
|
-
anomaly,
|
|
939
|
-
collisionRisk,
|
|
940
|
-
verdict,
|
|
941
|
-
details: {
|
|
942
|
-
characterEntropy: charEntropy,
|
|
943
|
-
structuralEntropy: structEntropy,
|
|
944
|
-
baselineEntropy: BASELINE_OPTIMAL_ENTROPY,
|
|
945
|
-
deltaFromOptimal: Number(Math.abs(charEntropy - BASELINE_OPTIMAL_ENTROPY).toFixed(4))
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
const GREASE_BRANDS = [
|
|
951
|
-
"Not/A)Brand",
|
|
952
|
-
"Not_A Brand",
|
|
953
|
-
"Not?A_Brand",
|
|
954
|
-
"Not.A/Brand",
|
|
955
|
-
"Not:A-Brand"
|
|
956
|
-
];
|
|
957
|
-
function generateBrandVersions(browserName, majorVersion, fullVersion, prng = Math.random) {
|
|
958
|
-
const greaseIdx = Math.floor(prng() * GREASE_BRANDS.length);
|
|
959
|
-
const greaseBrand = GREASE_BRANDS[greaseIdx] ?? "Not/A)Brand";
|
|
960
|
-
const greaseMajor = "8";
|
|
961
|
-
const greaseFull = "8.0.0.0";
|
|
962
|
-
const lowEntropyBrands = [];
|
|
963
|
-
const fullVersionList = [];
|
|
964
|
-
if (browserName === "Edge") {
|
|
965
|
-
const brands = [
|
|
966
|
-
{ brand: greaseBrand, major: greaseMajor, full: greaseFull },
|
|
967
|
-
{ brand: "Chromium", major: String(majorVersion), full: fullVersion },
|
|
968
|
-
{ brand: "Microsoft Edge", major: String(majorVersion), full: fullVersion }
|
|
969
|
-
];
|
|
970
|
-
shuffleArray(brands, prng);
|
|
971
|
-
for (const b of brands) {
|
|
972
|
-
lowEntropyBrands.push({ brand: b.brand, version: b.major });
|
|
973
|
-
fullVersionList.push({ brand: b.brand, version: b.full });
|
|
974
|
-
}
|
|
975
|
-
} else {
|
|
976
|
-
const brands = [
|
|
977
|
-
{ brand: greaseBrand, major: greaseMajor, full: greaseFull },
|
|
978
|
-
{ brand: "Chromium", major: String(majorVersion), full: fullVersion },
|
|
979
|
-
{ brand: "Google Chrome", major: String(majorVersion), full: fullVersion }
|
|
980
|
-
];
|
|
981
|
-
shuffleArray(brands, prng);
|
|
982
|
-
for (const b of brands) {
|
|
983
|
-
lowEntropyBrands.push({ brand: b.brand, version: b.major });
|
|
984
|
-
fullVersionList.push({ brand: b.brand, version: b.full });
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return { lowEntropyBrands, fullVersionList };
|
|
988
|
-
}
|
|
989
|
-
function formatStructuredBrandList(brands) {
|
|
990
|
-
return brands.map((b) => `"${b.brand}";v="${b.version}"`).join(", ");
|
|
991
|
-
}
|
|
992
|
-
function parseStructuredBrandList(headerValue) {
|
|
993
|
-
if (!headerValue) return [];
|
|
994
|
-
const brands = [];
|
|
995
|
-
const regex = /"([^"]+)";v="([^"]+)"/g;
|
|
996
|
-
let match;
|
|
997
|
-
while ((match = regex.exec(headerValue)) !== null) {
|
|
998
|
-
if (match[1] && match[2]) {
|
|
999
|
-
brands.push({ brand: match[1], version: match[2] });
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
return brands;
|
|
1003
|
-
}
|
|
1004
|
-
function generateUserAgentClientHints(options) {
|
|
1005
|
-
const { browserName, fullVersion, majorVersion, os, prng = Math.random } = options;
|
|
1006
|
-
const { lowEntropyBrands, fullVersionList } = generateBrandVersions(
|
|
1007
|
-
browserName,
|
|
1008
|
-
majorVersion,
|
|
1009
|
-
fullVersion,
|
|
1010
|
-
prng
|
|
1011
|
-
);
|
|
1012
|
-
const isWindows = os.platform === "Windows";
|
|
1013
|
-
const architecture = os.architecture;
|
|
1014
|
-
const bitness = "64";
|
|
1015
|
-
const model = "";
|
|
1016
|
-
const mobile = false;
|
|
1017
|
-
const secChUa = formatStructuredBrandList(lowEntropyBrands);
|
|
1018
|
-
const secChUaFullVersionList = formatStructuredBrandList(fullVersionList);
|
|
1019
|
-
const secChUaMobile = "?0";
|
|
1020
|
-
const secChUaPlatform = `"${os.platform}"`;
|
|
1021
|
-
const secChUaPlatformVersion = `"${os.version}"`;
|
|
1022
|
-
const secChUaArch = `"${architecture}"`;
|
|
1023
|
-
const secChUaBitness = `"${bitness}"`;
|
|
1024
|
-
const secChUaModel = `"${model}"`;
|
|
1025
|
-
const headers = {
|
|
1026
|
-
"sec-ch-ua": secChUa,
|
|
1027
|
-
"sec-ch-ua-mobile": secChUaMobile,
|
|
1028
|
-
"sec-ch-ua-platform": secChUaPlatform,
|
|
1029
|
-
"sec-ch-ua-platform-version": secChUaPlatformVersion,
|
|
1030
|
-
"sec-ch-ua-arch": secChUaArch,
|
|
1031
|
-
"sec-ch-ua-bitness": secChUaBitness,
|
|
1032
|
-
"sec-ch-ua-model": secChUaModel,
|
|
1033
|
-
"sec-ch-ua-full-version-list": secChUaFullVersionList
|
|
1034
|
-
};
|
|
1035
|
-
if (isWindows) {
|
|
1036
|
-
headers["sec-ch-ua-wow64"] = "?0";
|
|
1037
|
-
}
|
|
1038
|
-
return {
|
|
1039
|
-
brands: lowEntropyBrands,
|
|
1040
|
-
mobile,
|
|
1041
|
-
platform: os.platform,
|
|
1042
|
-
platformVersion: os.version,
|
|
1043
|
-
architecture,
|
|
1044
|
-
bitness,
|
|
1045
|
-
model,
|
|
1046
|
-
fullVersionList,
|
|
1047
|
-
wow64: isWindows ? false : void 0,
|
|
1048
|
-
headers
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
function shuffleArray(arr, prng) {
|
|
1052
|
-
for (let i = arr.length - 1; i > 0; i--) {
|
|
1053
|
-
const j = Math.floor(prng() * (i + 1));
|
|
1054
|
-
const temp = arr[i];
|
|
1055
|
-
arr[i] = arr[j];
|
|
1056
|
-
arr[j] = temp;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const DEFAULT_COUNTRY = "US";
|
|
1061
|
-
const DEFAULT_LOCALE = "en-US";
|
|
1062
|
-
const DEFAULT_TIMEZONE = "America/New_York";
|
|
1063
|
-
const DEFAULT_ACCEPT_LANGUAGE = "en-US,en;q=0.9";
|
|
1064
|
-
const COUNTRY_LOCALES = {
|
|
1065
|
-
US: {
|
|
1066
|
-
country: "US",
|
|
1067
|
-
countryName: "United States",
|
|
1068
|
-
primaryLocale: "en-US",
|
|
1069
|
-
languages: ["en-US", "en"],
|
|
1070
|
-
acceptLanguage: "en-US,en;q=0.9",
|
|
1071
|
-
timezones: [
|
|
1072
|
-
"America/New_York",
|
|
1073
|
-
"America/Los_Angeles",
|
|
1074
|
-
"America/Chicago",
|
|
1075
|
-
"America/Denver",
|
|
1076
|
-
"America/Phoenix",
|
|
1077
|
-
"America/Anchorage",
|
|
1078
|
-
"America/Honolulu",
|
|
1079
|
-
"America/Detroit",
|
|
1080
|
-
"America/Indiana/Indianapolis"
|
|
1081
|
-
],
|
|
1082
|
-
defaultTimezone: "America/New_York",
|
|
1083
|
-
currency: "USD",
|
|
1084
|
-
coordinates: { latitude: 37.7749, longitude: -122.4194, accuracy: 1e3 }
|
|
1085
|
-
},
|
|
1086
|
-
GB: {
|
|
1087
|
-
country: "GB",
|
|
1088
|
-
countryName: "United Kingdom",
|
|
1089
|
-
primaryLocale: "en-GB",
|
|
1090
|
-
languages: ["en-GB", "en-US", "en"],
|
|
1091
|
-
acceptLanguage: "en-GB,en-US;q=0.9,en;q=0.8",
|
|
1092
|
-
timezones: ["Europe/London"],
|
|
1093
|
-
defaultTimezone: "Europe/London",
|
|
1094
|
-
currency: "GBP",
|
|
1095
|
-
coordinates: { latitude: 51.5074, longitude: -0.1278, accuracy: 1e3 }
|
|
1096
|
-
},
|
|
1097
|
-
DE: {
|
|
1098
|
-
country: "DE",
|
|
1099
|
-
countryName: "Germany",
|
|
1100
|
-
primaryLocale: "de-DE",
|
|
1101
|
-
languages: ["de-DE", "de", "en-US", "en"],
|
|
1102
|
-
acceptLanguage: "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1103
|
-
timezones: ["Europe/Berlin"],
|
|
1104
|
-
defaultTimezone: "Europe/Berlin",
|
|
1105
|
-
currency: "EUR",
|
|
1106
|
-
coordinates: { latitude: 52.52, longitude: 13.405, accuracy: 1e3 }
|
|
1107
|
-
},
|
|
1108
|
-
FR: {
|
|
1109
|
-
country: "FR",
|
|
1110
|
-
countryName: "France",
|
|
1111
|
-
primaryLocale: "fr-FR",
|
|
1112
|
-
languages: ["fr-FR", "fr", "en-US", "en"],
|
|
1113
|
-
acceptLanguage: "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1114
|
-
timezones: ["Europe/Paris"],
|
|
1115
|
-
defaultTimezone: "Europe/Paris",
|
|
1116
|
-
currency: "EUR",
|
|
1117
|
-
coordinates: { latitude: 48.8566, longitude: 2.3522, accuracy: 1e3 }
|
|
1118
|
-
},
|
|
1119
|
-
JP: {
|
|
1120
|
-
country: "JP",
|
|
1121
|
-
countryName: "Japan",
|
|
1122
|
-
primaryLocale: "ja-JP",
|
|
1123
|
-
languages: ["ja-JP", "ja", "en-US"],
|
|
1124
|
-
acceptLanguage: "ja-JP,ja;q=0.9,en-US;q=0.8",
|
|
1125
|
-
timezones: ["Asia/Tokyo"],
|
|
1126
|
-
defaultTimezone: "Asia/Tokyo",
|
|
1127
|
-
currency: "JPY",
|
|
1128
|
-
coordinates: { latitude: 35.6762, longitude: 139.6503, accuracy: 1e3 }
|
|
1129
|
-
},
|
|
1130
|
-
SG: {
|
|
1131
|
-
country: "SG",
|
|
1132
|
-
countryName: "Singapore",
|
|
1133
|
-
primaryLocale: "en-SG",
|
|
1134
|
-
languages: ["en-SG", "en-US", "en", "zh-CN"],
|
|
1135
|
-
acceptLanguage: "en-SG,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7",
|
|
1136
|
-
timezones: ["Asia/Singapore"],
|
|
1137
|
-
defaultTimezone: "Asia/Singapore",
|
|
1138
|
-
currency: "SGD",
|
|
1139
|
-
coordinates: { latitude: 1.3521, longitude: 103.8198, accuracy: 1e3 }
|
|
1140
|
-
},
|
|
1141
|
-
HK: {
|
|
1142
|
-
country: "HK",
|
|
1143
|
-
countryName: "Hong Kong",
|
|
1144
|
-
primaryLocale: "zh-HK",
|
|
1145
|
-
languages: ["zh-HK", "zh-TW", "zh", "en-US"],
|
|
1146
|
-
acceptLanguage: "zh-HK,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7",
|
|
1147
|
-
timezones: ["Asia/Hong_Kong"],
|
|
1148
|
-
defaultTimezone: "Asia/Hong_Kong",
|
|
1149
|
-
currency: "HKD",
|
|
1150
|
-
coordinates: { latitude: 22.3193, longitude: 114.1694, accuracy: 1e3 }
|
|
1151
|
-
},
|
|
1152
|
-
TW: {
|
|
1153
|
-
country: "TW",
|
|
1154
|
-
countryName: "Taiwan",
|
|
1155
|
-
primaryLocale: "zh-TW",
|
|
1156
|
-
languages: ["zh-TW", "zh", "en-US"],
|
|
1157
|
-
acceptLanguage: "zh-TW,zh;q=0.9,en-US;q=0.8",
|
|
1158
|
-
timezones: ["Asia/Taipei"],
|
|
1159
|
-
defaultTimezone: "Asia/Taipei",
|
|
1160
|
-
currency: "TWD",
|
|
1161
|
-
coordinates: { latitude: 25.033, longitude: 121.5654, accuracy: 1e3 }
|
|
1162
|
-
},
|
|
1163
|
-
CN: {
|
|
1164
|
-
country: "CN",
|
|
1165
|
-
countryName: "China",
|
|
1166
|
-
primaryLocale: "zh-CN",
|
|
1167
|
-
languages: ["zh-CN", "zh", "en-US", "en"],
|
|
1168
|
-
acceptLanguage: "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1169
|
-
timezones: ["Asia/Shanghai", "Asia/Urumqi"],
|
|
1170
|
-
defaultTimezone: "Asia/Shanghai",
|
|
1171
|
-
currency: "CNY",
|
|
1172
|
-
coordinates: { latitude: 31.2304, longitude: 121.4737, accuracy: 1e3 }
|
|
1173
|
-
},
|
|
1174
|
-
KR: {
|
|
1175
|
-
country: "KR",
|
|
1176
|
-
countryName: "South Korea",
|
|
1177
|
-
primaryLocale: "ko-KR",
|
|
1178
|
-
languages: ["ko-KR", "ko", "en-US", "en"],
|
|
1179
|
-
acceptLanguage: "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1180
|
-
timezones: ["Asia/Seoul"],
|
|
1181
|
-
defaultTimezone: "Asia/Seoul",
|
|
1182
|
-
currency: "KRW",
|
|
1183
|
-
coordinates: { latitude: 37.5665, longitude: 126.978, accuracy: 1e3 }
|
|
1184
|
-
},
|
|
1185
|
-
AU: {
|
|
1186
|
-
country: "AU",
|
|
1187
|
-
countryName: "Australia",
|
|
1188
|
-
primaryLocale: "en-AU",
|
|
1189
|
-
languages: ["en-AU", "en-GB", "en-US", "en"],
|
|
1190
|
-
acceptLanguage: "en-AU,en-GB;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1191
|
-
timezones: [
|
|
1192
|
-
"Australia/Sydney",
|
|
1193
|
-
"Australia/Melbourne",
|
|
1194
|
-
"Australia/Brisbane",
|
|
1195
|
-
"Australia/Perth",
|
|
1196
|
-
"Australia/Adelaide",
|
|
1197
|
-
"Australia/Hobart",
|
|
1198
|
-
"Australia/Darwin"
|
|
1199
|
-
],
|
|
1200
|
-
defaultTimezone: "Australia/Sydney",
|
|
1201
|
-
currency: "AUD",
|
|
1202
|
-
coordinates: { latitude: -33.8688, longitude: 151.2093, accuracy: 1e3 }
|
|
1203
|
-
},
|
|
1204
|
-
CA: {
|
|
1205
|
-
country: "CA",
|
|
1206
|
-
countryName: "Canada",
|
|
1207
|
-
primaryLocale: "en-CA",
|
|
1208
|
-
languages: ["en-CA", "en-US", "en", "fr-CA"],
|
|
1209
|
-
acceptLanguage: "en-CA,en-US;q=0.9,en;q=0.8,fr-CA;q=0.7",
|
|
1210
|
-
timezones: [
|
|
1211
|
-
"America/Toronto",
|
|
1212
|
-
"America/Vancouver",
|
|
1213
|
-
"America/Montreal",
|
|
1214
|
-
"America/Edmonton",
|
|
1215
|
-
"America/Winnipeg",
|
|
1216
|
-
"America/Halifax",
|
|
1217
|
-
"America/St_Johns"
|
|
1218
|
-
],
|
|
1219
|
-
defaultTimezone: "America/Toronto",
|
|
1220
|
-
currency: "CAD",
|
|
1221
|
-
coordinates: { latitude: 43.6532, longitude: -79.3832, accuracy: 1e3 }
|
|
1222
|
-
},
|
|
1223
|
-
NL: {
|
|
1224
|
-
country: "NL",
|
|
1225
|
-
countryName: "Netherlands",
|
|
1226
|
-
primaryLocale: "nl-NL",
|
|
1227
|
-
languages: ["nl-NL", "nl", "en-US", "en"],
|
|
1228
|
-
acceptLanguage: "nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1229
|
-
timezones: ["Europe/Amsterdam"],
|
|
1230
|
-
defaultTimezone: "Europe/Amsterdam",
|
|
1231
|
-
currency: "EUR",
|
|
1232
|
-
coordinates: { latitude: 52.3676, longitude: 4.9041, accuracy: 1e3 }
|
|
1233
|
-
},
|
|
1234
|
-
IN: {
|
|
1235
|
-
country: "IN",
|
|
1236
|
-
countryName: "India",
|
|
1237
|
-
primaryLocale: "en-IN",
|
|
1238
|
-
languages: ["en-IN", "en-GB", "en-US", "en", "hi"],
|
|
1239
|
-
acceptLanguage: "en-IN,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,hi;q=0.6",
|
|
1240
|
-
timezones: ["Asia/Kolkata"],
|
|
1241
|
-
defaultTimezone: "Asia/Kolkata",
|
|
1242
|
-
currency: "INR",
|
|
1243
|
-
coordinates: { latitude: 28.6139, longitude: 77.209, accuracy: 1e3 }
|
|
1244
|
-
},
|
|
1245
|
-
BR: {
|
|
1246
|
-
country: "BR",
|
|
1247
|
-
countryName: "Brazil",
|
|
1248
|
-
primaryLocale: "pt-BR",
|
|
1249
|
-
languages: ["pt-BR", "pt", "en-US", "en"],
|
|
1250
|
-
acceptLanguage: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1251
|
-
timezones: [
|
|
1252
|
-
"America/Sao_Paulo",
|
|
1253
|
-
"America/Fortaleza",
|
|
1254
|
-
"America/Manaus",
|
|
1255
|
-
"America/Recife",
|
|
1256
|
-
"America/Belem",
|
|
1257
|
-
"America/Cuiaba"
|
|
1258
|
-
],
|
|
1259
|
-
defaultTimezone: "America/Sao_Paulo",
|
|
1260
|
-
currency: "BRL",
|
|
1261
|
-
coordinates: { latitude: -23.5505, longitude: -46.6333, accuracy: 1e3 }
|
|
1262
|
-
},
|
|
1263
|
-
IT: {
|
|
1264
|
-
country: "IT",
|
|
1265
|
-
countryName: "Italy",
|
|
1266
|
-
primaryLocale: "it-IT",
|
|
1267
|
-
languages: ["it-IT", "it", "en-US", "en"],
|
|
1268
|
-
acceptLanguage: "it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1269
|
-
timezones: ["Europe/Rome"],
|
|
1270
|
-
defaultTimezone: "Europe/Rome",
|
|
1271
|
-
currency: "EUR",
|
|
1272
|
-
coordinates: { latitude: 41.9028, longitude: 12.4964, accuracy: 1e3 }
|
|
1273
|
-
},
|
|
1274
|
-
ES: {
|
|
1275
|
-
country: "ES",
|
|
1276
|
-
countryName: "Spain",
|
|
1277
|
-
primaryLocale: "es-ES",
|
|
1278
|
-
languages: ["es-ES", "es", "en-US", "en"],
|
|
1279
|
-
acceptLanguage: "es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1280
|
-
timezones: ["Europe/Madrid", "Atlantic/Canary"],
|
|
1281
|
-
defaultTimezone: "Europe/Madrid",
|
|
1282
|
-
currency: "EUR",
|
|
1283
|
-
coordinates: { latitude: 40.4168, longitude: -3.7038, accuracy: 1e3 }
|
|
1284
|
-
},
|
|
1285
|
-
RU: {
|
|
1286
|
-
country: "RU",
|
|
1287
|
-
countryName: "Russia",
|
|
1288
|
-
primaryLocale: "ru-RU",
|
|
1289
|
-
languages: ["ru-RU", "ru", "en-US", "en"],
|
|
1290
|
-
acceptLanguage: "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1291
|
-
timezones: ["Europe/Moscow", "Europe/Samara", "Asia/Yekaterinburg", "Asia/Novosibirsk", "Asia/Vladivostok"],
|
|
1292
|
-
defaultTimezone: "Europe/Moscow",
|
|
1293
|
-
currency: "RUB",
|
|
1294
|
-
coordinates: { latitude: 55.7558, longitude: 37.6173, accuracy: 1e3 }
|
|
1295
|
-
},
|
|
1296
|
-
MX: {
|
|
1297
|
-
country: "MX",
|
|
1298
|
-
countryName: "Mexico",
|
|
1299
|
-
primaryLocale: "es-MX",
|
|
1300
|
-
languages: ["es-MX", "es", "en-US", "en"],
|
|
1301
|
-
acceptLanguage: "es-MX,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1302
|
-
timezones: ["America/Mexico_City", "America/Cancun", "America/Monterrey", "America/Tijuana"],
|
|
1303
|
-
defaultTimezone: "America/Mexico_City",
|
|
1304
|
-
currency: "MXN",
|
|
1305
|
-
coordinates: { latitude: 19.4326, longitude: -99.1332, accuracy: 1e3 }
|
|
1306
|
-
},
|
|
1307
|
-
ID: {
|
|
1308
|
-
country: "ID",
|
|
1309
|
-
countryName: "Indonesia",
|
|
1310
|
-
primaryLocale: "id-ID",
|
|
1311
|
-
languages: ["id-ID", "id", "en-US", "en"],
|
|
1312
|
-
acceptLanguage: "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1313
|
-
timezones: ["Asia/Jakarta", "Asia/Makassar", "Asia/Jayapura"],
|
|
1314
|
-
defaultTimezone: "Asia/Jakarta",
|
|
1315
|
-
currency: "IDR",
|
|
1316
|
-
coordinates: { latitude: -6.2088, longitude: 106.8456, accuracy: 1e3 }
|
|
1317
|
-
},
|
|
1318
|
-
MY: {
|
|
1319
|
-
country: "MY",
|
|
1320
|
-
countryName: "Malaysia",
|
|
1321
|
-
primaryLocale: "ms-MY",
|
|
1322
|
-
languages: ["ms-MY", "ms", "en-US", "en"],
|
|
1323
|
-
acceptLanguage: "ms-MY,ms;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1324
|
-
timezones: ["Asia/Kuala_Lumpur", "Asia/Kuching"],
|
|
1325
|
-
defaultTimezone: "Asia/Kuala_Lumpur",
|
|
1326
|
-
currency: "MYR",
|
|
1327
|
-
coordinates: { latitude: 3.139, longitude: 101.6869, accuracy: 1e3 }
|
|
1328
|
-
},
|
|
1329
|
-
TH: {
|
|
1330
|
-
country: "TH",
|
|
1331
|
-
countryName: "Thailand",
|
|
1332
|
-
primaryLocale: "th-TH",
|
|
1333
|
-
languages: ["th-TH", "th", "en-US", "en"],
|
|
1334
|
-
acceptLanguage: "th-TH,th;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1335
|
-
timezones: ["Asia/Bangkok"],
|
|
1336
|
-
defaultTimezone: "Asia/Bangkok",
|
|
1337
|
-
currency: "THB",
|
|
1338
|
-
coordinates: { latitude: 13.7563, longitude: 100.5018, accuracy: 1e3 }
|
|
1339
|
-
},
|
|
1340
|
-
VN: {
|
|
1341
|
-
country: "VN",
|
|
1342
|
-
countryName: "Vietnam",
|
|
1343
|
-
primaryLocale: "vi-VN",
|
|
1344
|
-
languages: ["vi-VN", "vi", "en-US", "en"],
|
|
1345
|
-
acceptLanguage: "vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1346
|
-
timezones: ["Asia/Ho_Chi_Minh"],
|
|
1347
|
-
defaultTimezone: "Asia/Ho_Chi_Minh",
|
|
1348
|
-
currency: "VND",
|
|
1349
|
-
coordinates: { latitude: 10.8231, longitude: 106.6297, accuracy: 1e3 }
|
|
1350
|
-
},
|
|
1351
|
-
PH: {
|
|
1352
|
-
country: "PH",
|
|
1353
|
-
countryName: "Philippines",
|
|
1354
|
-
primaryLocale: "en-PH",
|
|
1355
|
-
languages: ["en-PH", "en-US", "en", "fil"],
|
|
1356
|
-
acceptLanguage: "en-PH,en-US;q=0.9,en;q=0.8,fil;q=0.7",
|
|
1357
|
-
timezones: ["Asia/Manila"],
|
|
1358
|
-
defaultTimezone: "Asia/Manila",
|
|
1359
|
-
currency: "PHP",
|
|
1360
|
-
coordinates: { latitude: 14.5995, longitude: 120.9842, accuracy: 1e3 }
|
|
1361
|
-
},
|
|
1362
|
-
SE: {
|
|
1363
|
-
country: "SE",
|
|
1364
|
-
countryName: "Sweden",
|
|
1365
|
-
primaryLocale: "sv-SE",
|
|
1366
|
-
languages: ["sv-SE", "sv", "en-US", "en"],
|
|
1367
|
-
acceptLanguage: "sv-SE,sv;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1368
|
-
timezones: ["Europe/Stockholm"],
|
|
1369
|
-
defaultTimezone: "Europe/Stockholm",
|
|
1370
|
-
currency: "SEK",
|
|
1371
|
-
coordinates: { latitude: 59.3293, longitude: 18.0686, accuracy: 1e3 }
|
|
1372
|
-
},
|
|
1373
|
-
NO: {
|
|
1374
|
-
country: "NO",
|
|
1375
|
-
countryName: "Norway",
|
|
1376
|
-
primaryLocale: "nb-NO",
|
|
1377
|
-
languages: ["nb-NO", "nb", "nn", "en-US", "en"],
|
|
1378
|
-
acceptLanguage: "nb-NO,nb;q=0.9,nn;q=0.8,en-US;q=0.7,en;q=0.6",
|
|
1379
|
-
timezones: ["Europe/Oslo"],
|
|
1380
|
-
defaultTimezone: "Europe/Oslo",
|
|
1381
|
-
currency: "NOK",
|
|
1382
|
-
coordinates: { latitude: 59.9139, longitude: 10.7522, accuracy: 1e3 }
|
|
1383
|
-
},
|
|
1384
|
-
DK: {
|
|
1385
|
-
country: "DK",
|
|
1386
|
-
countryName: "Denmark",
|
|
1387
|
-
primaryLocale: "da-DK",
|
|
1388
|
-
languages: ["da-DK", "da", "en-US", "en"],
|
|
1389
|
-
acceptLanguage: "da-DK,da;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1390
|
-
timezones: ["Europe/Copenhagen"],
|
|
1391
|
-
defaultTimezone: "Europe/Copenhagen",
|
|
1392
|
-
currency: "DKK",
|
|
1393
|
-
coordinates: { latitude: 55.6761, longitude: 12.5683, accuracy: 1e3 }
|
|
1394
|
-
},
|
|
1395
|
-
FI: {
|
|
1396
|
-
country: "FI",
|
|
1397
|
-
countryName: "Finland",
|
|
1398
|
-
primaryLocale: "fi-FI",
|
|
1399
|
-
languages: ["fi-FI", "fi", "sv-FI", "en-US", "en"],
|
|
1400
|
-
acceptLanguage: "fi-FI,fi;q=0.9,sv-FI;q=0.8,en-US;q=0.7,en;q=0.6",
|
|
1401
|
-
timezones: ["Europe/Helsinki"],
|
|
1402
|
-
defaultTimezone: "Europe/Helsinki",
|
|
1403
|
-
currency: "EUR",
|
|
1404
|
-
coordinates: { latitude: 60.1699, longitude: 24.9384, accuracy: 1e3 }
|
|
1405
|
-
},
|
|
1406
|
-
PL: {
|
|
1407
|
-
country: "PL",
|
|
1408
|
-
countryName: "Poland",
|
|
1409
|
-
primaryLocale: "pl-PL",
|
|
1410
|
-
languages: ["pl-PL", "pl", "en-US", "en"],
|
|
1411
|
-
acceptLanguage: "pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1412
|
-
timezones: ["Europe/Warsaw"],
|
|
1413
|
-
defaultTimezone: "Europe/Warsaw",
|
|
1414
|
-
currency: "PLN",
|
|
1415
|
-
coordinates: { latitude: 52.2297, longitude: 21.0122, accuracy: 1e3 }
|
|
1416
|
-
},
|
|
1417
|
-
CH: {
|
|
1418
|
-
country: "CH",
|
|
1419
|
-
countryName: "Switzerland",
|
|
1420
|
-
primaryLocale: "de-CH",
|
|
1421
|
-
languages: ["de-CH", "fr-CH", "it-CH", "en-US", "en"],
|
|
1422
|
-
acceptLanguage: "de-CH,fr-CH;q=0.9,it-CH;q=0.8,en-US;q=0.7,en;q=0.6",
|
|
1423
|
-
timezones: ["Europe/Zurich"],
|
|
1424
|
-
defaultTimezone: "Europe/Zurich",
|
|
1425
|
-
currency: "CHF",
|
|
1426
|
-
coordinates: { latitude: 47.3769, longitude: 8.5417, accuracy: 1e3 }
|
|
1427
|
-
},
|
|
1428
|
-
AT: {
|
|
1429
|
-
country: "AT",
|
|
1430
|
-
countryName: "Austria",
|
|
1431
|
-
primaryLocale: "de-AT",
|
|
1432
|
-
languages: ["de-AT", "de", "en-US", "en"],
|
|
1433
|
-
acceptLanguage: "de-AT,de;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1434
|
-
timezones: ["Europe/Vienna"],
|
|
1435
|
-
defaultTimezone: "Europe/Vienna",
|
|
1436
|
-
currency: "EUR",
|
|
1437
|
-
coordinates: { latitude: 48.2082, longitude: 16.3738, accuracy: 1e3 }
|
|
1438
|
-
},
|
|
1439
|
-
BE: {
|
|
1440
|
-
country: "BE",
|
|
1441
|
-
countryName: "Belgium",
|
|
1442
|
-
primaryLocale: "nl-BE",
|
|
1443
|
-
languages: ["nl-BE", "fr-BE", "en-US", "en"],
|
|
1444
|
-
acceptLanguage: "nl-BE,fr-BE;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1445
|
-
timezones: ["Europe/Brussels"],
|
|
1446
|
-
defaultTimezone: "Europe/Brussels",
|
|
1447
|
-
currency: "EUR",
|
|
1448
|
-
coordinates: { latitude: 50.8503, longitude: 4.3517, accuracy: 1e3 }
|
|
1449
|
-
},
|
|
1450
|
-
IE: {
|
|
1451
|
-
country: "IE",
|
|
1452
|
-
countryName: "Ireland",
|
|
1453
|
-
primaryLocale: "en-IE",
|
|
1454
|
-
languages: ["en-IE", "en-GB", "en-US", "en", "ga"],
|
|
1455
|
-
acceptLanguage: "en-IE,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,ga;q=0.6",
|
|
1456
|
-
timezones: ["Europe/Dublin"],
|
|
1457
|
-
defaultTimezone: "Europe/Dublin",
|
|
1458
|
-
currency: "EUR",
|
|
1459
|
-
coordinates: { latitude: 53.3498, longitude: -6.2603, accuracy: 1e3 }
|
|
1460
|
-
},
|
|
1461
|
-
NZ: {
|
|
1462
|
-
country: "NZ",
|
|
1463
|
-
countryName: "New Zealand",
|
|
1464
|
-
primaryLocale: "en-NZ",
|
|
1465
|
-
languages: ["en-NZ", "en-GB", "en-US", "en"],
|
|
1466
|
-
acceptLanguage: "en-NZ,en-GB;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1467
|
-
timezones: ["Pacific/Auckland", "Pacific/Chatham"],
|
|
1468
|
-
defaultTimezone: "Pacific/Auckland",
|
|
1469
|
-
currency: "NZD",
|
|
1470
|
-
coordinates: { latitude: -36.8485, longitude: 174.7633, accuracy: 1e3 }
|
|
1471
|
-
},
|
|
1472
|
-
TR: {
|
|
1473
|
-
country: "TR",
|
|
1474
|
-
countryName: "Turkey",
|
|
1475
|
-
primaryLocale: "tr-TR",
|
|
1476
|
-
languages: ["tr-TR", "tr", "en-US", "en"],
|
|
1477
|
-
acceptLanguage: "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1478
|
-
timezones: ["Europe/Istanbul"],
|
|
1479
|
-
defaultTimezone: "Europe/Istanbul",
|
|
1480
|
-
currency: "TRY",
|
|
1481
|
-
coordinates: { latitude: 41.0082, longitude: 28.9784, accuracy: 1e3 }
|
|
1482
|
-
},
|
|
1483
|
-
SA: {
|
|
1484
|
-
country: "SA",
|
|
1485
|
-
countryName: "Saudi Arabia",
|
|
1486
|
-
primaryLocale: "ar-SA",
|
|
1487
|
-
languages: ["ar-SA", "ar", "en-US", "en"],
|
|
1488
|
-
acceptLanguage: "ar-SA,ar;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1489
|
-
timezones: ["Asia/Riyadh"],
|
|
1490
|
-
defaultTimezone: "Asia/Riyadh",
|
|
1491
|
-
currency: "SAR",
|
|
1492
|
-
coordinates: { latitude: 24.7136, longitude: 46.6753, accuracy: 1e3 }
|
|
1493
|
-
},
|
|
1494
|
-
AE: {
|
|
1495
|
-
country: "AE",
|
|
1496
|
-
countryName: "United Arab Emirates",
|
|
1497
|
-
primaryLocale: "ar-AE",
|
|
1498
|
-
languages: ["ar-AE", "ar", "en-US", "en"],
|
|
1499
|
-
acceptLanguage: "ar-AE,ar;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1500
|
-
timezones: ["Asia/Dubai"],
|
|
1501
|
-
defaultTimezone: "Asia/Dubai",
|
|
1502
|
-
currency: "AED",
|
|
1503
|
-
coordinates: { latitude: 25.2048, longitude: 55.2708, accuracy: 1e3 }
|
|
1504
|
-
},
|
|
1505
|
-
ZA: {
|
|
1506
|
-
country: "ZA",
|
|
1507
|
-
countryName: "South Africa",
|
|
1508
|
-
primaryLocale: "en-ZA",
|
|
1509
|
-
languages: ["en-ZA", "en-GB", "en-US", "en", "af"],
|
|
1510
|
-
acceptLanguage: "en-ZA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,af;q=0.6",
|
|
1511
|
-
timezones: ["Africa/Johannesburg"],
|
|
1512
|
-
defaultTimezone: "Africa/Johannesburg",
|
|
1513
|
-
currency: "ZAR",
|
|
1514
|
-
coordinates: { latitude: -26.2041, longitude: 28.0473, accuracy: 1e3 }
|
|
1515
|
-
},
|
|
1516
|
-
AR: {
|
|
1517
|
-
country: "AR",
|
|
1518
|
-
countryName: "Argentina",
|
|
1519
|
-
primaryLocale: "es-AR",
|
|
1520
|
-
languages: ["es-AR", "es", "en-US", "en"],
|
|
1521
|
-
acceptLanguage: "es-AR,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1522
|
-
timezones: ["America/Argentina/Buenos_Aires", "America/Argentina/Cordoba"],
|
|
1523
|
-
defaultTimezone: "America/Argentina/Buenos_Aires",
|
|
1524
|
-
currency: "ARS",
|
|
1525
|
-
coordinates: { latitude: -34.6037, longitude: -58.3816, accuracy: 1e3 }
|
|
1526
|
-
},
|
|
1527
|
-
CL: {
|
|
1528
|
-
country: "CL",
|
|
1529
|
-
countryName: "Chile",
|
|
1530
|
-
primaryLocale: "es-CL",
|
|
1531
|
-
languages: ["es-CL", "es", "en-US", "en"],
|
|
1532
|
-
acceptLanguage: "es-CL,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1533
|
-
timezones: ["America/Santiago", "Pacific/Easter"],
|
|
1534
|
-
defaultTimezone: "America/Santiago",
|
|
1535
|
-
currency: "CLP",
|
|
1536
|
-
coordinates: { latitude: -33.4489, longitude: -70.6693, accuracy: 1e3 }
|
|
1537
|
-
},
|
|
1538
|
-
CO: {
|
|
1539
|
-
country: "CO",
|
|
1540
|
-
countryName: "Colombia",
|
|
1541
|
-
primaryLocale: "es-CO",
|
|
1542
|
-
languages: ["es-CO", "es", "en-US", "en"],
|
|
1543
|
-
acceptLanguage: "es-CO,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1544
|
-
timezones: ["America/Bogota"],
|
|
1545
|
-
defaultTimezone: "America/Bogota",
|
|
1546
|
-
currency: "COP",
|
|
1547
|
-
coordinates: { latitude: 4.711, longitude: -74.0721, accuracy: 1e3 }
|
|
1548
|
-
},
|
|
1549
|
-
IL: {
|
|
1550
|
-
country: "IL",
|
|
1551
|
-
countryName: "Israel",
|
|
1552
|
-
primaryLocale: "he-IL",
|
|
1553
|
-
languages: ["he-IL", "he", "en-US", "en"],
|
|
1554
|
-
acceptLanguage: "he-IL,he;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1555
|
-
timezones: ["Asia/Jerusalem"],
|
|
1556
|
-
defaultTimezone: "Asia/Jerusalem",
|
|
1557
|
-
currency: "ILS",
|
|
1558
|
-
coordinates: { latitude: 31.7683, longitude: 35.2137, accuracy: 1e3 }
|
|
1559
|
-
},
|
|
1560
|
-
UA: {
|
|
1561
|
-
country: "UA",
|
|
1562
|
-
countryName: "Ukraine",
|
|
1563
|
-
primaryLocale: "uk-UA",
|
|
1564
|
-
languages: ["uk-UA", "uk", "en-US", "en"],
|
|
1565
|
-
acceptLanguage: "uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1566
|
-
timezones: ["Europe/Kyiv"],
|
|
1567
|
-
defaultTimezone: "Europe/Kyiv",
|
|
1568
|
-
currency: "UAH",
|
|
1569
|
-
coordinates: { latitude: 50.4501, longitude: 30.5234, accuracy: 1e3 }
|
|
1570
|
-
},
|
|
1571
|
-
PT: {
|
|
1572
|
-
country: "PT",
|
|
1573
|
-
countryName: "Portugal",
|
|
1574
|
-
primaryLocale: "pt-PT",
|
|
1575
|
-
languages: ["pt-PT", "pt", "en-US", "en"],
|
|
1576
|
-
acceptLanguage: "pt-PT,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1577
|
-
timezones: ["Europe/Lisbon", "Atlantic/Madeira", "Atlantic/Azores"],
|
|
1578
|
-
defaultTimezone: "Europe/Lisbon",
|
|
1579
|
-
currency: "EUR",
|
|
1580
|
-
coordinates: { latitude: 38.7223, longitude: -9.1393, accuracy: 1e3 }
|
|
1581
|
-
},
|
|
1582
|
-
CZ: {
|
|
1583
|
-
country: "CZ",
|
|
1584
|
-
countryName: "Czech Republic",
|
|
1585
|
-
primaryLocale: "cs-CZ",
|
|
1586
|
-
languages: ["cs-CZ", "cs", "en-US", "en"],
|
|
1587
|
-
acceptLanguage: "cs-CZ,cs;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1588
|
-
timezones: ["Europe/Prague"],
|
|
1589
|
-
defaultTimezone: "Europe/Prague",
|
|
1590
|
-
currency: "CZK",
|
|
1591
|
-
coordinates: { latitude: 50.0755, longitude: 14.4378, accuracy: 1e3 }
|
|
1592
|
-
},
|
|
1593
|
-
RO: {
|
|
1594
|
-
country: "RO",
|
|
1595
|
-
countryName: "Romania",
|
|
1596
|
-
primaryLocale: "ro-RO",
|
|
1597
|
-
languages: ["ro-RO", "ro", "en-US", "en"],
|
|
1598
|
-
acceptLanguage: "ro-RO,ro;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1599
|
-
timezones: ["Europe/Bucharest"],
|
|
1600
|
-
defaultTimezone: "Europe/Bucharest",
|
|
1601
|
-
currency: "RON",
|
|
1602
|
-
coordinates: { latitude: 44.4268, longitude: 26.1025, accuracy: 1e3 }
|
|
1603
|
-
},
|
|
1604
|
-
GR: {
|
|
1605
|
-
country: "GR",
|
|
1606
|
-
countryName: "Greece",
|
|
1607
|
-
primaryLocale: "el-GR",
|
|
1608
|
-
languages: ["el-GR", "el", "en-US", "en"],
|
|
1609
|
-
acceptLanguage: "el-GR,el;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1610
|
-
timezones: ["Europe/Athens"],
|
|
1611
|
-
defaultTimezone: "Europe/Athens",
|
|
1612
|
-
currency: "EUR",
|
|
1613
|
-
coordinates: { latitude: 37.9838, longitude: 23.7275, accuracy: 1e3 }
|
|
1614
|
-
},
|
|
1615
|
-
HU: {
|
|
1616
|
-
country: "HU",
|
|
1617
|
-
countryName: "Hungary",
|
|
1618
|
-
primaryLocale: "hu-HU",
|
|
1619
|
-
languages: ["hu-HU", "hu", "en-US", "en"],
|
|
1620
|
-
acceptLanguage: "hu-HU,hu;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
1621
|
-
timezones: ["Europe/Budapest"],
|
|
1622
|
-
defaultTimezone: "Europe/Budapest",
|
|
1623
|
-
currency: "HUF",
|
|
1624
|
-
coordinates: { latitude: 47.4979, longitude: 19.0402, accuracy: 1e3 }
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
function getCountryConfig(countryCode) {
|
|
1628
|
-
if (!countryCode) {
|
|
1629
|
-
return COUNTRY_LOCALES[DEFAULT_COUNTRY];
|
|
1630
|
-
}
|
|
1631
|
-
const normalized = countryCode.trim().toUpperCase();
|
|
1632
|
-
return COUNTRY_LOCALES[normalized] || COUNTRY_LOCALES[DEFAULT_COUNTRY];
|
|
1633
|
-
}
|
|
1634
|
-
function hasCountryConfig(countryCode) {
|
|
1635
|
-
if (!countryCode) return false;
|
|
1636
|
-
return Object.prototype.hasOwnProperty.call(COUNTRY_LOCALES, countryCode.trim().toUpperCase());
|
|
1637
|
-
}
|
|
1638
|
-
function getAllSupportedCountries() {
|
|
1639
|
-
return Object.keys(COUNTRY_LOCALES);
|
|
1640
|
-
}
|
|
1641
|
-
function formatAcceptLanguage(languages) {
|
|
1642
|
-
if (!languages || languages.length === 0) {
|
|
1643
|
-
return DEFAULT_ACCEPT_LANGUAGE;
|
|
1644
|
-
}
|
|
1645
|
-
const uniqueLangs = [];
|
|
1646
|
-
for (const lang of languages) {
|
|
1647
|
-
const trimmed = lang.trim();
|
|
1648
|
-
if (trimmed && !uniqueLangs.includes(trimmed)) {
|
|
1649
|
-
uniqueLangs.push(trimmed);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
if (uniqueLangs.length === 0) {
|
|
1653
|
-
return DEFAULT_ACCEPT_LANGUAGE;
|
|
1654
|
-
}
|
|
1655
|
-
return uniqueLangs.map((lang, index) => {
|
|
1656
|
-
if (index === 0) return lang;
|
|
1657
|
-
const weight = Math.max(0.1, Number((1 - index * 0.1).toFixed(1)));
|
|
1658
|
-
return `${lang};q=${weight}`;
|
|
1659
|
-
}).join(",");
|
|
1660
|
-
}
|
|
1661
|
-
function parseAcceptLanguage(header) {
|
|
1662
|
-
if (!header || typeof header !== "string") return [];
|
|
1663
|
-
return header.split(",").map((part) => {
|
|
1664
|
-
const [localePart, ...params] = part.trim().split(";");
|
|
1665
|
-
const locale = localePart.trim();
|
|
1666
|
-
let q = 1;
|
|
1667
|
-
for (const param of params) {
|
|
1668
|
-
const [key, val] = param.trim().split("=");
|
|
1669
|
-
if (key && key.trim() === "q" && val) {
|
|
1670
|
-
const parsed = parseFloat(val.trim());
|
|
1671
|
-
if (!isNaN(parsed)) {
|
|
1672
|
-
q = parsed;
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
return { locale, q };
|
|
1677
|
-
}).filter((item) => item.locale.length > 0).sort((a, b) => b.q - a.q);
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
const TIMEZONE_TO_COUNTRY = {
|
|
1681
|
-
// United States
|
|
1682
|
-
"America/New_York": "US",
|
|
1683
|
-
"America/Los_Angeles": "US",
|
|
1684
|
-
"America/Chicago": "US",
|
|
1685
|
-
"America/Denver": "US",
|
|
1686
|
-
"America/Phoenix": "US",
|
|
1687
|
-
"America/Anchorage": "US",
|
|
1688
|
-
"America/Honolulu": "US",
|
|
1689
|
-
"America/Detroit": "US",
|
|
1690
|
-
"America/Indiana/Indianapolis": "US",
|
|
1691
|
-
"America/Boise": "US",
|
|
1692
|
-
"America/Juneau": "US",
|
|
1693
|
-
"America/Adak": "US",
|
|
1694
|
-
"America/Nome": "US",
|
|
1695
|
-
"America/Sitka": "US",
|
|
1696
|
-
"America/Yakutat": "US",
|
|
1697
|
-
"America/Metlakatla": "US",
|
|
1698
|
-
"America/Menominee": "US",
|
|
1699
|
-
"America/Kentucky/Louisville": "US",
|
|
1700
|
-
"America/Kentucky/Monticello": "US",
|
|
1701
|
-
"America/Indiana/Marengo": "US",
|
|
1702
|
-
"America/Indiana/Vincennes": "US",
|
|
1703
|
-
"America/Indiana/Tell_City": "US",
|
|
1704
|
-
"America/Indiana/Petersburg": "US",
|
|
1705
|
-
"America/Indiana/Knox": "US",
|
|
1706
|
-
"America/Indiana/Winamac": "US",
|
|
1707
|
-
"America/Indiana/Vevay": "US",
|
|
1708
|
-
"America/North_Dakota/Center": "US",
|
|
1709
|
-
"America/North_Dakota/New_Salem": "US",
|
|
1710
|
-
"America/North_Dakota/Beulah": "US",
|
|
1711
|
-
// United Kingdom
|
|
1712
|
-
"Europe/London": "GB",
|
|
1713
|
-
// Germany
|
|
1714
|
-
"Europe/Berlin": "DE",
|
|
1715
|
-
"Europe/Busingen": "DE",
|
|
1716
|
-
// France
|
|
1717
|
-
"Europe/Paris": "FR",
|
|
1718
|
-
// Japan
|
|
1719
|
-
"Asia/Tokyo": "JP",
|
|
1720
|
-
// Singapore
|
|
1721
|
-
"Asia/Singapore": "SG",
|
|
1722
|
-
// Hong Kong
|
|
1723
|
-
"Asia/Hong_Kong": "HK",
|
|
1724
|
-
// Taiwan
|
|
1725
|
-
"Asia/Taipei": "TW",
|
|
1726
|
-
// China
|
|
1727
|
-
"Asia/Shanghai": "CN",
|
|
1728
|
-
"Asia/Urumqi": "CN",
|
|
1729
|
-
"Asia/Chongqing": "CN",
|
|
1730
|
-
"Asia/Harbin": "CN",
|
|
1731
|
-
"Asia/Kashgar": "CN",
|
|
1732
|
-
// South Korea
|
|
1733
|
-
"Asia/Seoul": "KR",
|
|
1734
|
-
// Australia
|
|
1735
|
-
"Australia/Sydney": "AU",
|
|
1736
|
-
"Australia/Melbourne": "AU",
|
|
1737
|
-
"Australia/Brisbane": "AU",
|
|
1738
|
-
"Australia/Perth": "AU",
|
|
1739
|
-
"Australia/Adelaide": "AU",
|
|
1740
|
-
"Australia/Hobart": "AU",
|
|
1741
|
-
"Australia/Darwin": "AU",
|
|
1742
|
-
"Australia/Broken_Hill": "AU",
|
|
1743
|
-
"Australia/Lord_Howe": "AU",
|
|
1744
|
-
"Australia/Lindeman": "AU",
|
|
1745
|
-
"Australia/Eucla": "AU",
|
|
1746
|
-
// Canada
|
|
1747
|
-
"America/Toronto": "CA",
|
|
1748
|
-
"America/Vancouver": "CA",
|
|
1749
|
-
"America/Montreal": "CA",
|
|
1750
|
-
"America/Edmonton": "CA",
|
|
1751
|
-
"America/Winnipeg": "CA",
|
|
1752
|
-
"America/Halifax": "CA",
|
|
1753
|
-
"America/St_Johns": "CA",
|
|
1754
|
-
"America/Regina": "CA",
|
|
1755
|
-
"America/Calgary": "CA",
|
|
1756
|
-
"America/Whitehorse": "CA",
|
|
1757
|
-
"America/Dawson": "CA",
|
|
1758
|
-
"America/Yellowknife": "CA",
|
|
1759
|
-
"America/Iqaluit": "CA",
|
|
1760
|
-
// Netherlands
|
|
1761
|
-
"Europe/Amsterdam": "NL",
|
|
1762
|
-
// India
|
|
1763
|
-
"Asia/Kolkata": "IN",
|
|
1764
|
-
"Asia/Calcutta": "IN",
|
|
1765
|
-
// Brazil
|
|
1766
|
-
"America/Sao_Paulo": "BR",
|
|
1767
|
-
"America/Fortaleza": "BR",
|
|
1768
|
-
"America/Manaus": "BR",
|
|
1769
|
-
"America/Recife": "BR",
|
|
1770
|
-
"America/Belem": "BR",
|
|
1771
|
-
"America/Cuiaba": "BR",
|
|
1772
|
-
"America/Porto_Velho": "BR",
|
|
1773
|
-
"America/Boa_Vista": "BR",
|
|
1774
|
-
"America/Rio_Branco": "BR",
|
|
1775
|
-
"America/Campo_Grande": "BR",
|
|
1776
|
-
"America/Maceio": "BR",
|
|
1777
|
-
"America/Araguaina": "BR",
|
|
1778
|
-
"America/Santarem": "BR",
|
|
1779
|
-
"America/Noronha": "BR",
|
|
1780
|
-
// Italy
|
|
1781
|
-
"Europe/Rome": "IT",
|
|
1782
|
-
// Spain
|
|
1783
|
-
"Europe/Madrid": "ES",
|
|
1784
|
-
"Atlantic/Canary": "ES",
|
|
1785
|
-
"Africa/Ceuta": "ES",
|
|
1786
|
-
// Russia
|
|
1787
|
-
"Europe/Moscow": "RU",
|
|
1788
|
-
"Europe/Samara": "RU",
|
|
1789
|
-
"Europe/Kaliningrad": "RU",
|
|
1790
|
-
"Asia/Yekaterinburg": "RU",
|
|
1791
|
-
"Asia/Novosibirsk": "RU",
|
|
1792
|
-
"Asia/Krasnoyarsk": "RU",
|
|
1793
|
-
"Asia/Irkutsk": "RU",
|
|
1794
|
-
"Asia/Yakutsk": "RU",
|
|
1795
|
-
"Asia/Vladivostok": "RU",
|
|
1796
|
-
"Asia/Magadan": "RU",
|
|
1797
|
-
"Asia/Kamchatka": "RU",
|
|
1798
|
-
// Mexico
|
|
1799
|
-
"America/Mexico_City": "MX",
|
|
1800
|
-
"America/Cancun": "MX",
|
|
1801
|
-
"America/Monterrey": "MX",
|
|
1802
|
-
"America/Tijuana": "MX",
|
|
1803
|
-
"America/Hermosillo": "MX",
|
|
1804
|
-
"America/Chihuahua": "MX",
|
|
1805
|
-
"America/Mazatlan": "MX",
|
|
1806
|
-
"America/Merida": "MX",
|
|
1807
|
-
// Indonesia
|
|
1808
|
-
"Asia/Jakarta": "ID",
|
|
1809
|
-
"Asia/Makassar": "ID",
|
|
1810
|
-
"Asia/Jayapura": "ID",
|
|
1811
|
-
"Asia/Pontianak": "ID",
|
|
1812
|
-
// Malaysia
|
|
1813
|
-
"Asia/Kuala_Lumpur": "MY",
|
|
1814
|
-
"Asia/Kuching": "MY",
|
|
1815
|
-
// Thailand
|
|
1816
|
-
"Asia/Bangkok": "TH",
|
|
1817
|
-
// Vietnam
|
|
1818
|
-
"Asia/Ho_Chi_Minh": "VN",
|
|
1819
|
-
// Philippines
|
|
1820
|
-
"Asia/Manila": "PH",
|
|
1821
|
-
// Sweden
|
|
1822
|
-
"Europe/Stockholm": "SE",
|
|
1823
|
-
// Norway
|
|
1824
|
-
"Europe/Oslo": "NO",
|
|
1825
|
-
// Denmark
|
|
1826
|
-
"Europe/Copenhagen": "DK",
|
|
1827
|
-
// Finland
|
|
1828
|
-
"Europe/Helsinki": "FI",
|
|
1829
|
-
// Poland
|
|
1830
|
-
"Europe/Warsaw": "PL",
|
|
1831
|
-
// Switzerland
|
|
1832
|
-
"Europe/Zurich": "CH",
|
|
1833
|
-
// Austria
|
|
1834
|
-
"Europe/Vienna": "AT",
|
|
1835
|
-
// Belgium
|
|
1836
|
-
"Europe/Brussels": "BE",
|
|
1837
|
-
// Ireland
|
|
1838
|
-
"Europe/Dublin": "IE",
|
|
1839
|
-
// New Zealand
|
|
1840
|
-
"Pacific/Auckland": "NZ",
|
|
1841
|
-
"Pacific/Chatham": "NZ",
|
|
1842
|
-
// Turkey
|
|
1843
|
-
"Europe/Istanbul": "TR",
|
|
1844
|
-
// Saudi Arabia
|
|
1845
|
-
"Asia/Riyadh": "SA",
|
|
1846
|
-
// United Arab Emirates
|
|
1847
|
-
"Asia/Dubai": "AE",
|
|
1848
|
-
// South Africa
|
|
1849
|
-
"Africa/Johannesburg": "ZA",
|
|
1850
|
-
// Argentina
|
|
1851
|
-
"America/Argentina/Buenos_Aires": "AR",
|
|
1852
|
-
"America/Argentina/Cordoba": "AR",
|
|
1853
|
-
"America/Argentina/Salta": "AR",
|
|
1854
|
-
"America/Argentina/Jujuy": "AR",
|
|
1855
|
-
"America/Argentina/Tucuman": "AR",
|
|
1856
|
-
"America/Argentina/Catamarca": "AR",
|
|
1857
|
-
"America/Argentina/La_Rioja": "AR",
|
|
1858
|
-
"America/Argentina/San_Juan": "AR",
|
|
1859
|
-
"America/Argentina/Mendoza": "AR",
|
|
1860
|
-
"America/Argentina/San_Luis": "AR",
|
|
1861
|
-
"America/Argentina/Rio_Gallegos": "AR",
|
|
1862
|
-
"America/Argentina/Ushuaia": "AR",
|
|
1863
|
-
// Chile
|
|
1864
|
-
"America/Santiago": "CL",
|
|
1865
|
-
"America/Punta_Arenas": "CL",
|
|
1866
|
-
"Pacific/Easter": "CL",
|
|
1867
|
-
// Colombia
|
|
1868
|
-
"America/Bogota": "CO",
|
|
1869
|
-
// Israel
|
|
1870
|
-
"Asia/Jerusalem": "IL",
|
|
1871
|
-
// Ukraine
|
|
1872
|
-
"Europe/Kyiv": "UA",
|
|
1873
|
-
// Portugal
|
|
1874
|
-
"Europe/Lisbon": "PT",
|
|
1875
|
-
"Atlantic/Madeira": "PT",
|
|
1876
|
-
"Atlantic/Azores": "PT",
|
|
1877
|
-
// Czech Republic
|
|
1878
|
-
"Europe/Prague": "CZ",
|
|
1879
|
-
// Romania
|
|
1880
|
-
"Europe/Bucharest": "RO",
|
|
1881
|
-
// Greece
|
|
1882
|
-
"Europe/Athens": "GR",
|
|
1883
|
-
// Hungary
|
|
1884
|
-
"Europe/Budapest": "HU"
|
|
1885
|
-
};
|
|
1886
|
-
function isValidTimezone(timezone) {
|
|
1887
|
-
if (!timezone || typeof timezone !== "string") {
|
|
1888
|
-
return false;
|
|
1889
|
-
}
|
|
1890
|
-
const trimmed = timezone.trim();
|
|
1891
|
-
if (trimmed.length === 0) {
|
|
1892
|
-
return false;
|
|
1893
|
-
}
|
|
1894
|
-
if (TIMEZONE_TO_COUNTRY[trimmed]) {
|
|
1895
|
-
return true;
|
|
1896
|
-
}
|
|
1897
|
-
try {
|
|
1898
|
-
Intl.DateTimeFormat(void 0, { timeZone: trimmed });
|
|
1899
|
-
return true;
|
|
1900
|
-
} catch {
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
function getTimezonesForCountry(countryCode) {
|
|
1905
|
-
if (!countryCode) return [DEFAULT_TIMEZONE];
|
|
1906
|
-
const normalized = countryCode.trim().toUpperCase();
|
|
1907
|
-
const config = COUNTRY_LOCALES[normalized];
|
|
1908
|
-
if (config && config.timezones && config.timezones.length > 0) {
|
|
1909
|
-
return [...config.timezones];
|
|
1910
|
-
}
|
|
1911
|
-
const matched = Object.entries(TIMEZONE_TO_COUNTRY).filter(([_, cc]) => cc === normalized).map(([tz]) => tz);
|
|
1912
|
-
return matched.length > 0 ? matched : [DEFAULT_TIMEZONE];
|
|
1913
|
-
}
|
|
1914
|
-
function getCountryForTimezone(timezone) {
|
|
1915
|
-
if (!timezone) return void 0;
|
|
1916
|
-
const trimmed = timezone.trim();
|
|
1917
|
-
return TIMEZONE_TO_COUNTRY[trimmed];
|
|
1918
|
-
}
|
|
1919
|
-
function getDefaultTimezoneForCountry(countryCode) {
|
|
1920
|
-
if (!countryCode) return DEFAULT_TIMEZONE;
|
|
1921
|
-
const normalized = countryCode.trim().toUpperCase();
|
|
1922
|
-
const config = COUNTRY_LOCALES[normalized];
|
|
1923
|
-
if (config && config.defaultTimezone) {
|
|
1924
|
-
return config.defaultTimezone;
|
|
1925
|
-
}
|
|
1926
|
-
const countryTzs = getTimezonesForCountry(normalized);
|
|
1927
|
-
return countryTzs[0] || DEFAULT_TIMEZONE;
|
|
1928
|
-
}
|
|
1929
|
-
function isTimezoneAlignedWithCountry(timezone, countryCode) {
|
|
1930
|
-
if (!timezone || !countryCode) return false;
|
|
1931
|
-
const normalized = countryCode.trim().toUpperCase();
|
|
1932
|
-
const mappedCountry = getCountryForTimezone(timezone);
|
|
1933
|
-
if (mappedCountry === normalized) {
|
|
1934
|
-
return true;
|
|
1935
|
-
}
|
|
1936
|
-
const countryTzs = getTimezonesForCountry(normalized);
|
|
1937
|
-
return countryTzs.includes(timezone.trim());
|
|
1938
|
-
}
|
|
1939
|
-
function getTimezoneOffsetMinutes(timezone, date = /* @__PURE__ */ new Date()) {
|
|
1940
|
-
if (!timezone || !isValidTimezone(timezone)) {
|
|
1941
|
-
return 0;
|
|
1942
|
-
}
|
|
1943
|
-
try {
|
|
1944
|
-
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
|
|
1945
|
-
const tzDate = new Date(date.toLocaleString("en-US", { timeZone: timezone }));
|
|
1946
|
-
return Math.round((tzDate.getTime() - utcDate.getTime()) / 6e4);
|
|
1947
|
-
} catch {
|
|
1948
|
-
return 0;
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
function listAllTimezones() {
|
|
1952
|
-
return Object.keys(TIMEZONE_TO_COUNTRY);
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
const ALPHA3_TO_ALPHA2 = {
|
|
1956
|
-
USA: "US",
|
|
1957
|
-
GBR: "GB",
|
|
1958
|
-
DEU: "DE",
|
|
1959
|
-
FRA: "FR",
|
|
1960
|
-
JPN: "JP",
|
|
1961
|
-
SGP: "SG",
|
|
1962
|
-
HKG: "HK",
|
|
1963
|
-
TWN: "TW",
|
|
1964
|
-
CHN: "CN",
|
|
1965
|
-
KOR: "KR",
|
|
1966
|
-
AUS: "AU",
|
|
1967
|
-
CAN: "CA",
|
|
1968
|
-
NLD: "NL",
|
|
1969
|
-
IND: "IN",
|
|
1970
|
-
BRA: "BR",
|
|
1971
|
-
ITA: "IT",
|
|
1972
|
-
ESP: "ES",
|
|
1973
|
-
RUS: "RU",
|
|
1974
|
-
MEX: "MX",
|
|
1975
|
-
IDN: "ID",
|
|
1976
|
-
MYS: "MY",
|
|
1977
|
-
THA: "TH",
|
|
1978
|
-
VNM: "VN",
|
|
1979
|
-
PHL: "PH",
|
|
1980
|
-
SWE: "SE",
|
|
1981
|
-
NOR: "NO",
|
|
1982
|
-
DNK: "DK",
|
|
1983
|
-
FIN: "FI",
|
|
1984
|
-
POL: "PL",
|
|
1985
|
-
CHE: "CH",
|
|
1986
|
-
AUT: "AT",
|
|
1987
|
-
BEL: "BE",
|
|
1988
|
-
IRL: "IE",
|
|
1989
|
-
NZL: "NZ",
|
|
1990
|
-
TUR: "TR",
|
|
1991
|
-
SAU: "SA",
|
|
1992
|
-
ARE: "AE",
|
|
1993
|
-
ZAF: "ZA",
|
|
1994
|
-
ARG: "AR",
|
|
1995
|
-
CHL: "CL",
|
|
1996
|
-
COL: "CO",
|
|
1997
|
-
ISR: "IL",
|
|
1998
|
-
UKR: "UA",
|
|
1999
|
-
PRT: "PT",
|
|
2000
|
-
CZE: "CZ",
|
|
2001
|
-
ROU: "RO",
|
|
2002
|
-
GRC: "GR",
|
|
2003
|
-
HUN: "HU"
|
|
2004
|
-
};
|
|
2005
|
-
const COUNTRY_NAME_TO_CODE = {
|
|
2006
|
-
"united states": "US",
|
|
2007
|
-
"united states of america": "US",
|
|
2008
|
-
"great britain": "GB",
|
|
2009
|
-
"united kingdom": "GB",
|
|
2010
|
-
england: "GB",
|
|
2011
|
-
uk: "GB",
|
|
2012
|
-
germany: "DE",
|
|
2013
|
-
deutschland: "DE",
|
|
2014
|
-
france: "FR",
|
|
2015
|
-
japan: "JP",
|
|
2016
|
-
singapore: "SG",
|
|
2017
|
-
"hong kong": "HK",
|
|
2018
|
-
taiwan: "TW",
|
|
2019
|
-
china: "CN",
|
|
2020
|
-
"south korea": "KR",
|
|
2021
|
-
korea: "KR",
|
|
2022
|
-
australia: "AU",
|
|
2023
|
-
canada: "CA",
|
|
2024
|
-
netherlands: "NL",
|
|
2025
|
-
holland: "NL",
|
|
2026
|
-
india: "IN",
|
|
2027
|
-
brazil: "BR",
|
|
2028
|
-
brasil: "BR",
|
|
2029
|
-
italy: "IT",
|
|
2030
|
-
spain: "ES",
|
|
2031
|
-
russia: "RU",
|
|
2032
|
-
mexico: "MX",
|
|
2033
|
-
indonesia: "ID",
|
|
2034
|
-
malaysia: "MY",
|
|
2035
|
-
thailand: "TH",
|
|
2036
|
-
vietnam: "VN",
|
|
2037
|
-
philippines: "PH",
|
|
2038
|
-
sweden: "SE",
|
|
2039
|
-
norway: "NO",
|
|
2040
|
-
denmark: "DK",
|
|
2041
|
-
finland: "FI",
|
|
2042
|
-
poland: "PL",
|
|
2043
|
-
switzerland: "CH",
|
|
2044
|
-
austria: "AT",
|
|
2045
|
-
belgium: "BE",
|
|
2046
|
-
ireland: "IE",
|
|
2047
|
-
"new zealand": "NZ",
|
|
2048
|
-
turkey: "TR",
|
|
2049
|
-
t\u00FCrkiye: "TR",
|
|
2050
|
-
"saudi arabia": "SA",
|
|
2051
|
-
"united arab emirates": "AE",
|
|
2052
|
-
uae: "AE",
|
|
2053
|
-
"south africa": "ZA",
|
|
2054
|
-
argentina: "AR",
|
|
2055
|
-
chile: "CL",
|
|
2056
|
-
colombia: "CO",
|
|
2057
|
-
israel: "IL",
|
|
2058
|
-
ukraine: "UA",
|
|
2059
|
-
portugal: "PT",
|
|
2060
|
-
"czech republic": "CZ",
|
|
2061
|
-
czechia: "CZ",
|
|
2062
|
-
romania: "RO",
|
|
2063
|
-
greece: "GR",
|
|
2064
|
-
hungary: "HU"
|
|
2065
|
-
};
|
|
2066
|
-
function normalizeCountryCode(code) {
|
|
2067
|
-
if (!code || typeof code !== "string") return null;
|
|
2068
|
-
const trimmed = code.trim();
|
|
2069
|
-
if (!trimmed) return null;
|
|
2070
|
-
const upper = trimmed.toUpperCase();
|
|
2071
|
-
if (upper === "UK") return "GB";
|
|
2072
|
-
if (/^[A-Z]{2}$/.test(upper)) {
|
|
2073
|
-
return upper;
|
|
2074
|
-
}
|
|
2075
|
-
if (/^[A-Z]{3}$/.test(upper) && ALPHA3_TO_ALPHA2[upper]) {
|
|
2076
|
-
return ALPHA3_TO_ALPHA2[upper];
|
|
2077
|
-
}
|
|
2078
|
-
const lower = trimmed.toLowerCase();
|
|
2079
|
-
if (COUNTRY_NAME_TO_CODE[lower]) {
|
|
2080
|
-
return COUNTRY_NAME_TO_CODE[lower];
|
|
2081
|
-
}
|
|
2082
|
-
return null;
|
|
2083
|
-
}
|
|
2084
|
-
function extractCountryFromProxyUrl(url) {
|
|
2085
|
-
if (!url || typeof url !== "string") return null;
|
|
2086
|
-
const trimmed = url.trim();
|
|
2087
|
-
if (!trimmed) return null;
|
|
2088
|
-
let parsedUrl = null;
|
|
2089
|
-
try {
|
|
2090
|
-
const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed);
|
|
2091
|
-
parsedUrl = new URL(hasScheme ? trimmed : `http://${trimmed}`);
|
|
2092
|
-
} catch {
|
|
2093
|
-
parsedUrl = null;
|
|
2094
|
-
}
|
|
2095
|
-
if (parsedUrl) {
|
|
2096
|
-
const paramNames = ["country", "country_code", "countrycode", "cc", "geo", "region"];
|
|
2097
|
-
for (const param of paramNames) {
|
|
2098
|
-
const val = parsedUrl.searchParams.get(param);
|
|
2099
|
-
if (val) {
|
|
2100
|
-
const normalized = normalizeCountryCode(val);
|
|
2101
|
-
if (normalized) return normalized;
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
const localeVal = parsedUrl.searchParams.get("locale");
|
|
2105
|
-
if (localeVal) {
|
|
2106
|
-
const parts = localeVal.split(/[-_]/);
|
|
2107
|
-
const possibleCc = parts[parts.length - 1];
|
|
2108
|
-
const normalized = normalizeCountryCode(possibleCc);
|
|
2109
|
-
if (normalized) return normalized;
|
|
2110
|
-
}
|
|
2111
|
-
if (parsedUrl.username) {
|
|
2112
|
-
const username = decodeURIComponent(parsedUrl.username);
|
|
2113
|
-
const userPatterns = [
|
|
2114
|
-
/(?:^|[-_.:=])country[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=]|$)/i,
|
|
2115
|
-
/(?:^|[-_.:=])cc[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=]|$)/i,
|
|
2116
|
-
/(?:^|[-_.:=])geo[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=]|$)/i,
|
|
2117
|
-
/(?:^|[-_.:=])region[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=]|$)/i,
|
|
2118
|
-
/(?:^|[-_.:=])zone[-_][a-zA-Z0-9]+[-_]([a-zA-Z]{2,3})(?:[-_.:=]|$)/i
|
|
2119
|
-
];
|
|
2120
|
-
for (const pattern of userPatterns) {
|
|
2121
|
-
const match = username.match(pattern);
|
|
2122
|
-
if (match && match[1]) {
|
|
2123
|
-
const normalized = normalizeCountryCode(match[1]);
|
|
2124
|
-
if (normalized) return normalized;
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
const hostname = parsedUrl.hostname.toLowerCase();
|
|
2129
|
-
const hostParts = hostname.split(".");
|
|
2130
|
-
if (hostParts.length >= 3) {
|
|
2131
|
-
const firstPart = hostParts[0];
|
|
2132
|
-
const subParts = firstPart.split(/[-_]/);
|
|
2133
|
-
for (const part of subParts) {
|
|
2134
|
-
if (/^[a-zA-Z]{2}$/.test(part)) {
|
|
2135
|
-
const normalized = normalizeCountryCode(part);
|
|
2136
|
-
if (normalized && hasCountryConfig(normalized)) {
|
|
2137
|
-
return normalized;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
const rawPatterns = [
|
|
2144
|
-
/(?:^|[-_.:=?&])country[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=?&]|$)/i,
|
|
2145
|
-
/(?:^|[-_.:=?&])country_code=([a-zA-Z]{2,3})/i,
|
|
2146
|
-
/(?:^|[-_.:=?&])cc[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=?&]|$)/i,
|
|
2147
|
-
/(?:^|[-_.:=?&])geo[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=?&]|$)/i,
|
|
2148
|
-
/(?:^|[-_.:=?&])region[-_.:=]([a-zA-Z]{2,3})(?:[-_.:=?&]|$)/i
|
|
2149
|
-
];
|
|
2150
|
-
for (const pattern of rawPatterns) {
|
|
2151
|
-
const match = trimmed.match(pattern);
|
|
2152
|
-
if (match && match[1]) {
|
|
2153
|
-
const normalized = normalizeCountryCode(match[1]);
|
|
2154
|
-
if (normalized) return normalized;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
return null;
|
|
2158
|
-
}
|
|
2159
|
-
function detectCountry(input, options) {
|
|
2160
|
-
const fallback = options?.fallbackCountry && normalizeCountryCode(options.fallbackCountry) || DEFAULT_COUNTRY;
|
|
2161
|
-
if (!input || typeof input !== "string") {
|
|
2162
|
-
return fallback;
|
|
2163
|
-
}
|
|
2164
|
-
const trimmed = input.trim();
|
|
2165
|
-
if (!trimmed) {
|
|
2166
|
-
return fallback;
|
|
2167
|
-
}
|
|
2168
|
-
const directCode = normalizeCountryCode(trimmed);
|
|
2169
|
-
if (directCode && hasCountryConfig(directCode)) {
|
|
2170
|
-
return directCode;
|
|
2171
|
-
}
|
|
2172
|
-
const tzCountry = getCountryForTimezone(trimmed);
|
|
2173
|
-
if (tzCountry) {
|
|
2174
|
-
return tzCountry;
|
|
2175
|
-
}
|
|
2176
|
-
const proxyCountry = extractCountryFromProxyUrl(trimmed);
|
|
2177
|
-
if (proxyCountry && hasCountryConfig(proxyCountry)) {
|
|
2178
|
-
return proxyCountry;
|
|
2179
|
-
}
|
|
2180
|
-
return directCode || fallback;
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
function alignGeoContext(geoInput) {
|
|
2184
|
-
let countryInput;
|
|
2185
|
-
let proxyUrlInput;
|
|
2186
|
-
let explicitTimezone;
|
|
2187
|
-
let explicitLocale;
|
|
2188
|
-
let explicitLanguages;
|
|
2189
|
-
let explicitAcceptLanguage;
|
|
2190
|
-
let fallbackCountry = DEFAULT_COUNTRY;
|
|
2191
|
-
let explicitCoordinates;
|
|
2192
|
-
if (typeof geoInput === "string") {
|
|
2193
|
-
const trimmed = geoInput.trim();
|
|
2194
|
-
if (isValidTimezone(trimmed) && trimmed.includes("/")) {
|
|
2195
|
-
explicitTimezone = trimmed;
|
|
2196
|
-
countryInput = trimmed;
|
|
2197
|
-
} else if (trimmed.includes("://") || trimmed.includes("@") || trimmed.includes("?")) {
|
|
2198
|
-
proxyUrlInput = trimmed;
|
|
2199
|
-
} else {
|
|
2200
|
-
countryInput = trimmed;
|
|
2201
|
-
}
|
|
2202
|
-
} else if (geoInput && typeof geoInput === "object") {
|
|
2203
|
-
countryInput = geoInput.country;
|
|
2204
|
-
proxyUrlInput = geoInput.proxyUrl;
|
|
2205
|
-
explicitTimezone = geoInput.timezone;
|
|
2206
|
-
explicitLocale = geoInput.locale;
|
|
2207
|
-
explicitLanguages = geoInput.languages;
|
|
2208
|
-
explicitAcceptLanguage = geoInput.acceptLanguage;
|
|
2209
|
-
fallbackCountry = geoInput.fallbackCountry || DEFAULT_COUNTRY;
|
|
2210
|
-
explicitCoordinates = geoInput.coordinates;
|
|
2211
|
-
}
|
|
2212
|
-
const detectedTarget = proxyUrlInput || countryInput;
|
|
2213
|
-
const country = detectCountry(detectedTarget, { fallbackCountry });
|
|
2214
|
-
const config = getCountryConfig(country);
|
|
2215
|
-
let timezone;
|
|
2216
|
-
if (explicitTimezone && isValidTimezone(explicitTimezone)) {
|
|
2217
|
-
timezone = explicitTimezone;
|
|
2218
|
-
} else {
|
|
2219
|
-
timezone = config.defaultTimezone || getDefaultTimezoneForCountry(country);
|
|
2220
|
-
}
|
|
2221
|
-
const locale = explicitLocale || config.primaryLocale || DEFAULT_LOCALE;
|
|
2222
|
-
const languages = explicitLanguages && explicitLanguages.length > 0 ? explicitLanguages : [...config.languages];
|
|
2223
|
-
let acceptLanguage;
|
|
2224
|
-
if (explicitAcceptLanguage) {
|
|
2225
|
-
acceptLanguage = explicitAcceptLanguage;
|
|
2226
|
-
} else if (explicitLanguages && explicitLanguages.length > 0) {
|
|
2227
|
-
acceptLanguage = formatAcceptLanguage(explicitLanguages);
|
|
2228
|
-
} else {
|
|
2229
|
-
acceptLanguage = config.acceptLanguage || DEFAULT_ACCEPT_LANGUAGE;
|
|
2230
|
-
}
|
|
2231
|
-
const timezoneOffset = getTimezoneOffsetMinutes(timezone);
|
|
2232
|
-
const coordinates = explicitCoordinates || config.coordinates;
|
|
2233
|
-
const currency = config.currency || "USD";
|
|
2234
|
-
return {
|
|
2235
|
-
country,
|
|
2236
|
-
timezone,
|
|
2237
|
-
locale,
|
|
2238
|
-
acceptLanguage,
|
|
2239
|
-
languages,
|
|
2240
|
-
timezoneOffset,
|
|
2241
|
-
currency,
|
|
2242
|
-
coordinates,
|
|
2243
|
-
language: locale
|
|
2244
|
-
};
|
|
2245
|
-
}
|
|
2246
|
-
function isGeoAligned(context) {
|
|
2247
|
-
if (!context || !context.country) {
|
|
2248
|
-
return false;
|
|
2249
|
-
}
|
|
2250
|
-
const country = context.country.toUpperCase();
|
|
2251
|
-
if (context.timezone) {
|
|
2252
|
-
if (!isValidTimezone(context.timezone)) {
|
|
2253
|
-
return false;
|
|
2254
|
-
}
|
|
2255
|
-
if (!isTimezoneAlignedWithCountry(context.timezone, country)) {
|
|
2256
|
-
return false;
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
if (context.locale) {
|
|
2260
|
-
const config = getCountryConfig(country);
|
|
2261
|
-
const localeNorm = context.locale.toLowerCase();
|
|
2262
|
-
const configLocaleNorm = config.primaryLocale.toLowerCase();
|
|
2263
|
-
const countryNorm = country.toLowerCase();
|
|
2264
|
-
const matchesCountry = localeNorm.endsWith(`-${countryNorm}`) || localeNorm.endsWith(`_${countryNorm}`);
|
|
2265
|
-
const matchesPrimary = localeNorm === configLocaleNorm;
|
|
2266
|
-
const matchesLanguages = config.languages.some((l) => l.toLowerCase() === localeNorm);
|
|
2267
|
-
if (!matchesCountry && !matchesPrimary && !matchesLanguages) {
|
|
2268
|
-
return false;
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
return true;
|
|
2272
|
-
}
|
|
2273
|
-
function toLocaleProfile(geo) {
|
|
2274
|
-
return {
|
|
2275
|
-
language: geo.locale,
|
|
2276
|
-
languages: [...geo.languages],
|
|
2277
|
-
timezone: geo.timezone,
|
|
2278
|
-
timezoneOffset: geo.timezoneOffset,
|
|
2279
|
-
country: geo.country,
|
|
2280
|
-
currency: geo.currency
|
|
2281
|
-
};
|
|
2282
|
-
}
|
|
2283
|
-
function toGeoLocation(geo) {
|
|
2284
|
-
return {
|
|
2285
|
-
latitude: geo.coordinates.latitude,
|
|
2286
|
-
longitude: geo.coordinates.longitude,
|
|
2287
|
-
accuracy: geo.coordinates.accuracy ?? 1e3,
|
|
2288
|
-
country: geo.country,
|
|
2289
|
-
countryCode: geo.country,
|
|
2290
|
-
timezone: geo.timezone,
|
|
2291
|
-
locale: geo.locale,
|
|
2292
|
-
languages: [...geo.languages]
|
|
2293
|
-
};
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
const MACOS_PRESETS = [
|
|
2297
|
-
{
|
|
2298
|
-
id: "mac-m3-pro-mbp14",
|
|
2299
|
-
name: 'MacBook Pro 14" (Apple M3 Pro)',
|
|
2300
|
-
deviceType: "laptop",
|
|
2301
|
-
os: {
|
|
2302
|
-
name: "macOS",
|
|
2303
|
-
platform: "macOS",
|
|
2304
|
-
version: "15.0.0",
|
|
2305
|
-
// macOS Sequoia
|
|
2306
|
-
release: "Sequoia",
|
|
2307
|
-
architecture: "arm"
|
|
2308
|
-
},
|
|
2309
|
-
hardware: {
|
|
2310
|
-
screen: {
|
|
2311
|
-
width: 1512,
|
|
2312
|
-
height: 982,
|
|
2313
|
-
availWidth: 1512,
|
|
2314
|
-
availHeight: 945,
|
|
2315
|
-
// Top notch & menu bar
|
|
2316
|
-
dpr: 2,
|
|
2317
|
-
colorDepth: 30,
|
|
2318
|
-
pixelDepth: 30
|
|
2319
|
-
},
|
|
2320
|
-
cpu: {
|
|
2321
|
-
architecture: "arm",
|
|
2322
|
-
concurrency: 12
|
|
2323
|
-
},
|
|
2324
|
-
memory: {
|
|
2325
|
-
deviceMemory: 8,
|
|
2326
|
-
physicalMemoryGB: 18
|
|
2327
|
-
},
|
|
2328
|
-
gpu: {
|
|
2329
|
-
vendor: "Google Inc. (Apple)",
|
|
2330
|
-
renderer: "ANGLE (Apple, Apple M3 Pro, OpenGL 4.1)",
|
|
2331
|
-
webglVendor: "WebKit",
|
|
2332
|
-
webglRenderer: "WebKit WebGL"
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
},
|
|
2336
|
-
{
|
|
2337
|
-
id: "mac-m2-air13",
|
|
2338
|
-
name: 'MacBook Air 13" (Apple M2)',
|
|
2339
|
-
deviceType: "laptop",
|
|
2340
|
-
os: {
|
|
2341
|
-
name: "macOS",
|
|
2342
|
-
platform: "macOS",
|
|
2343
|
-
version: "14.6.1",
|
|
2344
|
-
// macOS Sonoma
|
|
2345
|
-
release: "Sonoma",
|
|
2346
|
-
architecture: "arm"
|
|
2347
|
-
},
|
|
2348
|
-
hardware: {
|
|
2349
|
-
screen: {
|
|
2350
|
-
width: 1440,
|
|
2351
|
-
height: 900,
|
|
2352
|
-
availWidth: 1440,
|
|
2353
|
-
availHeight: 875,
|
|
2354
|
-
dpr: 2,
|
|
2355
|
-
colorDepth: 24,
|
|
2356
|
-
pixelDepth: 24
|
|
2357
|
-
},
|
|
2358
|
-
cpu: {
|
|
2359
|
-
architecture: "arm",
|
|
2360
|
-
concurrency: 8
|
|
2361
|
-
},
|
|
2362
|
-
memory: {
|
|
2363
|
-
deviceMemory: 8,
|
|
2364
|
-
physicalMemoryGB: 16
|
|
2365
|
-
},
|
|
2366
|
-
gpu: {
|
|
2367
|
-
vendor: "Google Inc. (Apple)",
|
|
2368
|
-
renderer: "ANGLE (Apple, Apple M2, OpenGL 4.1)",
|
|
2369
|
-
webglVendor: "WebKit",
|
|
2370
|
-
webglRenderer: "WebKit WebGL"
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
},
|
|
2374
|
-
{
|
|
2375
|
-
id: "mac-m2-max-mbp16",
|
|
2376
|
-
name: 'MacBook Pro 16" (Apple M2 Max)',
|
|
2377
|
-
deviceType: "laptop",
|
|
2378
|
-
os: {
|
|
2379
|
-
name: "macOS",
|
|
2380
|
-
platform: "macOS",
|
|
2381
|
-
version: "14.6.1",
|
|
2382
|
-
release: "Sonoma",
|
|
2383
|
-
architecture: "arm"
|
|
2384
|
-
},
|
|
2385
|
-
hardware: {
|
|
2386
|
-
screen: {
|
|
2387
|
-
width: 1728,
|
|
2388
|
-
height: 1117,
|
|
2389
|
-
availWidth: 1728,
|
|
2390
|
-
availHeight: 1080,
|
|
2391
|
-
dpr: 2,
|
|
2392
|
-
colorDepth: 30,
|
|
2393
|
-
pixelDepth: 30
|
|
2394
|
-
},
|
|
2395
|
-
cpu: {
|
|
2396
|
-
architecture: "arm",
|
|
2397
|
-
concurrency: 12
|
|
2398
|
-
},
|
|
2399
|
-
memory: {
|
|
2400
|
-
deviceMemory: 8,
|
|
2401
|
-
physicalMemoryGB: 32
|
|
2402
|
-
},
|
|
2403
|
-
gpu: {
|
|
2404
|
-
vendor: "Google Inc. (Apple)",
|
|
2405
|
-
renderer: "ANGLE (Apple, Apple M2 Max, OpenGL 4.1)",
|
|
2406
|
-
webglVendor: "WebKit",
|
|
2407
|
-
webglRenderer: "WebKit WebGL"
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
},
|
|
2411
|
-
{
|
|
2412
|
-
id: "mac-m3-imac24",
|
|
2413
|
-
name: 'iMac 24" (Apple M3)',
|
|
2414
|
-
deviceType: "desktop",
|
|
2415
|
-
os: {
|
|
2416
|
-
name: "macOS",
|
|
2417
|
-
platform: "macOS",
|
|
2418
|
-
version: "15.0.0",
|
|
2419
|
-
release: "Sequoia",
|
|
2420
|
-
architecture: "arm"
|
|
2421
|
-
},
|
|
2422
|
-
hardware: {
|
|
2423
|
-
screen: {
|
|
2424
|
-
width: 2240,
|
|
2425
|
-
height: 1260,
|
|
2426
|
-
availWidth: 2240,
|
|
2427
|
-
availHeight: 1235,
|
|
2428
|
-
dpr: 2,
|
|
2429
|
-
colorDepth: 30,
|
|
2430
|
-
pixelDepth: 30
|
|
2431
|
-
},
|
|
2432
|
-
cpu: {
|
|
2433
|
-
architecture: "arm",
|
|
2434
|
-
concurrency: 8
|
|
2435
|
-
},
|
|
2436
|
-
memory: {
|
|
2437
|
-
deviceMemory: 8,
|
|
2438
|
-
physicalMemoryGB: 16
|
|
2439
|
-
},
|
|
2440
|
-
gpu: {
|
|
2441
|
-
vendor: "Google Inc. (Apple)",
|
|
2442
|
-
renderer: "ANGLE (Apple, Apple M3, OpenGL 4.1)",
|
|
2443
|
-
webglVendor: "WebKit",
|
|
2444
|
-
webglRenderer: "WebKit WebGL"
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
},
|
|
2448
|
-
{
|
|
2449
|
-
id: "mac-m2-studio-display",
|
|
2450
|
-
name: "Mac Studio (Apple M2 Max, Studio Display)",
|
|
2451
|
-
deviceType: "desktop",
|
|
2452
|
-
os: {
|
|
2453
|
-
name: "macOS",
|
|
2454
|
-
platform: "macOS",
|
|
2455
|
-
version: "15.0.0",
|
|
2456
|
-
release: "Sequoia",
|
|
2457
|
-
architecture: "arm"
|
|
2458
|
-
},
|
|
2459
|
-
hardware: {
|
|
2460
|
-
screen: {
|
|
2461
|
-
width: 2560,
|
|
2462
|
-
height: 1440,
|
|
2463
|
-
availWidth: 2560,
|
|
2464
|
-
availHeight: 1415,
|
|
2465
|
-
dpr: 2,
|
|
2466
|
-
colorDepth: 30,
|
|
2467
|
-
pixelDepth: 30
|
|
2468
|
-
},
|
|
2469
|
-
cpu: {
|
|
2470
|
-
architecture: "arm",
|
|
2471
|
-
concurrency: 12
|
|
2472
|
-
},
|
|
2473
|
-
memory: {
|
|
2474
|
-
deviceMemory: 8,
|
|
2475
|
-
physicalMemoryGB: 32
|
|
2476
|
-
},
|
|
2477
|
-
gpu: {
|
|
2478
|
-
vendor: "Google Inc. (Apple)",
|
|
2479
|
-
renderer: "ANGLE (Apple, Apple M2 Max, OpenGL 4.1)",
|
|
2480
|
-
webglVendor: "WebKit",
|
|
2481
|
-
webglRenderer: "WebKit WebGL"
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
},
|
|
2485
|
-
{
|
|
2486
|
-
id: "mac-intel-mbp13",
|
|
2487
|
-
name: 'MacBook Pro 13" (Intel Core i5)',
|
|
2488
|
-
deviceType: "laptop",
|
|
2489
|
-
os: {
|
|
2490
|
-
name: "macOS",
|
|
2491
|
-
platform: "macOS",
|
|
2492
|
-
version: "14.6.1",
|
|
2493
|
-
release: "Sonoma",
|
|
2494
|
-
architecture: "x86"
|
|
2495
|
-
},
|
|
2496
|
-
hardware: {
|
|
2497
|
-
screen: {
|
|
2498
|
-
width: 1440,
|
|
2499
|
-
height: 900,
|
|
2500
|
-
availWidth: 1440,
|
|
2501
|
-
availHeight: 875,
|
|
2502
|
-
dpr: 2,
|
|
2503
|
-
colorDepth: 24,
|
|
2504
|
-
pixelDepth: 24
|
|
2505
|
-
},
|
|
2506
|
-
cpu: {
|
|
2507
|
-
architecture: "x86",
|
|
2508
|
-
concurrency: 8
|
|
2509
|
-
},
|
|
2510
|
-
memory: {
|
|
2511
|
-
deviceMemory: 8,
|
|
2512
|
-
physicalMemoryGB: 16
|
|
2513
|
-
},
|
|
2514
|
-
gpu: {
|
|
2515
|
-
vendor: "Google Inc. (Intel)",
|
|
2516
|
-
renderer: "ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 645, OpenGL 4.1)",
|
|
2517
|
-
webglVendor: "WebKit",
|
|
2518
|
-
webglRenderer: "WebKit WebGL"
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
];
|
|
2523
|
-
const WINDOWS_PRESETS = [
|
|
2524
|
-
{
|
|
2525
|
-
id: "win11-rtx3060-desktop",
|
|
2526
|
-
name: "Windows 11 Desktop (NVIDIA RTX 3060)",
|
|
2527
|
-
deviceType: "desktop",
|
|
2528
|
-
os: {
|
|
2529
|
-
name: "Windows",
|
|
2530
|
-
platform: "Windows",
|
|
2531
|
-
version: "15.0.0",
|
|
2532
|
-
// Windows 11
|
|
2533
|
-
release: "Windows 11",
|
|
2534
|
-
architecture: "x86"
|
|
2535
|
-
},
|
|
2536
|
-
hardware: {
|
|
2537
|
-
screen: {
|
|
2538
|
-
width: 1920,
|
|
2539
|
-
height: 1080,
|
|
2540
|
-
availWidth: 1920,
|
|
2541
|
-
availHeight: 1040,
|
|
2542
|
-
// 40px taskbar
|
|
2543
|
-
dpr: 1,
|
|
2544
|
-
colorDepth: 24,
|
|
2545
|
-
pixelDepth: 24
|
|
2546
|
-
},
|
|
2547
|
-
cpu: {
|
|
2548
|
-
architecture: "x86",
|
|
2549
|
-
concurrency: 12
|
|
2550
|
-
},
|
|
2551
|
-
memory: {
|
|
2552
|
-
deviceMemory: 8,
|
|
2553
|
-
physicalMemoryGB: 16
|
|
2554
|
-
},
|
|
2555
|
-
gpu: {
|
|
2556
|
-
vendor: "Google Inc. (NVIDIA)",
|
|
2557
|
-
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11)",
|
|
2558
|
-
webglVendor: "WebKit",
|
|
2559
|
-
webglRenderer: "WebKit WebGL"
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
},
|
|
2563
|
-
{
|
|
2564
|
-
id: "win11-rtx4070-laptop",
|
|
2565
|
-
name: "Windows 11 Laptop (NVIDIA RTX 4070, 2K 125%)",
|
|
2566
|
-
deviceType: "laptop",
|
|
2567
|
-
os: {
|
|
2568
|
-
name: "Windows",
|
|
2569
|
-
platform: "Windows",
|
|
2570
|
-
version: "15.0.0",
|
|
2571
|
-
release: "Windows 11",
|
|
2572
|
-
architecture: "x86"
|
|
2573
|
-
},
|
|
2574
|
-
hardware: {
|
|
2575
|
-
screen: {
|
|
2576
|
-
width: 2560,
|
|
2577
|
-
height: 1440,
|
|
2578
|
-
availWidth: 2560,
|
|
2579
|
-
availHeight: 1400,
|
|
2580
|
-
dpr: 1.25,
|
|
2581
|
-
colorDepth: 24,
|
|
2582
|
-
pixelDepth: 24
|
|
2583
|
-
},
|
|
2584
|
-
cpu: {
|
|
2585
|
-
architecture: "x86",
|
|
2586
|
-
concurrency: 16
|
|
2587
|
-
},
|
|
2588
|
-
memory: {
|
|
2589
|
-
deviceMemory: 8,
|
|
2590
|
-
physicalMemoryGB: 32
|
|
2591
|
-
},
|
|
2592
|
-
gpu: {
|
|
2593
|
-
vendor: "Google Inc. (NVIDIA)",
|
|
2594
|
-
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11)",
|
|
2595
|
-
webglVendor: "WebKit",
|
|
2596
|
-
webglRenderer: "WebKit WebGL"
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
},
|
|
2600
|
-
{
|
|
2601
|
-
id: "win10-gtx1660s-desktop",
|
|
2602
|
-
name: "Windows 10 Desktop (NVIDIA GTX 1660 SUPER)",
|
|
2603
|
-
deviceType: "desktop",
|
|
2604
|
-
os: {
|
|
2605
|
-
name: "Windows",
|
|
2606
|
-
platform: "Windows",
|
|
2607
|
-
version: "10.0.0",
|
|
2608
|
-
// Windows 10
|
|
2609
|
-
release: "Windows 10",
|
|
2610
|
-
architecture: "x86"
|
|
2611
|
-
},
|
|
2612
|
-
hardware: {
|
|
2613
|
-
screen: {
|
|
2614
|
-
width: 1920,
|
|
2615
|
-
height: 1080,
|
|
2616
|
-
availWidth: 1920,
|
|
2617
|
-
availHeight: 1040,
|
|
2618
|
-
dpr: 1,
|
|
2619
|
-
colorDepth: 24,
|
|
2620
|
-
pixelDepth: 24
|
|
2621
|
-
},
|
|
2622
|
-
cpu: {
|
|
2623
|
-
architecture: "x86",
|
|
2624
|
-
concurrency: 8
|
|
2625
|
-
},
|
|
2626
|
-
memory: {
|
|
2627
|
-
deviceMemory: 8,
|
|
2628
|
-
physicalMemoryGB: 16
|
|
2629
|
-
},
|
|
2630
|
-
gpu: {
|
|
2631
|
-
vendor: "Google Inc. (NVIDIA)",
|
|
2632
|
-
renderer: "ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11)",
|
|
2633
|
-
webglVendor: "WebKit",
|
|
2634
|
-
webglRenderer: "WebKit WebGL"
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
},
|
|
2638
|
-
{
|
|
2639
|
-
id: "win10-rx6700xt-desktop",
|
|
2640
|
-
name: "Windows 10 Workstation (AMD Radeon RX 6700 XT)",
|
|
2641
|
-
deviceType: "desktop",
|
|
2642
|
-
os: {
|
|
2643
|
-
name: "Windows",
|
|
2644
|
-
platform: "Windows",
|
|
2645
|
-
version: "10.0.0",
|
|
2646
|
-
release: "Windows 10",
|
|
2647
|
-
architecture: "x86"
|
|
2648
|
-
},
|
|
2649
|
-
hardware: {
|
|
2650
|
-
screen: {
|
|
2651
|
-
width: 2560,
|
|
2652
|
-
height: 1440,
|
|
2653
|
-
availWidth: 2560,
|
|
2654
|
-
availHeight: 1400,
|
|
2655
|
-
dpr: 1,
|
|
2656
|
-
colorDepth: 24,
|
|
2657
|
-
pixelDepth: 24
|
|
2658
|
-
},
|
|
2659
|
-
cpu: {
|
|
2660
|
-
architecture: "x86",
|
|
2661
|
-
concurrency: 16
|
|
2662
|
-
},
|
|
2663
|
-
memory: {
|
|
2664
|
-
deviceMemory: 8,
|
|
2665
|
-
physicalMemoryGB: 32
|
|
2666
|
-
},
|
|
2667
|
-
gpu: {
|
|
2668
|
-
vendor: "Google Inc. (AMD)",
|
|
2669
|
-
renderer: "ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11)",
|
|
2670
|
-
webglVendor: "WebKit",
|
|
2671
|
-
webglRenderer: "WebKit WebGL"
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
},
|
|
2675
|
-
{
|
|
2676
|
-
id: "win11-iris-xe-laptop",
|
|
2677
|
-
name: "Windows 11 Ultrabook (Intel Iris Xe)",
|
|
2678
|
-
deviceType: "laptop",
|
|
2679
|
-
os: {
|
|
2680
|
-
name: "Windows",
|
|
2681
|
-
platform: "Windows",
|
|
2682
|
-
version: "15.0.0",
|
|
2683
|
-
release: "Windows 11",
|
|
2684
|
-
architecture: "x86"
|
|
2685
|
-
},
|
|
2686
|
-
hardware: {
|
|
2687
|
-
screen: {
|
|
2688
|
-
width: 1920,
|
|
2689
|
-
height: 1080,
|
|
2690
|
-
availWidth: 1920,
|
|
2691
|
-
availHeight: 1032,
|
|
2692
|
-
dpr: 1.25,
|
|
2693
|
-
colorDepth: 24,
|
|
2694
|
-
pixelDepth: 24
|
|
2695
|
-
},
|
|
2696
|
-
cpu: {
|
|
2697
|
-
architecture: "x86",
|
|
2698
|
-
concurrency: 8
|
|
2699
|
-
},
|
|
2700
|
-
memory: {
|
|
2701
|
-
deviceMemory: 8,
|
|
2702
|
-
physicalMemoryGB: 16
|
|
2703
|
-
},
|
|
2704
|
-
gpu: {
|
|
2705
|
-
vendor: "Google Inc. (Intel)",
|
|
2706
|
-
renderer: "ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11)",
|
|
2707
|
-
webglVendor: "WebKit",
|
|
2708
|
-
webglRenderer: "WebKit WebGL"
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
},
|
|
2712
|
-
{
|
|
2713
|
-
id: "win11-rtx4080-4k",
|
|
2714
|
-
name: "Windows 11 High-End 4K (NVIDIA RTX 4080)",
|
|
2715
|
-
deviceType: "desktop",
|
|
2716
|
-
os: {
|
|
2717
|
-
name: "Windows",
|
|
2718
|
-
platform: "Windows",
|
|
2719
|
-
version: "15.0.0",
|
|
2720
|
-
release: "Windows 11",
|
|
2721
|
-
architecture: "x86"
|
|
2722
|
-
},
|
|
2723
|
-
hardware: {
|
|
2724
|
-
screen: {
|
|
2725
|
-
width: 3840,
|
|
2726
|
-
height: 2160,
|
|
2727
|
-
availWidth: 3840,
|
|
2728
|
-
availHeight: 2112,
|
|
2729
|
-
dpr: 1.5,
|
|
2730
|
-
colorDepth: 24,
|
|
2731
|
-
pixelDepth: 24
|
|
2732
|
-
},
|
|
2733
|
-
cpu: {
|
|
2734
|
-
architecture: "x86",
|
|
2735
|
-
concurrency: 24
|
|
2736
|
-
},
|
|
2737
|
-
memory: {
|
|
2738
|
-
deviceMemory: 8,
|
|
2739
|
-
physicalMemoryGB: 32
|
|
2740
|
-
},
|
|
2741
|
-
gpu: {
|
|
2742
|
-
vendor: "Google Inc. (NVIDIA)",
|
|
2743
|
-
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 Direct3D11)",
|
|
2744
|
-
webglVendor: "WebKit",
|
|
2745
|
-
webglRenderer: "WebKit WebGL"
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
];
|
|
2750
|
-
const ALL_PRESETS = [
|
|
2751
|
-
...MACOS_PRESETS,
|
|
2752
|
-
...WINDOWS_PRESETS
|
|
2753
|
-
];
|
|
2754
|
-
function getHardwarePresets(filters) {
|
|
2755
|
-
let list = ALL_PRESETS;
|
|
2756
|
-
if (!filters) return list;
|
|
2757
|
-
if (filters.platform) {
|
|
2758
|
-
list = list.filter((p) => p.os.platform === filters.platform);
|
|
2759
|
-
}
|
|
2760
|
-
if (filters.architecture) {
|
|
2761
|
-
list = list.filter((p) => p.os.architecture === filters.architecture);
|
|
2762
|
-
}
|
|
2763
|
-
if (filters.deviceType) {
|
|
2764
|
-
list = list.filter((p) => p.deviceType === filters.deviceType);
|
|
2765
|
-
}
|
|
2766
|
-
return list;
|
|
2767
|
-
}
|
|
2768
|
-
function validateHardwareContradictions(os, hardware) {
|
|
2769
|
-
const errors = [];
|
|
2770
|
-
if (os.platform === "macOS") {
|
|
2771
|
-
if (hardware.gpu.renderer.includes("Direct3D")) {
|
|
2772
|
-
errors.push("macOS cannot use Direct3D graphics backend");
|
|
2773
|
-
}
|
|
2774
|
-
if (os.architecture === "arm") {
|
|
2775
|
-
if (!hardware.gpu.renderer.includes("Apple M")) {
|
|
2776
|
-
errors.push(`ARM macOS must use Apple Silicon M-series GPU, got: ${hardware.gpu.renderer}`);
|
|
2777
|
-
}
|
|
2778
|
-
if (hardware.gpu.renderer.includes("NVIDIA") || hardware.gpu.renderer.includes("AMD")) {
|
|
2779
|
-
errors.push("Apple Silicon Mac cannot have discrete NVIDIA or AMD GPU");
|
|
2780
|
-
}
|
|
2781
|
-
} else if (os.architecture === "x86") {
|
|
2782
|
-
if (hardware.gpu.renderer.includes("Apple M")) {
|
|
2783
|
-
errors.push("Intel x86 Mac cannot have Apple Silicon M-series GPU");
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
if (hardware.screen.dpr !== 2) {
|
|
2787
|
-
errors.push(`Modern macOS profiles must be Retina (dpr: 2), got: ${hardware.screen.dpr}`);
|
|
2788
|
-
}
|
|
2789
|
-
} else if (os.platform === "Windows") {
|
|
2790
|
-
if (hardware.gpu.renderer.includes("Apple M")) {
|
|
2791
|
-
errors.push("Windows cannot use Apple Silicon GPU");
|
|
2792
|
-
}
|
|
2793
|
-
if (os.architecture === "x86" && !hardware.gpu.renderer.includes("Direct3D")) {
|
|
2794
|
-
errors.push("Windows x86 ANGLE backend should use Direct3D11");
|
|
2795
|
-
}
|
|
2796
|
-
if (os.version !== "15.0.0" && os.version !== "10.0.0") {
|
|
2797
|
-
errors.push(`Windows platform version must be 15.0.0 (Win11) or 10.0.0 (Win10), got: ${os.version}`);
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
if (hardware.screen.availWidth > hardware.screen.width) {
|
|
2801
|
-
errors.push("screen.availWidth cannot be greater than screen.width");
|
|
2802
|
-
}
|
|
2803
|
-
if (hardware.screen.availHeight > hardware.screen.height) {
|
|
2804
|
-
errors.push("screen.availHeight cannot be greater than screen.height");
|
|
2805
|
-
}
|
|
2806
|
-
if (hardware.screen.width <= 0 || hardware.screen.height <= 0) {
|
|
2807
|
-
errors.push("Screen dimensions must be strictly positive");
|
|
2808
|
-
}
|
|
2809
|
-
if (hardware.cpu.concurrency < 2 || hardware.cpu.concurrency > 64) {
|
|
2810
|
-
errors.push(`Unrealistic hardware concurrency: ${hardware.cpu.concurrency}`);
|
|
2811
|
-
}
|
|
2812
|
-
if (hardware.cpu.architecture !== os.architecture) {
|
|
2813
|
-
errors.push(
|
|
2814
|
-
`CPU architecture (${hardware.cpu.architecture}) does not match OS architecture (${os.architecture})`
|
|
2815
|
-
);
|
|
2816
|
-
}
|
|
2817
|
-
if (hardware.memory.deviceMemory <= 0 || hardware.memory.deviceMemory > 128) {
|
|
2818
|
-
errors.push(`Invalid deviceMemory: ${hardware.memory.deviceMemory}`);
|
|
2819
|
-
}
|
|
2820
|
-
return errors;
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
|
-
function murmur3(key, seed = 0) {
|
|
2824
|
-
let h1 = seed >>> 0;
|
|
2825
|
-
const c1 = 3432918353;
|
|
2826
|
-
const c2 = 461845907;
|
|
2827
|
-
const len = key.length;
|
|
2828
|
-
for (let i = 0; i < len; i++) {
|
|
2829
|
-
let k1 = key.charCodeAt(i);
|
|
2830
|
-
k1 = Math.imul(k1, c1);
|
|
2831
|
-
k1 = k1 << 15 | k1 >>> 17;
|
|
2832
|
-
k1 = Math.imul(k1, c2);
|
|
2833
|
-
h1 ^= k1;
|
|
2834
|
-
h1 = h1 << 13 | h1 >>> 19;
|
|
2835
|
-
h1 = Math.imul(h1, 5) + 3864292196 >>> 0;
|
|
2836
|
-
}
|
|
2837
|
-
h1 ^= len;
|
|
2838
|
-
h1 ^= h1 >>> 16;
|
|
2839
|
-
h1 = Math.imul(h1, 2246822507);
|
|
2840
|
-
h1 ^= h1 >>> 13;
|
|
2841
|
-
h1 = Math.imul(h1, 3266489909);
|
|
2842
|
-
h1 ^= h1 >>> 16;
|
|
2843
|
-
return h1 >>> 0;
|
|
2844
|
-
}
|
|
2845
|
-
function fnv1a(key) {
|
|
2846
|
-
let hash = 2166136261;
|
|
2847
|
-
for (let i = 0; i < key.length; i++) {
|
|
2848
|
-
hash ^= key.charCodeAt(i);
|
|
2849
|
-
hash = Math.imul(hash, 16777619);
|
|
2850
|
-
}
|
|
2851
|
-
return hash >>> 0;
|
|
2852
|
-
}
|
|
2853
|
-
function createMulberry32(seed) {
|
|
2854
|
-
let s = seed >>> 0;
|
|
2855
|
-
return function() {
|
|
2856
|
-
s = s + 1831565813 >>> 0;
|
|
2857
|
-
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
2858
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2859
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
function createDeterministicPRNG(seed) {
|
|
2863
|
-
const numericSeed = typeof seed === "number" ? seed >>> 0 : murmur3(String(seed));
|
|
2864
|
-
return createMulberry32(numericSeed);
|
|
2865
|
-
}
|
|
2866
|
-
const CHROME_VERSIONS = [
|
|
2867
|
-
{ major: 131, full: "131.0.6778.86" },
|
|
2868
|
-
{ major: 130, full: "130.0.6723.116" },
|
|
2869
|
-
{ major: 128, full: "128.0.6613.137" },
|
|
2870
|
-
{ major: 126, full: "126.0.6478.182" },
|
|
2871
|
-
{ major: 124, full: "124.0.6367.208" }
|
|
2872
|
-
];
|
|
2873
|
-
const EDGE_VERSIONS = [
|
|
2874
|
-
{ major: 131, full: "131.0.2903.70" },
|
|
2875
|
-
{ major: 130, full: "130.0.2849.68" },
|
|
2876
|
-
{ major: 128, full: "128.0.2739.67" },
|
|
2877
|
-
{ major: 126, full: "126.0.2592.87" }
|
|
2878
|
-
];
|
|
2879
|
-
function buildUserAgent(browserName, fullVersion, os) {
|
|
2880
|
-
if (os.platform === "macOS") {
|
|
2881
|
-
if (browserName === "Edge") {
|
|
2882
|
-
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}`;
|
|
2883
|
-
}
|
|
2884
|
-
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36`;
|
|
2885
|
-
}
|
|
2886
|
-
if (browserName === "Edge") {
|
|
2887
|
-
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36 Edg/${fullVersion}`;
|
|
2888
|
-
}
|
|
2889
|
-
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${fullVersion} Safari/537.36`;
|
|
2890
|
-
}
|
|
2891
|
-
function getStaticFallbackHeaders(userAgent, clientHintsHeaders, acceptLanguage) {
|
|
2892
|
-
return {
|
|
2893
|
-
...clientHintsHeaders,
|
|
2894
|
-
"user-agent": userAgent,
|
|
2895
|
-
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",
|
|
2896
|
-
"accept-encoding": "gzip, deflate, br, zstd",
|
|
2897
|
-
"accept-language": acceptLanguage,
|
|
2898
|
-
"upgrade-insecure-requests": "1",
|
|
2899
|
-
"sec-fetch-dest": "document",
|
|
2900
|
-
"sec-fetch-mode": "navigate",
|
|
2901
|
-
"sec-fetch-site": "none",
|
|
2902
|
-
"sec-fetch-user": "?1",
|
|
2903
|
-
priority: "u=0, i"
|
|
2904
|
-
};
|
|
2905
|
-
}
|
|
2906
|
-
function getApifyHeadersIfAvailable(browserName, os, userAgent, clientHintsHeaders, acceptLanguage) {
|
|
2907
|
-
try {
|
|
2908
|
-
const hgModule = require("header-generator");
|
|
2909
|
-
const HeaderGenerator = hgModule.HeaderGenerator;
|
|
2910
|
-
if (HeaderGenerator) {
|
|
2911
|
-
const generator = new HeaderGenerator({
|
|
2912
|
-
browsers: [{ name: browserName === "Edge" ? "edge" : "chrome" }],
|
|
2913
|
-
operatingSystems: [os.platform === "macOS" ? "macos" : "windows"]
|
|
2914
|
-
});
|
|
2915
|
-
const generated = generator.getHeaders();
|
|
2916
|
-
return {
|
|
2917
|
-
...generated,
|
|
2918
|
-
...clientHintsHeaders,
|
|
2919
|
-
"user-agent": userAgent,
|
|
2920
|
-
"accept-language": acceptLanguage
|
|
2921
|
-
};
|
|
2922
|
-
}
|
|
2923
|
-
} catch {
|
|
2924
|
-
}
|
|
2925
|
-
return getStaticFallbackHeaders(userAgent, clientHintsHeaders, acceptLanguage);
|
|
2926
|
-
}
|
|
2927
|
-
function generatePassport(options = {}) {
|
|
2928
|
-
const seedString = options.seed !== void 0 ? String(options.seed) : generateRandomSeed();
|
|
2929
|
-
const prng = createDeterministicPRNG(seedString);
|
|
2930
|
-
const presets = getHardwarePresets({
|
|
2931
|
-
platform: options.os
|
|
2932
|
-
});
|
|
2933
|
-
const presetIndex = Math.floor(prng() * presets.length);
|
|
2934
|
-
const preset = presets[presetIndex] ?? ALL_PRESETS[0];
|
|
2935
|
-
const os = { ...preset.os };
|
|
2936
|
-
const hardware = {
|
|
2937
|
-
screen: { ...preset.hardware.screen },
|
|
2938
|
-
cpu: { ...preset.hardware.cpu },
|
|
2939
|
-
memory: { ...preset.hardware.memory },
|
|
2940
|
-
gpu: { ...preset.hardware.gpu }
|
|
2941
|
-
};
|
|
2942
|
-
const browserName = options.browser ?? (prng() > 0.85 ? "Edge" : "Chrome");
|
|
2943
|
-
const versionPool = browserName === "Edge" ? EDGE_VERSIONS : CHROME_VERSIONS;
|
|
2944
|
-
const versionIndex = Math.floor(prng() * versionPool.length);
|
|
2945
|
-
const selectedVersion = versionPool[versionIndex];
|
|
2946
|
-
const userAgent = buildUserAgent(browserName, selectedVersion.full, os);
|
|
2947
|
-
const browser = {
|
|
2948
|
-
name: browserName,
|
|
2949
|
-
version: selectedVersion.full,
|
|
2950
|
-
majorVersion: selectedVersion.major,
|
|
2951
|
-
userAgent
|
|
2952
|
-
};
|
|
2953
|
-
const clientHints = generateUserAgentClientHints({
|
|
2954
|
-
browserName,
|
|
2955
|
-
fullVersion: selectedVersion.full,
|
|
2956
|
-
majorVersion: selectedVersion.major,
|
|
2957
|
-
os,
|
|
2958
|
-
prng
|
|
2959
|
-
});
|
|
2960
|
-
const geoContext = alignGeoContext({
|
|
2961
|
-
country: options.geo || options.country,
|
|
2962
|
-
proxyUrl: options.proxy || options.proxyUrl,
|
|
2963
|
-
timezone: options.timezone,
|
|
2964
|
-
locale: options.locale
|
|
2965
|
-
});
|
|
2966
|
-
const locale = {
|
|
2967
|
-
language: geoContext.locale,
|
|
2968
|
-
languages: geoContext.languages,
|
|
2969
|
-
timezone: geoContext.timezone,
|
|
2970
|
-
timezoneOffset: geoContext.timezoneOffset,
|
|
2971
|
-
country: geoContext.country,
|
|
2972
|
-
currency: geoContext.currency,
|
|
2973
|
-
acceptLanguage: geoContext.acceptLanguage
|
|
2974
|
-
};
|
|
2975
|
-
const acceptLanguage = geoContext.acceptLanguage;
|
|
2976
|
-
let headers;
|
|
2977
|
-
if (options.useHeaderGenerator) {
|
|
2978
|
-
headers = getApifyHeadersIfAvailable(
|
|
2979
|
-
browserName,
|
|
2980
|
-
os,
|
|
2981
|
-
userAgent,
|
|
2982
|
-
clientHints.headers,
|
|
2983
|
-
acceptLanguage
|
|
2984
|
-
);
|
|
2985
|
-
} else {
|
|
2986
|
-
headers = getStaticFallbackHeaders(
|
|
2987
|
-
userAgent,
|
|
2988
|
-
clientHints.headers,
|
|
2989
|
-
acceptLanguage
|
|
2990
|
-
);
|
|
2991
|
-
}
|
|
2992
|
-
const context = {
|
|
2993
|
-
viewport: {
|
|
2994
|
-
width: hardware.screen.availWidth,
|
|
2995
|
-
height: hardware.screen.availHeight
|
|
2996
|
-
},
|
|
2997
|
-
userAgent,
|
|
2998
|
-
locale: locale.language,
|
|
2999
|
-
timezoneId: locale.timezone,
|
|
3000
|
-
extraHTTPHeaders: headers,
|
|
3001
|
-
deviceScaleFactor: hardware.screen.dpr,
|
|
3002
|
-
hasTouch: false,
|
|
3003
|
-
isMobile: false,
|
|
3004
|
-
colorScheme: "light"
|
|
3005
|
-
};
|
|
3006
|
-
const contradictions = validateHardwareContradictions(os, hardware);
|
|
3007
|
-
const evaluation = {
|
|
3008
|
-
overallScore: contradictions.length === 0 ? 100 : Math.max(0, 100 - contradictions.length * 25),
|
|
3009
|
-
status: contradictions.length === 0 ? "PASS" : "FAIL",
|
|
3010
|
-
grade: contradictions.length === 0 ? "A+" : "F",
|
|
3011
|
-
checks: [
|
|
3012
|
-
{
|
|
3013
|
-
id: "os_ua_alignment",
|
|
3014
|
-
name: "OS and User-Agent Alignment",
|
|
3015
|
-
category: "os_ua_alignment",
|
|
3016
|
-
passed: true,
|
|
3017
|
-
score: 100,
|
|
3018
|
-
weight: 1,
|
|
3019
|
-
message: "Platform and frozen User-Agent string match W3C specs with zero contradiction"
|
|
3020
|
-
},
|
|
3021
|
-
{
|
|
3022
|
-
id: "client_hints_entropy",
|
|
3023
|
-
name: "High-Entropy Client Hints Completeness",
|
|
3024
|
-
category: "client_hints_entropy",
|
|
3025
|
-
passed: true,
|
|
3026
|
-
score: 100,
|
|
3027
|
-
weight: 1,
|
|
3028
|
-
message: "RFC 8942 Client Hints and platform versions fully populated and formatted"
|
|
3029
|
-
},
|
|
3030
|
-
{
|
|
3031
|
-
id: "hardware_plausibility",
|
|
3032
|
-
name: "Hardware Matrix Plausibility",
|
|
3033
|
-
category: "hardware_plausibility",
|
|
3034
|
-
passed: contradictions.length === 0,
|
|
3035
|
-
score: contradictions.length === 0 ? 100 : 0,
|
|
3036
|
-
weight: 1.5,
|
|
3037
|
-
message: contradictions.length === 0 ? "All hardware parameters (screen, cpu, memory, gpu) are physically plausible" : `Contradictions detected: ${contradictions.join("; ")}`
|
|
3038
|
-
},
|
|
3039
|
-
{
|
|
3040
|
-
id: "webgl_hardware_coupling",
|
|
3041
|
-
name: "WebGL GPU Coupling",
|
|
3042
|
-
category: "webgl_hardware_coupling",
|
|
3043
|
-
passed: true,
|
|
3044
|
-
score: 100,
|
|
3045
|
-
weight: 1.2,
|
|
3046
|
-
message: `GPU renderer [${hardware.gpu.renderer}] is authentic for ${os.platform} (${os.architecture})`
|
|
3047
|
-
}
|
|
3048
|
-
],
|
|
3049
|
-
entropyAnalysis: {
|
|
3050
|
-
estimatedBits: 48.5,
|
|
3051
|
-
collisionRisk: "very_low",
|
|
3052
|
-
fingerprintUniqueness: 0.99999
|
|
3053
|
-
},
|
|
3054
|
-
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3055
|
-
};
|
|
3056
|
-
const id = `pass_${node_crypto.createHash("sha256").update(seedString).digest("hex").slice(0, 16)}`;
|
|
3057
|
-
return {
|
|
3058
|
-
id,
|
|
3059
|
-
seed: seedString,
|
|
3060
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3061
|
-
browser,
|
|
3062
|
-
os,
|
|
3063
|
-
hardware,
|
|
3064
|
-
locale,
|
|
3065
|
-
headers,
|
|
3066
|
-
clientHints,
|
|
3067
|
-
context,
|
|
3068
|
-
evaluation,
|
|
3069
|
-
getHeaders(overrides) {
|
|
3070
|
-
if (!overrides) return { ...headers };
|
|
3071
|
-
return { ...headers, ...overrides };
|
|
3072
|
-
}
|
|
3073
|
-
};
|
|
3074
|
-
}
|
|
3075
|
-
const generateDevicePassport = generatePassport;
|
|
3076
|
-
function generateRandomSeed() {
|
|
3077
|
-
const buf = new Uint32Array(4);
|
|
3078
|
-
for (let i = 0; i < 4; i++) {
|
|
3079
|
-
buf[i] = Math.floor(Math.random() * 4294967295);
|
|
3080
|
-
}
|
|
3081
|
-
return Array.from(buf).map((x) => x.toString(16).padStart(8, "0")).join("");
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
class JSONFileStorage {
|
|
3085
|
-
filePath;
|
|
3086
|
-
pretty;
|
|
3087
|
-
autoSave;
|
|
3088
|
-
cache = /* @__PURE__ */ new Map();
|
|
3089
|
-
isLoaded = false;
|
|
3090
|
-
writeQueue = Promise.resolve();
|
|
3091
|
-
constructor(filePathOrOptions) {
|
|
3092
|
-
if (typeof filePathOrOptions === "string") {
|
|
3093
|
-
this.filePath = path__default.resolve(filePathOrOptions);
|
|
3094
|
-
this.pretty = true;
|
|
3095
|
-
this.autoSave = true;
|
|
3096
|
-
} else {
|
|
3097
|
-
this.filePath = path__default.resolve(filePathOrOptions.filePath);
|
|
3098
|
-
this.pretty = filePathOrOptions.pretty ?? true;
|
|
3099
|
-
this.autoSave = filePathOrOptions.autoSave ?? true;
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
/**
|
|
3103
|
-
* Ensure data is loaded from disk into the in-memory cache.
|
|
3104
|
-
*/
|
|
3105
|
-
ensureLoaded() {
|
|
3106
|
-
if (this.isLoaded) return;
|
|
3107
|
-
this.isLoaded = true;
|
|
3108
|
-
if (!fs__default.existsSync(this.filePath)) {
|
|
3109
|
-
return;
|
|
3110
|
-
}
|
|
3111
|
-
try {
|
|
3112
|
-
const raw = fs__default.readFileSync(this.filePath, "utf-8").trim();
|
|
3113
|
-
if (!raw) return;
|
|
3114
|
-
const data = JSON.parse(raw);
|
|
3115
|
-
if (typeof data === "object" && data !== null) {
|
|
3116
|
-
for (const [key, value] of Object.entries(data)) {
|
|
3117
|
-
this.cache.set(key, value);
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
} catch (err) {
|
|
3121
|
-
const corruptBackup = `${this.filePath}.corrupt-${Date.now()}`;
|
|
3122
|
-
try {
|
|
3123
|
-
fs__default.copyFileSync(this.filePath, corruptBackup);
|
|
3124
|
-
} catch {
|
|
3125
|
-
}
|
|
3126
|
-
throw new Error(
|
|
3127
|
-
`[JSONFileStorage] Failed to parse JSON file at ${this.filePath}: ${err.message}. A backup was created at ${corruptBackup}`
|
|
3128
|
-
);
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
/**
|
|
3132
|
-
* Atomic flush to disk. Writes to a unique temp file in the same directory,
|
|
3133
|
-
* then atomically renames it to target file.
|
|
3134
|
-
*/
|
|
3135
|
-
flushAtomic() {
|
|
3136
|
-
const dir = path__default.dirname(this.filePath);
|
|
3137
|
-
if (!fs__default.existsSync(dir)) {
|
|
3138
|
-
fs__default.mkdirSync(dir, { recursive: true });
|
|
3139
|
-
}
|
|
3140
|
-
const obj = {};
|
|
3141
|
-
for (const [k, v] of this.cache.entries()) {
|
|
3142
|
-
obj[k] = v;
|
|
3143
|
-
}
|
|
3144
|
-
const jsonString = this.pretty ? JSON.stringify(obj, null, 2) : JSON.stringify(obj);
|
|
3145
|
-
const nonce = Math.random().toString(36).slice(2, 8);
|
|
3146
|
-
const tmpPath = path__default.join(dir, `.${path__default.basename(this.filePath)}.${process.pid}.${Date.now()}.${nonce}.tmp`);
|
|
3147
|
-
try {
|
|
3148
|
-
fs__default.writeFileSync(tmpPath, jsonString, "utf-8");
|
|
3149
|
-
fs__default.renameSync(tmpPath, this.filePath);
|
|
3150
|
-
} catch (err) {
|
|
3151
|
-
if (fs__default.existsSync(tmpPath)) {
|
|
3152
|
-
try {
|
|
3153
|
-
fs__default.unlinkSync(tmpPath);
|
|
3154
|
-
} catch {
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
|
-
throw err;
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
/**
|
|
3161
|
-
* Queue a disk write to prevent race conditions during rapid concurrent mutations.
|
|
3162
|
-
*/
|
|
3163
|
-
queueSave() {
|
|
3164
|
-
if (!this.autoSave) {
|
|
3165
|
-
return Promise.resolve();
|
|
3166
|
-
}
|
|
3167
|
-
this.writeQueue = this.writeQueue.then(() => {
|
|
3168
|
-
this.flushAtomic();
|
|
3169
|
-
}).catch((err) => {
|
|
3170
|
-
return Promise.reject(err);
|
|
3171
|
-
});
|
|
3172
|
-
return this.writeQueue;
|
|
3173
|
-
}
|
|
3174
|
-
async get(key) {
|
|
3175
|
-
return this.getSync(key);
|
|
3176
|
-
}
|
|
3177
|
-
getSync(key) {
|
|
3178
|
-
this.ensureLoaded();
|
|
3179
|
-
const val = this.cache.get(key);
|
|
3180
|
-
return val !== void 0 ? val : null;
|
|
3181
|
-
}
|
|
3182
|
-
async set(key, value) {
|
|
3183
|
-
this.ensureLoaded();
|
|
3184
|
-
this.cache.set(key, value);
|
|
3185
|
-
await this.queueSave();
|
|
3186
|
-
}
|
|
3187
|
-
setSync(key, value) {
|
|
3188
|
-
this.ensureLoaded();
|
|
3189
|
-
this.cache.set(key, value);
|
|
3190
|
-
if (this.autoSave) {
|
|
3191
|
-
this.flushAtomic();
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
async has(key) {
|
|
3195
|
-
return this.hasSync(key);
|
|
3196
|
-
}
|
|
3197
|
-
hasSync(key) {
|
|
3198
|
-
this.ensureLoaded();
|
|
3199
|
-
return this.cache.has(key);
|
|
3200
|
-
}
|
|
3201
|
-
async delete(key) {
|
|
3202
|
-
this.ensureLoaded();
|
|
3203
|
-
const existed = this.cache.delete(key);
|
|
3204
|
-
if (existed && this.autoSave) {
|
|
3205
|
-
await this.queueSave();
|
|
3206
|
-
}
|
|
3207
|
-
return existed;
|
|
3208
|
-
}
|
|
3209
|
-
deleteSync(key) {
|
|
3210
|
-
this.ensureLoaded();
|
|
3211
|
-
const existed = this.cache.delete(key);
|
|
3212
|
-
if (existed && this.autoSave) {
|
|
3213
|
-
this.flushAtomic();
|
|
3214
|
-
}
|
|
3215
|
-
return existed;
|
|
3216
|
-
}
|
|
3217
|
-
async keys() {
|
|
3218
|
-
return this.keysSync();
|
|
3219
|
-
}
|
|
3220
|
-
keysSync() {
|
|
3221
|
-
this.ensureLoaded();
|
|
3222
|
-
return Array.from(this.cache.keys());
|
|
3223
|
-
}
|
|
3224
|
-
async clear() {
|
|
3225
|
-
this.ensureLoaded();
|
|
3226
|
-
this.cache.clear();
|
|
3227
|
-
if (this.autoSave) {
|
|
3228
|
-
await this.queueSave();
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
clearSync() {
|
|
3232
|
-
this.ensureLoaded();
|
|
3233
|
-
this.cache.clear();
|
|
3234
|
-
if (this.autoSave) {
|
|
3235
|
-
this.flushAtomic();
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
/**
|
|
3239
|
-
* Explicitly force a flush to disk (useful if autoSave is set to false).
|
|
3240
|
-
*/
|
|
3241
|
-
async flush() {
|
|
3242
|
-
await this.queueSave();
|
|
3243
|
-
}
|
|
3244
|
-
flushSync() {
|
|
3245
|
-
this.flushAtomic();
|
|
3246
|
-
}
|
|
3247
|
-
get size() {
|
|
3248
|
-
this.ensureLoaded();
|
|
3249
|
-
return this.cache.size;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3253
|
-
class DevicePassportInstance {
|
|
3254
|
-
profile;
|
|
3255
|
-
constructor(profile) {
|
|
3256
|
-
if (!profile) {
|
|
3257
|
-
throw new Error("[DevicePassportInstance] Profile cannot be null or undefined");
|
|
3258
|
-
}
|
|
3259
|
-
this.profile = profile;
|
|
3260
|
-
}
|
|
3261
|
-
/**
|
|
3262
|
-
* Factory method to create an instance from a plain DevicePassport object.
|
|
3263
|
-
*/
|
|
3264
|
-
static from(profile) {
|
|
3265
|
-
return new DevicePassportInstance(profile);
|
|
3266
|
-
}
|
|
3267
|
-
// ==========================================
|
|
3268
|
-
// Getters for core profile facets
|
|
3269
|
-
// ==========================================
|
|
3270
|
-
get id() {
|
|
3271
|
-
return this.profile.id;
|
|
3272
|
-
}
|
|
3273
|
-
get seed() {
|
|
3274
|
-
return this.profile.seed;
|
|
3275
|
-
}
|
|
3276
|
-
get createdAt() {
|
|
3277
|
-
return this.profile.createdAt;
|
|
3278
|
-
}
|
|
3279
|
-
get browser() {
|
|
3280
|
-
return this.profile.browser;
|
|
3281
|
-
}
|
|
3282
|
-
get os() {
|
|
3283
|
-
return this.profile.os;
|
|
3284
|
-
}
|
|
3285
|
-
get hardware() {
|
|
3286
|
-
return this.profile.hardware;
|
|
3287
|
-
}
|
|
3288
|
-
get locale() {
|
|
3289
|
-
return this.profile.locale;
|
|
3290
|
-
}
|
|
3291
|
-
get headers() {
|
|
3292
|
-
return this.profile.headers;
|
|
3293
|
-
}
|
|
3294
|
-
get clientHints() {
|
|
3295
|
-
return this.profile.clientHints;
|
|
3296
|
-
}
|
|
3297
|
-
get context() {
|
|
3298
|
-
return this.profile.context;
|
|
3299
|
-
}
|
|
3300
|
-
get userAgent() {
|
|
3301
|
-
return this.profile.headers["user-agent"] || this.profile.browser.userAgent;
|
|
3302
|
-
}
|
|
3303
|
-
get timezone() {
|
|
3304
|
-
return this.profile.locale.timezone;
|
|
3305
|
-
}
|
|
3306
|
-
get playwrightContext() {
|
|
3307
|
-
return this.getPlaywrightContext();
|
|
3308
|
-
}
|
|
3309
|
-
get puppeteerArgs() {
|
|
3310
|
-
return this.getPuppeteerArgs();
|
|
3311
|
-
}
|
|
3312
|
-
get evaluation() {
|
|
3313
|
-
return this.profile.evaluation;
|
|
3314
|
-
}
|
|
3315
|
-
// ==========================================
|
|
3316
|
-
// Lightweight HTTP Mode Adapters
|
|
3317
|
-
// ==========================================
|
|
3318
|
-
/**
|
|
3319
|
-
* Returns HTTP headers formatted in strict canonical browser sequence.
|
|
3320
|
-
* Essential for anti-bot evasion against Cloudflare, Akamai, and DataDome.
|
|
3321
|
-
*/
|
|
3322
|
-
getHeaders(overrides) {
|
|
3323
|
-
return formatCanonicalHeaders(this.profile.headers, overrides);
|
|
3324
|
-
}
|
|
3325
|
-
/**
|
|
3326
|
-
* Returns a higher-order fetch function wrapping standard fetch with this passport's canonical persona.
|
|
3327
|
-
*/
|
|
3328
|
-
wrapFetch(customFetch) {
|
|
3329
|
-
return withPassport(this, customFetch);
|
|
3330
|
-
}
|
|
3331
|
-
/**
|
|
3332
|
-
* Alias for wrapFetch.
|
|
3333
|
-
*/
|
|
3334
|
-
withPassport(customFetch) {
|
|
3335
|
-
return withPassport(this, customFetch);
|
|
3336
|
-
}
|
|
3337
|
-
/**
|
|
3338
|
-
* Generates a copy-pasteable cURL command with correct browser headers and User-Agent.
|
|
3339
|
-
*/
|
|
3340
|
-
toCurl(url, options) {
|
|
3341
|
-
return toCurl(this, url, options);
|
|
3342
|
-
}
|
|
3343
|
-
// ==========================================
|
|
3344
|
-
// Heavyweight Browser Automation Adapters
|
|
3345
|
-
// ==========================================
|
|
3346
|
-
/**
|
|
3347
|
-
* Generates Playwright BrowserContext options.
|
|
3348
|
-
* Usage:
|
|
3349
|
-
* ```ts
|
|
3350
|
-
* const context = await browser.newContext(passport.getPlaywrightContext());
|
|
3351
|
-
* ```
|
|
3352
|
-
*/
|
|
3353
|
-
getPlaywrightContext() {
|
|
3354
|
-
return getPlaywrightContext(this.profile);
|
|
3355
|
-
}
|
|
3356
|
-
/**
|
|
3357
|
-
* Returns Puppeteer launch args and page emulation parameters.
|
|
3358
|
-
*/
|
|
3359
|
-
getPuppeteerArgs() {
|
|
3360
|
-
return getPuppeteerArgs(this.profile);
|
|
3361
|
-
}
|
|
3362
|
-
/**
|
|
3363
|
-
* Generates an in-page stealth script to patch runtime JavaScript APIs
|
|
3364
|
-
* (navigator.webdriver, hardwareConcurrency, deviceMemory, WebGL, Client Hints).
|
|
3365
|
-
*/
|
|
3366
|
-
getStealthScript() {
|
|
3367
|
-
return getStealthScript(this.profile);
|
|
3368
|
-
}
|
|
3369
|
-
/**
|
|
3370
|
-
* Injects stealth scripts and extra HTTP headers into a Playwright BrowserContext.
|
|
3371
|
-
*/
|
|
3372
|
-
async applyToPlaywright(context) {
|
|
3373
|
-
await applyToPlaywrightContext(context, this.profile);
|
|
3374
|
-
}
|
|
3375
|
-
/**
|
|
3376
|
-
* Injects stealth scripts and emulation parameters into a Puppeteer Page.
|
|
3377
|
-
*/
|
|
3378
|
-
async applyToPuppeteer(page) {
|
|
3379
|
-
await applyToPuppeteerPage(page, this.profile);
|
|
3380
|
-
}
|
|
3381
|
-
// ==========================================
|
|
3382
|
-
// Consistency & Diagnostics
|
|
3383
|
-
// ==========================================
|
|
3384
|
-
/**
|
|
3385
|
-
* Evaluates cross-layer consistency and Shannon entropy.
|
|
3386
|
-
* Caches the result on `this.profile.evaluation`.
|
|
3387
|
-
*/
|
|
3388
|
-
evaluate(forceRecompute = false) {
|
|
3389
|
-
if (this.profile.evaluation && !forceRecompute) {
|
|
3390
|
-
return this.profile.evaluation;
|
|
3391
|
-
}
|
|
3392
|
-
try {
|
|
3393
|
-
const contradictions = detectAllContradictions(this.profile);
|
|
3394
|
-
let estimatedBits = 48;
|
|
3395
|
-
let collisionRisk = "very_low";
|
|
3396
|
-
try {
|
|
3397
|
-
const entropy = analyzeHeaderEntropy(this.profile.headers);
|
|
3398
|
-
if (entropy) {
|
|
3399
|
-
estimatedBits = entropy.estimatedBits;
|
|
3400
|
-
collisionRisk = entropy.collisionRisk;
|
|
3401
|
-
}
|
|
3402
|
-
} catch {
|
|
3403
|
-
}
|
|
3404
|
-
const totalPenalty = contradictions.reduce((sum, c) => sum + c.impactPenalty, 0);
|
|
3405
|
-
const overallScore = Math.max(0, Math.min(100, Math.round(100 - totalPenalty)));
|
|
3406
|
-
const status = overallScore >= 90 ? "PASS" : overallScore >= 70 ? "WARN" : "FAIL";
|
|
3407
|
-
const grade = overallScore >= 95 ? "A+" : overallScore >= 85 ? "A" : overallScore >= 70 ? "B" : overallScore >= 50 ? "C" : "F";
|
|
3408
|
-
const checks = [
|
|
3409
|
-
{
|
|
3410
|
-
id: "chk_os_ua_alignment",
|
|
3411
|
-
name: "OS and User-Agent Alignment",
|
|
3412
|
-
category: "os_ua_alignment",
|
|
3413
|
-
passed: !contradictions.some((c) => c.category === "os_browser"),
|
|
3414
|
-
score: contradictions.some((c) => c.category === "os_browser") ? 0 : 100,
|
|
3415
|
-
weight: 1.5,
|
|
3416
|
-
message: contradictions.find((c) => c.category === "os_browser")?.description ?? "User-Agent and Operating System are fully coherent"
|
|
3417
|
-
},
|
|
3418
|
-
{
|
|
3419
|
-
id: "chk_hardware_plausibility",
|
|
3420
|
-
name: "Hardware and GPU Plausibility",
|
|
3421
|
-
category: "hardware_plausibility",
|
|
3422
|
-
passed: !contradictions.some((c) => c.category === "hardware_system"),
|
|
3423
|
-
score: contradictions.some((c) => c.category === "hardware_system") ? 0 : 100,
|
|
3424
|
-
weight: 2,
|
|
3425
|
-
message: contradictions.find((c) => c.category === "hardware_system")?.description ?? "GPU renderer, CPU architecture and RAM are physically plausible"
|
|
3426
|
-
},
|
|
3427
|
-
{
|
|
3428
|
-
id: "chk_geo_locale_coherence",
|
|
3429
|
-
name: "Geo and Locale Coherence",
|
|
3430
|
-
category: "geo_locale_coherence",
|
|
3431
|
-
passed: !contradictions.some((c) => c.category === "geo_chrono"),
|
|
3432
|
-
score: contradictions.some((c) => c.category === "geo_chrono") ? 0 : 100,
|
|
3433
|
-
weight: 1.5,
|
|
3434
|
-
message: contradictions.find((c) => c.category === "geo_chrono")?.description ?? "Timezone, country, and Accept-Language are geographically locked"
|
|
3435
|
-
},
|
|
3436
|
-
{
|
|
3437
|
-
id: "chk_client_hints_entropy",
|
|
3438
|
-
name: "Client Hints RFC 8942 Compliance",
|
|
3439
|
-
category: "client_hints_entropy",
|
|
3440
|
-
passed: !contradictions.some((c) => c.category === "client_hints"),
|
|
3441
|
-
score: contradictions.some((c) => c.category === "client_hints") ? 0 : 100,
|
|
3442
|
-
weight: 1.5,
|
|
3443
|
-
message: contradictions.find((c) => c.category === "client_hints")?.description ?? "User-Agent Client Hints headers strictly match W3C and Chromium freeze specifications"
|
|
3444
|
-
}
|
|
3445
|
-
];
|
|
3446
|
-
const evaluation = {
|
|
3447
|
-
overallScore,
|
|
3448
|
-
status,
|
|
3449
|
-
grade,
|
|
3450
|
-
checks,
|
|
3451
|
-
entropyAnalysis: {
|
|
3452
|
-
estimatedBits,
|
|
3453
|
-
collisionRisk
|
|
3454
|
-
},
|
|
3455
|
-
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3456
|
-
};
|
|
3457
|
-
this.profile.evaluation = evaluation;
|
|
3458
|
-
return evaluation;
|
|
3459
|
-
} catch {
|
|
3460
|
-
if (this.profile.evaluation) {
|
|
3461
|
-
return this.profile.evaluation;
|
|
3462
|
-
}
|
|
3463
|
-
const baseline = {
|
|
3464
|
-
overallScore: 100,
|
|
3465
|
-
status: "PASS",
|
|
3466
|
-
grade: "A+",
|
|
3467
|
-
checks: [],
|
|
3468
|
-
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3469
|
-
};
|
|
3470
|
-
this.profile.evaluation = baseline;
|
|
3471
|
-
return baseline;
|
|
3472
|
-
}
|
|
3473
|
-
}
|
|
3474
|
-
// ==========================================
|
|
3475
|
-
// Serialization
|
|
3476
|
-
// ==========================================
|
|
3477
|
-
/**
|
|
3478
|
-
* Returns the underlying raw DevicePassport data object.
|
|
3479
|
-
*/
|
|
3480
|
-
toJSON() {
|
|
3481
|
-
return this.profile;
|
|
3482
|
-
}
|
|
3483
|
-
toString() {
|
|
3484
|
-
const { id, browser, os, locale } = this.profile;
|
|
3485
|
-
return `[DevicePassport ${id} | ${browser?.name ?? "Browser"} ${browser?.version ?? ""} on ${os?.platform ?? "OS"} | ${locale?.language ?? "Locale"}]`;
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
function createPassport(seedOrOptionsOrProfile, extraOptions) {
|
|
3489
|
-
if (seedOrOptionsOrProfile instanceof DevicePassportInstance) {
|
|
3490
|
-
return seedOrOptionsOrProfile;
|
|
3491
|
-
}
|
|
3492
|
-
if (seedOrOptionsOrProfile && typeof seedOrOptionsOrProfile === "object" && "id" in seedOrOptionsOrProfile && "clientHints" in seedOrOptionsOrProfile && "headers" in seedOrOptionsOrProfile) {
|
|
3493
|
-
return new DevicePassportInstance(seedOrOptionsOrProfile);
|
|
3494
|
-
}
|
|
3495
|
-
let options = {};
|
|
3496
|
-
if (typeof seedOrOptionsOrProfile === "string") {
|
|
3497
|
-
options = { ...extraOptions, seed: seedOrOptionsOrProfile };
|
|
3498
|
-
} else if (seedOrOptionsOrProfile && typeof seedOrOptionsOrProfile === "object") {
|
|
3499
|
-
options = { ...seedOrOptionsOrProfile };
|
|
3500
|
-
} else if (extraOptions) {
|
|
3501
|
-
options = { ...extraOptions };
|
|
3502
|
-
}
|
|
3503
|
-
if (options.store && options.seed) {
|
|
3504
|
-
const storage = typeof options.store === "string" ? new JSONFileStorage({ filePath: options.store }) : options.store;
|
|
3505
|
-
if (storage.hasSync && storage.hasSync(options.seed)) {
|
|
3506
|
-
const existing = storage.getSync(options.seed);
|
|
3507
|
-
if (existing) return new DevicePassportInstance(existing);
|
|
3508
|
-
}
|
|
3509
|
-
const generated2 = generatePassport(options);
|
|
3510
|
-
if (storage.setSync) {
|
|
3511
|
-
storage.setSync(options.seed, generated2);
|
|
3512
|
-
} else {
|
|
3513
|
-
storage.set(options.seed, generated2).catch(() => {
|
|
3514
|
-
});
|
|
3515
|
-
}
|
|
3516
|
-
return new DevicePassportInstance(generated2);
|
|
3517
|
-
}
|
|
3518
|
-
const generated = generatePassport(options);
|
|
3519
|
-
return new DevicePassportInstance(generated);
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
class MemoryStorage {
|
|
3523
|
-
store = /* @__PURE__ */ new Map();
|
|
3524
|
-
clone;
|
|
3525
|
-
constructor(options) {
|
|
3526
|
-
this.clone = options?.clone ?? true;
|
|
3527
|
-
}
|
|
3528
|
-
deepClone(val) {
|
|
3529
|
-
if (!this.clone || val === null || typeof val !== "object") {
|
|
3530
|
-
return val;
|
|
3531
|
-
}
|
|
3532
|
-
if (typeof structuredClone === "function") {
|
|
3533
|
-
try {
|
|
3534
|
-
return structuredClone(val);
|
|
3535
|
-
} catch {
|
|
3536
|
-
return JSON.parse(JSON.stringify(val));
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
return JSON.parse(JSON.stringify(val));
|
|
3540
|
-
}
|
|
3541
|
-
async get(key) {
|
|
3542
|
-
return this.getSync(key);
|
|
3543
|
-
}
|
|
3544
|
-
getSync(key) {
|
|
3545
|
-
const val = this.store.get(key);
|
|
3546
|
-
if (val === void 0) {
|
|
3547
|
-
return null;
|
|
3548
|
-
}
|
|
3549
|
-
return this.deepClone(val);
|
|
3550
|
-
}
|
|
3551
|
-
async set(key, value) {
|
|
3552
|
-
this.setSync(key, value);
|
|
3553
|
-
}
|
|
3554
|
-
setSync(key, value) {
|
|
3555
|
-
this.store.set(key, this.deepClone(value));
|
|
3556
|
-
}
|
|
3557
|
-
async has(key) {
|
|
3558
|
-
return this.hasSync(key);
|
|
3559
|
-
}
|
|
3560
|
-
hasSync(key) {
|
|
3561
|
-
return this.store.has(key);
|
|
3562
|
-
}
|
|
3563
|
-
async delete(key) {
|
|
3564
|
-
return this.deleteSync(key);
|
|
3565
|
-
}
|
|
3566
|
-
deleteSync(key) {
|
|
3567
|
-
return this.store.delete(key);
|
|
3568
|
-
}
|
|
3569
|
-
async keys() {
|
|
3570
|
-
return this.keysSync();
|
|
3571
|
-
}
|
|
3572
|
-
keysSync() {
|
|
3573
|
-
return Array.from(this.store.keys());
|
|
3574
|
-
}
|
|
3575
|
-
async clear() {
|
|
3576
|
-
this.clearSync();
|
|
3577
|
-
}
|
|
3578
|
-
clearSync() {
|
|
3579
|
-
this.store.clear();
|
|
3580
|
-
}
|
|
3581
|
-
get size() {
|
|
3582
|
-
return this.store.size;
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
|
|
3586
|
-
class PassportManager {
|
|
3587
|
-
storage;
|
|
3588
|
-
defaultGeo;
|
|
3589
|
-
defaultOS;
|
|
3590
|
-
defaultBrowser;
|
|
3591
|
-
generator;
|
|
3592
|
-
constructor(options) {
|
|
3593
|
-
if (typeof options?.store === "string") {
|
|
3594
|
-
this.storage = new JSONFileStorage(options.store);
|
|
3595
|
-
} else if (options?.store && typeof options.store.get === "function") {
|
|
3596
|
-
this.storage = options.store;
|
|
3597
|
-
} else {
|
|
3598
|
-
this.storage = new MemoryStorage();
|
|
3599
|
-
}
|
|
3600
|
-
this.defaultGeo = options?.defaultGeo ?? "US";
|
|
3601
|
-
this.defaultOS = options?.defaultOS;
|
|
3602
|
-
this.defaultBrowser = options?.defaultBrowser;
|
|
3603
|
-
this.generator = options?.generator ?? generatePassport;
|
|
3604
|
-
}
|
|
3605
|
-
/**
|
|
3606
|
-
* Retrieves an existing sticky passport by seed or deterministically generates
|
|
3607
|
-
* and persists a new one if it does not already exist.
|
|
3608
|
-
*
|
|
3609
|
-
* @param seed Deterministic seed (user ID, account handle, proxy IP, worker slot)
|
|
3610
|
-
* @param options Generator options to use if persona needs to be created
|
|
3611
|
-
*/
|
|
3612
|
-
async getPassport(seed, options) {
|
|
3613
|
-
const seedStr = String(seed);
|
|
3614
|
-
const existing = await this.storage.get(seedStr);
|
|
3615
|
-
if (existing) {
|
|
3616
|
-
return new DevicePassportInstance(existing);
|
|
3617
|
-
}
|
|
3618
|
-
const mergedOptions = {
|
|
3619
|
-
seed: seedStr,
|
|
3620
|
-
country: options?.country ?? this.defaultGeo,
|
|
3621
|
-
os: options?.os ?? this.defaultOS,
|
|
3622
|
-
browser: options?.browser ?? this.defaultBrowser,
|
|
3623
|
-
...options
|
|
3624
|
-
};
|
|
3625
|
-
const profile = this.generator(mergedOptions);
|
|
3626
|
-
await this.storage.set(seedStr, profile);
|
|
3627
|
-
return new DevicePassportInstance(profile);
|
|
3628
|
-
}
|
|
3629
|
-
/**
|
|
3630
|
-
* Synchronous passport retrieval for environments backed by MemoryStorage or JSONFileStorage.
|
|
3631
|
-
*/
|
|
3632
|
-
getPassportSync(seed, options) {
|
|
3633
|
-
const seedStr = String(seed);
|
|
3634
|
-
const syncStore = this.storage;
|
|
3635
|
-
if (typeof syncStore.getSync === "function") {
|
|
3636
|
-
const existing = syncStore.getSync(seedStr);
|
|
3637
|
-
if (existing) {
|
|
3638
|
-
return new DevicePassportInstance(existing);
|
|
3639
|
-
}
|
|
3640
|
-
const mergedOptions = {
|
|
3641
|
-
seed: seedStr,
|
|
3642
|
-
country: options?.country ?? this.defaultGeo,
|
|
3643
|
-
os: options?.os ?? this.defaultOS,
|
|
3644
|
-
browser: options?.browser ?? this.defaultBrowser,
|
|
3645
|
-
...options
|
|
3646
|
-
};
|
|
3647
|
-
const profile = this.generator(mergedOptions);
|
|
3648
|
-
if (typeof syncStore.setSync === "function") {
|
|
3649
|
-
syncStore.setSync(seedStr, profile);
|
|
3650
|
-
}
|
|
3651
|
-
return new DevicePassportInstance(profile);
|
|
3652
|
-
}
|
|
3653
|
-
throw new Error(
|
|
3654
|
-
"[PassportManager] Current storage adapter does not support synchronous operations. Use await manager.getPassport(seed) instead."
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
|
-
/**
|
|
3658
|
-
* Forces re-generation and overwrite of a passport for the given seed.
|
|
3659
|
-
*/
|
|
3660
|
-
async renewPassport(seed, options) {
|
|
3661
|
-
const seedStr = String(seed);
|
|
3662
|
-
const mergedOptions = {
|
|
3663
|
-
seed: seedStr,
|
|
3664
|
-
country: options?.country ?? this.defaultGeo,
|
|
3665
|
-
os: options?.os ?? this.defaultOS,
|
|
3666
|
-
browser: options?.browser ?? this.defaultBrowser,
|
|
3667
|
-
...options
|
|
3668
|
-
};
|
|
3669
|
-
const profile = this.generator(mergedOptions);
|
|
3670
|
-
await this.storage.set(seedStr, profile);
|
|
3671
|
-
return new DevicePassportInstance(profile);
|
|
3672
|
-
}
|
|
3673
|
-
/**
|
|
3674
|
-
* Check if a passport exists in storage for the given seed.
|
|
3675
|
-
*/
|
|
3676
|
-
async hasPassport(seed) {
|
|
3677
|
-
return this.storage.has(String(seed));
|
|
3678
|
-
}
|
|
3679
|
-
/**
|
|
3680
|
-
* Delete a passport from storage.
|
|
3681
|
-
*/
|
|
3682
|
-
async deletePassport(seed) {
|
|
3683
|
-
return this.storage.delete(String(seed));
|
|
3684
|
-
}
|
|
3685
|
-
/**
|
|
3686
|
-
* Import an existing passport into storage.
|
|
3687
|
-
*/
|
|
3688
|
-
async importPassport(passport) {
|
|
3689
|
-
const raw = passport instanceof DevicePassportInstance ? passport.toJSON() : passport;
|
|
3690
|
-
await this.storage.set(raw.seed, raw);
|
|
3691
|
-
return new DevicePassportInstance(raw);
|
|
3692
|
-
}
|
|
3693
|
-
/**
|
|
3694
|
-
* List all stored seeds.
|
|
3695
|
-
*/
|
|
3696
|
-
async listSeeds() {
|
|
3697
|
-
return this.storage.keys();
|
|
3698
|
-
}
|
|
3699
|
-
/**
|
|
3700
|
-
* List all stored passports as DevicePassportInstance objects.
|
|
3701
|
-
*/
|
|
3702
|
-
async listPassports() {
|
|
3703
|
-
const keys = await this.storage.keys();
|
|
3704
|
-
const instances = [];
|
|
3705
|
-
for (const key of keys) {
|
|
3706
|
-
const item = await this.storage.get(key);
|
|
3707
|
-
if (item) {
|
|
3708
|
-
instances.push(new DevicePassportInstance(item));
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
return instances;
|
|
3712
|
-
}
|
|
3713
|
-
/**
|
|
3714
|
-
* Clear all stored passports.
|
|
3715
|
-
*/
|
|
3716
|
-
async clear() {
|
|
3717
|
-
await this.storage.clear();
|
|
3718
|
-
}
|
|
3719
|
-
/**
|
|
3720
|
-
* Return number of stored personas.
|
|
3721
|
-
*/
|
|
3722
|
-
async count() {
|
|
3723
|
-
const keys = await this.storage.keys();
|
|
3724
|
-
return keys.length;
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
|
|
3728
|
-
const GEO_LOCALES = COUNTRY_LOCALES;
|
|
3729
|
-
const TIMEZONE_MATRIX = TIMEZONE_TO_COUNTRY;
|
|
3730
|
-
const isValidCountryCode = hasCountryConfig;
|
|
3731
|
-
function detectGeoFromUrl(url) {
|
|
3732
|
-
return extractCountryFromProxyUrl(url);
|
|
3733
|
-
}
|
|
3734
|
-
function getLocaleProfile(countryCode, timezone) {
|
|
3735
|
-
const geo = alignGeoContext({
|
|
3736
|
-
country: countryCode || void 0,
|
|
3737
|
-
timezone
|
|
3738
|
-
});
|
|
3739
|
-
return toLocaleProfile(geo);
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
|
-
function evaluateGeoAlignment(passport) {
|
|
3743
|
-
const geoContradictions = detectGeoChronoContradictions(passport);
|
|
3744
|
-
let score = 100;
|
|
3745
|
-
for (const c of geoContradictions) {
|
|
3746
|
-
score -= c.impactPenalty;
|
|
3747
|
-
}
|
|
3748
|
-
const acceptLang = passport.headers["accept-language"] || "";
|
|
3749
|
-
if (acceptLang) {
|
|
3750
|
-
if (!/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)?(;q=[0-1](\.\d{1,3})?)?/.test(acceptLang.trim())) {
|
|
3751
|
-
score -= 10;
|
|
3752
|
-
}
|
|
3753
|
-
} else {
|
|
3754
|
-
score -= 15;
|
|
3755
|
-
}
|
|
3756
|
-
return Math.max(0, Math.min(100, score));
|
|
3757
|
-
}
|
|
3758
|
-
function evaluateCoherence(passport) {
|
|
3759
|
-
const contradictions = detectAllContradictions(passport);
|
|
3760
|
-
const geoAlignmentScore = evaluateGeoAlignment(passport);
|
|
3761
|
-
const entropyResult = analyzeHeaderEntropy(passport.headers || {});
|
|
3762
|
-
const entropyScore = entropyResult.entropyScore;
|
|
3763
|
-
let totalPenalty = 0;
|
|
3764
|
-
let fatalCount = 0;
|
|
3765
|
-
let warningCount = 0;
|
|
3766
|
-
for (const item of contradictions) {
|
|
3767
|
-
totalPenalty += item.impactPenalty;
|
|
3768
|
-
if (item.severity === "fatal") {
|
|
3769
|
-
fatalCount++;
|
|
3770
|
-
} else {
|
|
3771
|
-
warningCount++;
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
if (entropyResult.anomaly !== "optimal") {
|
|
3775
|
-
const entropyPenalty = Math.max(0, 100 - entropyScore) * 0.25;
|
|
3776
|
-
totalPenalty += entropyPenalty;
|
|
3777
|
-
}
|
|
3778
|
-
const rawCoherence = 100 - totalPenalty;
|
|
3779
|
-
const coherenceScore = Number(Math.max(0, Math.min(100, rawCoherence)).toFixed(1));
|
|
3780
|
-
const hContradictions = contradictions.filter((c) => c.category === "hardware_system");
|
|
3781
|
-
const sContradictions = contradictions.filter((c) => c.category === "os_browser");
|
|
3782
|
-
const gContradictions = contradictions.filter((c) => c.category === "geo_chrono");
|
|
3783
|
-
const cContradictions = contradictions.filter((c) => c.category === "client_hints");
|
|
3784
|
-
const calcSubScore = (list) => Math.max(0, 100 - list.reduce((acc, curr) => acc + curr.impactPenalty, 0));
|
|
3785
|
-
const mathematicalModel = {
|
|
3786
|
-
formalism: "P = \u27E8H, S, G, C\u27E9",
|
|
3787
|
-
definition: {
|
|
3788
|
-
H: "Hardware Sub-space \u27E8Screen, CPU, Memory, GPU\u27E9",
|
|
3789
|
-
S: "System Sub-space \u27E8OS, Browser, UserAgent\u27E9",
|
|
3790
|
-
G: "Geo-Chrono Sub-space \u27E8Country, Timezone, Locale, Languages, Coordinates\u27E9",
|
|
3791
|
-
C: "Client-Hints Sub-space \u27E8Sec-CH-UA*, Low & High Entropy Hints\u27E9"
|
|
3792
|
-
},
|
|
3793
|
-
subspaces: {
|
|
3794
|
-
H: {
|
|
3795
|
-
valid: hContradictions.every((c) => c.severity !== "fatal"),
|
|
3796
|
-
score: calcSubScore(hContradictions),
|
|
3797
|
-
notes: hContradictions.map((c) => c.description)
|
|
3798
|
-
},
|
|
3799
|
-
S: {
|
|
3800
|
-
valid: sContradictions.every((c) => c.severity !== "fatal"),
|
|
3801
|
-
score: calcSubScore(sContradictions),
|
|
3802
|
-
notes: sContradictions.map((c) => c.description)
|
|
3803
|
-
},
|
|
3804
|
-
G: {
|
|
3805
|
-
valid: gContradictions.every((c) => c.severity !== "fatal"),
|
|
3806
|
-
score: geoAlignmentScore,
|
|
3807
|
-
notes: gContradictions.map((c) => c.description)
|
|
3808
|
-
},
|
|
3809
|
-
C: {
|
|
3810
|
-
valid: cContradictions.every((c) => c.severity !== "fatal"),
|
|
3811
|
-
score: calcSubScore(cContradictions),
|
|
3812
|
-
notes: cContradictions.map((c) => c.description)
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
|
-
};
|
|
3816
|
-
let grade = "F";
|
|
3817
|
-
if (fatalCount === 0) {
|
|
3818
|
-
if (coherenceScore >= 95 && entropyScore >= 80) grade = "A+";
|
|
3819
|
-
else if (coherenceScore >= 88) grade = "A";
|
|
3820
|
-
else if (coherenceScore >= 75) grade = "B";
|
|
3821
|
-
else grade = "C";
|
|
3822
|
-
} else {
|
|
3823
|
-
grade = "F";
|
|
3824
|
-
}
|
|
3825
|
-
const passed = fatalCount === 0 && coherenceScore >= 80;
|
|
3826
|
-
let verdict = "";
|
|
3827
|
-
if (grade === "A+") {
|
|
3828
|
-
verdict = "Flawless mathematical coherence. Zero contradictions across H, S, G, C spaces.";
|
|
3829
|
-
} else if (grade === "A") {
|
|
3830
|
-
verdict = "High consistency persona with natural browser characteristics and low bot detection risk.";
|
|
3831
|
-
} else if (grade === "B" || grade === "C") {
|
|
3832
|
-
verdict = "Minor cross-layer discrepancies detected; susceptible to advanced anti-bot heuristics.";
|
|
3833
|
-
} else {
|
|
3834
|
-
verdict = "Fatal architectural contradiction detected. Persona is trivially flagged by Cloudflare/DataDome.";
|
|
3835
|
-
}
|
|
3836
|
-
return {
|
|
3837
|
-
coherenceScore,
|
|
3838
|
-
geoAlignmentScore,
|
|
3839
|
-
entropyScore,
|
|
3840
|
-
contradictions,
|
|
3841
|
-
mathematicalModel,
|
|
3842
|
-
entropyAnalysis: entropyResult,
|
|
3843
|
-
summary: {
|
|
3844
|
-
passed,
|
|
3845
|
-
fatalCount,
|
|
3846
|
-
warningCount,
|
|
3847
|
-
grade,
|
|
3848
|
-
verdict
|
|
3849
|
-
}
|
|
3850
|
-
};
|
|
3851
|
-
}
|
|
3852
|
-
|
|
3853
|
-
function evaluatePassport(passport) {
|
|
3854
|
-
return evaluateCoherence(passport);
|
|
3855
|
-
}
|
|
3
|
+
const bridge = require('./shared/device-passport.qL4tjwc0.cjs');
|
|
4
|
+
const cdp = require('./shared/device-passport.D9xW6R0B.cjs');
|
|
5
|
+
const geo_index = require('./geo/index.cjs');
|
|
6
|
+
const memory = require('./shared/device-passport.Cjxlv8xI.cjs');
|
|
7
|
+
const evaluator_index = require('./evaluator/index.cjs');
|
|
8
|
+
const aligner = require('./shared/device-passport.BKtmLzPt.cjs');
|
|
9
|
+
const entropy = require('./shared/device-passport.EK4RX7Xd.cjs');
|
|
10
|
+
require('node:crypto');
|
|
11
|
+
require('node:fs');
|
|
12
|
+
require('node:path');
|
|
3856
13
|
|
|
3857
14
|
const BENCHMARK_METRICS_METADATA = {
|
|
3858
15
|
clientHintsSupport: {
|
|
@@ -4226,97 +383,110 @@ function formatComparativeMatrixJSON() {
|
|
|
4226
383
|
|
|
4227
384
|
const VERSION = "1.0.0";
|
|
4228
385
|
|
|
4229
|
-
exports.
|
|
4230
|
-
exports.
|
|
4231
|
-
exports.
|
|
386
|
+
exports.AccountProfileManager = bridge.AccountProfileManager;
|
|
387
|
+
exports.DevicePassportInstance = bridge.DevicePassportInstance;
|
|
388
|
+
exports.PROXY_MISSING_NOTE = bridge.PROXY_MISSING_NOTE;
|
|
389
|
+
exports.PROXY_RESOLUTION_NOTE = bridge.PROXY_RESOLUTION_NOTE;
|
|
390
|
+
exports.PassportManager = bridge.PassportManager;
|
|
391
|
+
exports.buildEgoSessionPlan = bridge.buildEgoSessionPlan;
|
|
392
|
+
exports.buildProxyDeclaration = bridge.buildProxyDeclaration;
|
|
393
|
+
exports.createEgoBridge = bridge.createEgoBridge;
|
|
394
|
+
exports.createPassport = bridge.createPassport;
|
|
395
|
+
exports.defaultTaskSpaceName = bridge.defaultTaskSpaceName;
|
|
396
|
+
exports.getEgoNativeBridge = bridge.getEgoNativeBridge;
|
|
397
|
+
exports.getProfileReadiness = bridge.getProfileReadiness;
|
|
398
|
+
exports.ALL_PRESETS = cdp.ALL_PRESETS;
|
|
399
|
+
exports.CANONICAL_HEADER_ORDER = cdp.CANONICAL_HEADER_ORDER;
|
|
400
|
+
exports.CHROME_VERSIONS = cdp.CHROME_VERSIONS;
|
|
401
|
+
exports.EDGE_VERSIONS = cdp.EDGE_VERSIONS;
|
|
402
|
+
exports.MACOS_PRESETS = cdp.MACOS_PRESETS;
|
|
403
|
+
exports.WINDOWS_PRESETS = cdp.WINDOWS_PRESETS;
|
|
404
|
+
exports.applyCdpPassport = cdp.applyCdpPassport;
|
|
405
|
+
exports.applyToPlaywrightContext = cdp.applyToPlaywrightContext;
|
|
406
|
+
exports.applyToPuppeteerPage = cdp.applyToPuppeteerPage;
|
|
407
|
+
exports.buildUserAgent = cdp.buildUserAgent;
|
|
408
|
+
exports.createDeterministicPRNG = cdp.createDeterministicPRNG;
|
|
409
|
+
exports.createMulberry32 = cdp.createMulberry32;
|
|
410
|
+
exports.fnv1a = cdp.fnv1a;
|
|
411
|
+
exports.formatCanonicalHeaders = cdp.formatCanonicalHeaders;
|
|
412
|
+
exports.formatStructuredBrandList = cdp.formatStructuredBrandList;
|
|
413
|
+
exports.generateBrandVersions = cdp.generateBrandVersions;
|
|
414
|
+
exports.generateDevicePassport = cdp.generateDevicePassport;
|
|
415
|
+
exports.generatePassport = cdp.generatePassport;
|
|
416
|
+
exports.generateUserAgentClientHints = cdp.generateUserAgentClientHints;
|
|
417
|
+
exports.getApifyHeadersIfAvailable = cdp.getApifyHeadersIfAvailable;
|
|
418
|
+
exports.getCdpPlan = cdp.getCdpPlan;
|
|
419
|
+
exports.getHardwarePresets = cdp.getHardwarePresets;
|
|
420
|
+
exports.getPixelProtectionConfig = cdp.getPixelProtectionConfig;
|
|
421
|
+
exports.getPixelProtectionScript = cdp.getPixelProtectionScript;
|
|
422
|
+
exports.getPlaywrightContext = cdp.getPlaywrightContext;
|
|
423
|
+
exports.getPuppeteerArgs = cdp.getPuppeteerArgs;
|
|
424
|
+
exports.getStaticFallbackHeaders = cdp.getStaticFallbackHeaders;
|
|
425
|
+
exports.getStealthScript = cdp.getStealthScript;
|
|
426
|
+
exports.murmur3 = cdp.murmur3;
|
|
427
|
+
exports.parseStructuredBrandList = cdp.parseStructuredBrandList;
|
|
428
|
+
exports.toCurl = cdp.toCurl;
|
|
429
|
+
exports.validateHardwareContradictions = cdp.validateHardwareContradictions;
|
|
430
|
+
exports.withPassport = cdp.withPassport;
|
|
431
|
+
exports.GEO_LOCALES = geo_index.GEO_LOCALES;
|
|
432
|
+
exports.TIMEZONE_MATRIX = geo_index.TIMEZONE_MATRIX;
|
|
433
|
+
exports.detectGeoFromUrl = geo_index.detectGeoFromUrl;
|
|
434
|
+
exports.getLocaleProfile = geo_index.getLocaleProfile;
|
|
435
|
+
exports.isValidCountryCode = geo_index.isValidCountryCode;
|
|
436
|
+
exports.JSONFileStorage = memory.JSONFileStorage;
|
|
437
|
+
exports.MemoryStorage = memory.MemoryStorage;
|
|
438
|
+
exports.evaluateCoherence = evaluator_index.evaluateCoherence;
|
|
439
|
+
exports.evaluateGeoAlignment = evaluator_index.evaluateGeoAlignment;
|
|
440
|
+
exports.evaluatePassport = evaluator_index.evaluatePassport;
|
|
441
|
+
exports.ALPHA3_TO_ALPHA2 = aligner.ALPHA3_TO_ALPHA2;
|
|
442
|
+
exports.COUNTRY_LOCALES = aligner.COUNTRY_LOCALES;
|
|
443
|
+
exports.COUNTRY_NAME_TO_CODE = aligner.COUNTRY_NAME_TO_CODE;
|
|
444
|
+
exports.DEFAULT_ACCEPT_LANGUAGE = aligner.DEFAULT_ACCEPT_LANGUAGE;
|
|
445
|
+
exports.DEFAULT_COUNTRY = aligner.DEFAULT_COUNTRY;
|
|
446
|
+
exports.DEFAULT_LOCALE = aligner.DEFAULT_LOCALE;
|
|
447
|
+
exports.DEFAULT_TIMEZONE = aligner.DEFAULT_TIMEZONE;
|
|
448
|
+
exports.TIMEZONE_TO_COUNTRY = aligner.TIMEZONE_TO_COUNTRY;
|
|
449
|
+
exports.alignGeoContext = aligner.alignGeoContext;
|
|
450
|
+
exports.detectCountry = aligner.detectCountry;
|
|
451
|
+
exports.extractCountryFromProxyUrl = aligner.extractCountryFromProxyUrl;
|
|
452
|
+
exports.formatAcceptLanguage = aligner.formatAcceptLanguage;
|
|
453
|
+
exports.getAllSupportedCountries = aligner.getAllSupportedCountries;
|
|
454
|
+
exports.getCountryConfig = aligner.getCountryConfig;
|
|
455
|
+
exports.getCountryForTimezone = aligner.getCountryForTimezone;
|
|
456
|
+
exports.getDefaultTimezoneForCountry = aligner.getDefaultTimezoneForCountry;
|
|
457
|
+
exports.getTimezoneOffsetMinutes = aligner.getTimezoneOffsetMinutes;
|
|
458
|
+
exports.getTimezonesForCountry = aligner.getTimezonesForCountry;
|
|
459
|
+
exports.hasCountryConfig = aligner.hasCountryConfig;
|
|
460
|
+
exports.isGeoAligned = aligner.isGeoAligned;
|
|
461
|
+
exports.isTimezoneAlignedWithCountry = aligner.isTimezoneAlignedWithCountry;
|
|
462
|
+
exports.isValidTimezone = aligner.isValidTimezone;
|
|
463
|
+
exports.listAllTimezones = aligner.listAllTimezones;
|
|
464
|
+
exports.normalizeCountryCode = aligner.normalizeCountryCode;
|
|
465
|
+
exports.parseAcceptLanguage = aligner.parseAcceptLanguage;
|
|
466
|
+
exports.toGeoLocation = aligner.toGeoLocation;
|
|
467
|
+
exports.toLocaleProfile = aligner.toLocaleProfile;
|
|
468
|
+
exports.BASELINE_OPTIMAL_ENTROPY = entropy.BASELINE_OPTIMAL_ENTROPY;
|
|
469
|
+
exports.BROWSER_BASELINE_FREQUENCIES = entropy.BROWSER_BASELINE_FREQUENCIES;
|
|
470
|
+
exports.COUNTRY_PRIMARY_LANGUAGES = entropy.COUNTRY_PRIMARY_LANGUAGES;
|
|
471
|
+
exports.COUNTRY_TIMEZONE_MAP = entropy.COUNTRY_TIMEZONE_MAP;
|
|
472
|
+
exports.MIN_STRUCTURAL_ENTROPY = entropy.MIN_STRUCTURAL_ENTROPY;
|
|
473
|
+
exports.OPTIMAL_CHAR_ENTROPY_MAX = entropy.OPTIMAL_CHAR_ENTROPY_MAX;
|
|
474
|
+
exports.OPTIMAL_CHAR_ENTROPY_MIN = entropy.OPTIMAL_CHAR_ENTROPY_MIN;
|
|
475
|
+
exports.OPTIMAL_ENTROPY_MAX = entropy.OPTIMAL_ENTROPY_MAX;
|
|
476
|
+
exports.OPTIMAL_ENTROPY_MIN = entropy.OPTIMAL_ENTROPY_MIN;
|
|
477
|
+
exports.analyzeHeaderEntropy = entropy.analyzeHeaderEntropy;
|
|
478
|
+
exports.calculateKLDivergence = entropy.calculateKLDivergence;
|
|
479
|
+
exports.calculateShannonEntropy = entropy.calculateShannonEntropy;
|
|
480
|
+
exports.calculateStructuralEntropy = entropy.calculateStructuralEntropy;
|
|
481
|
+
exports.detectAllContradictions = entropy.detectAllContradictions;
|
|
482
|
+
exports.detectClientHintsContradictions = entropy.detectClientHintsContradictions;
|
|
483
|
+
exports.detectGeoChronoContradictions = entropy.detectGeoChronoContradictions;
|
|
484
|
+
exports.detectHardwareContradictions = entropy.detectHardwareContradictions;
|
|
485
|
+
exports.detectOSBrowserContradictions = entropy.detectOSBrowserContradictions;
|
|
4232
486
|
exports.BENCHMARK_METRICS_METADATA = BENCHMARK_METRICS_METADATA;
|
|
4233
|
-
exports.BROWSER_BASELINE_FREQUENCIES = BROWSER_BASELINE_FREQUENCIES;
|
|
4234
|
-
exports.CANONICAL_HEADER_ORDER = CANONICAL_HEADER_ORDER;
|
|
4235
|
-
exports.CHROME_VERSIONS = CHROME_VERSIONS;
|
|
4236
487
|
exports.COMPARATIVE_DATASET = COMPARATIVE_DATASET;
|
|
4237
|
-
exports.COUNTRY_LOCALES = COUNTRY_LOCALES;
|
|
4238
|
-
exports.COUNTRY_NAME_TO_CODE = COUNTRY_NAME_TO_CODE;
|
|
4239
|
-
exports.COUNTRY_PRIMARY_LANGUAGES = COUNTRY_PRIMARY_LANGUAGES;
|
|
4240
|
-
exports.COUNTRY_TIMEZONE_MAP = COUNTRY_TIMEZONE_MAP;
|
|
4241
|
-
exports.DEFAULT_ACCEPT_LANGUAGE = DEFAULT_ACCEPT_LANGUAGE;
|
|
4242
|
-
exports.DEFAULT_COUNTRY = DEFAULT_COUNTRY;
|
|
4243
|
-
exports.DEFAULT_LOCALE = DEFAULT_LOCALE;
|
|
4244
|
-
exports.DEFAULT_TIMEZONE = DEFAULT_TIMEZONE;
|
|
4245
|
-
exports.DevicePassportInstance = DevicePassportInstance;
|
|
4246
|
-
exports.EDGE_VERSIONS = EDGE_VERSIONS;
|
|
4247
|
-
exports.GEO_LOCALES = GEO_LOCALES;
|
|
4248
|
-
exports.JSONFileStorage = JSONFileStorage;
|
|
4249
|
-
exports.MACOS_PRESETS = MACOS_PRESETS;
|
|
4250
|
-
exports.MIN_STRUCTURAL_ENTROPY = MIN_STRUCTURAL_ENTROPY;
|
|
4251
|
-
exports.MemoryStorage = MemoryStorage;
|
|
4252
|
-
exports.OPTIMAL_CHAR_ENTROPY_MAX = OPTIMAL_CHAR_ENTROPY_MAX;
|
|
4253
|
-
exports.OPTIMAL_CHAR_ENTROPY_MIN = OPTIMAL_CHAR_ENTROPY_MIN;
|
|
4254
|
-
exports.OPTIMAL_ENTROPY_MAX = OPTIMAL_ENTROPY_MAX;
|
|
4255
|
-
exports.OPTIMAL_ENTROPY_MIN = OPTIMAL_ENTROPY_MIN;
|
|
4256
|
-
exports.PassportManager = PassportManager;
|
|
4257
|
-
exports.TIMEZONE_MATRIX = TIMEZONE_MATRIX;
|
|
4258
|
-
exports.TIMEZONE_TO_COUNTRY = TIMEZONE_TO_COUNTRY;
|
|
4259
488
|
exports.VERSION = VERSION;
|
|
4260
|
-
exports.WINDOWS_PRESETS = WINDOWS_PRESETS;
|
|
4261
|
-
exports.alignGeoContext = alignGeoContext;
|
|
4262
|
-
exports.analyzeHeaderEntropy = analyzeHeaderEntropy;
|
|
4263
|
-
exports.applyToPlaywrightContext = applyToPlaywrightContext;
|
|
4264
|
-
exports.applyToPuppeteerPage = applyToPuppeteerPage;
|
|
4265
|
-
exports.buildUserAgent = buildUserAgent;
|
|
4266
|
-
exports.calculateKLDivergence = calculateKLDivergence;
|
|
4267
|
-
exports.calculateShannonEntropy = calculateShannonEntropy;
|
|
4268
|
-
exports.calculateStructuralEntropy = calculateStructuralEntropy;
|
|
4269
|
-
exports.createDeterministicPRNG = createDeterministicPRNG;
|
|
4270
|
-
exports.createMulberry32 = createMulberry32;
|
|
4271
|
-
exports.createPassport = createPassport;
|
|
4272
|
-
exports.detectAllContradictions = detectAllContradictions;
|
|
4273
|
-
exports.detectClientHintsContradictions = detectClientHintsContradictions;
|
|
4274
|
-
exports.detectCountry = detectCountry;
|
|
4275
|
-
exports.detectGeoChronoContradictions = detectGeoChronoContradictions;
|
|
4276
|
-
exports.detectGeoFromUrl = detectGeoFromUrl;
|
|
4277
|
-
exports.detectHardwareContradictions = detectHardwareContradictions;
|
|
4278
|
-
exports.detectOSBrowserContradictions = detectOSBrowserContradictions;
|
|
4279
|
-
exports.evaluateCoherence = evaluateCoherence;
|
|
4280
|
-
exports.evaluateGeoAlignment = evaluateGeoAlignment;
|
|
4281
|
-
exports.evaluatePassport = evaluatePassport;
|
|
4282
|
-
exports.extractCountryFromProxyUrl = extractCountryFromProxyUrl;
|
|
4283
|
-
exports.fnv1a = fnv1a;
|
|
4284
|
-
exports.formatAcceptLanguage = formatAcceptLanguage;
|
|
4285
|
-
exports.formatCanonicalHeaders = formatCanonicalHeaders;
|
|
4286
489
|
exports.formatComparativeMatrixJSON = formatComparativeMatrixJSON;
|
|
4287
490
|
exports.formatComparativeMatrixLaTeX = formatComparativeMatrixLaTeX;
|
|
4288
491
|
exports.formatComparativeMatrixMarkdown = formatComparativeMatrixMarkdown;
|
|
4289
|
-
exports.formatStructuredBrandList = formatStructuredBrandList;
|
|
4290
|
-
exports.generateBrandVersions = generateBrandVersions;
|
|
4291
|
-
exports.generateDevicePassport = generateDevicePassport;
|
|
4292
|
-
exports.generatePassport = generatePassport;
|
|
4293
|
-
exports.generateUserAgentClientHints = generateUserAgentClientHints;
|
|
4294
|
-
exports.getAllSupportedCountries = getAllSupportedCountries;
|
|
4295
|
-
exports.getApifyHeadersIfAvailable = getApifyHeadersIfAvailable;
|
|
4296
|
-
exports.getCountryConfig = getCountryConfig;
|
|
4297
|
-
exports.getCountryForTimezone = getCountryForTimezone;
|
|
4298
|
-
exports.getDefaultTimezoneForCountry = getDefaultTimezoneForCountry;
|
|
4299
|
-
exports.getHardwarePresets = getHardwarePresets;
|
|
4300
|
-
exports.getLocaleProfile = getLocaleProfile;
|
|
4301
|
-
exports.getPlaywrightContext = getPlaywrightContext;
|
|
4302
|
-
exports.getPuppeteerArgs = getPuppeteerArgs;
|
|
4303
|
-
exports.getStaticFallbackHeaders = getStaticFallbackHeaders;
|
|
4304
|
-
exports.getStealthScript = getStealthScript;
|
|
4305
|
-
exports.getTimezoneOffsetMinutes = getTimezoneOffsetMinutes;
|
|
4306
|
-
exports.getTimezonesForCountry = getTimezonesForCountry;
|
|
4307
|
-
exports.hasCountryConfig = hasCountryConfig;
|
|
4308
|
-
exports.isGeoAligned = isGeoAligned;
|
|
4309
|
-
exports.isTimezoneAlignedWithCountry = isTimezoneAlignedWithCountry;
|
|
4310
|
-
exports.isValidCountryCode = isValidCountryCode;
|
|
4311
|
-
exports.isValidTimezone = isValidTimezone;
|
|
4312
|
-
exports.listAllTimezones = listAllTimezones;
|
|
4313
|
-
exports.murmur3 = murmur3;
|
|
4314
|
-
exports.normalizeCountryCode = normalizeCountryCode;
|
|
4315
|
-
exports.parseAcceptLanguage = parseAcceptLanguage;
|
|
4316
|
-
exports.parseStructuredBrandList = parseStructuredBrandList;
|
|
4317
492
|
exports.runComparativeBenchmark = runComparativeBenchmark;
|
|
4318
|
-
exports.toCurl = toCurl;
|
|
4319
|
-
exports.toGeoLocation = toGeoLocation;
|
|
4320
|
-
exports.toLocaleProfile = toLocaleProfile;
|
|
4321
|
-
exports.validateHardwareContradictions = validateHardwareContradictions;
|
|
4322
|
-
exports.withPassport = withPassport;
|