@fanboynz/network-scanner 1.0.64 → 1.0.66
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/lib/fingerprint.js +654 -1818
- package/lib/interaction.js +89 -21
- package/nwss.js +2 -2
- package/package.json +1 -1
package/lib/fingerprint.js
CHANGED
|
@@ -1,198 +1,106 @@
|
|
|
1
1
|
// === Enhanced Fingerprint Protection Module - Puppeteer 23.x Compatible ===
|
|
2
2
|
// This module handles advanced browser fingerprint spoofing, user agent changes,
|
|
3
3
|
// and comprehensive bot detection evasion techniques.
|
|
4
|
+
//const { applyErrorSuppression } = require('./error-suppression');
|
|
4
5
|
|
|
5
6
|
// Default values for fingerprint spoofing if not set to 'random'
|
|
6
7
|
const DEFAULT_PLATFORM = 'Win32';
|
|
7
8
|
const DEFAULT_TIMEZONE = 'America/New_York';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
const BUILT_IN_PROPERTIES = [
|
|
10
|
+
// Built-in properties that should not be modified
|
|
11
|
+
const BUILT_IN_PROPERTIES = new Set([
|
|
13
12
|
'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash',
|
|
14
|
-
'constructor', 'prototype', '__proto__', 'toString', 'valueOf',
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// User agent collections with latest versions
|
|
17
|
+
const USER_AGENT_COLLECTIONS = {
|
|
18
|
+
chrome: [
|
|
19
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
20
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
21
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
22
|
+
],
|
|
23
|
+
firefox: [
|
|
24
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
25
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
26
|
+
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
|
27
|
+
],
|
|
28
|
+
safari: [
|
|
29
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",
|
|
30
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Timezone configuration with offsets
|
|
35
|
+
const TIMEZONE_CONFIG = {
|
|
36
|
+
'America/New_York': { offset: 300, abbr: 'EST' },
|
|
37
|
+
'America/Los_Angeles': { offset: 480, abbr: 'PST' },
|
|
38
|
+
'Europe/London': { offset: 0, abbr: 'GMT' },
|
|
39
|
+
'America/Chicago': { offset: 360, abbr: 'CST' }
|
|
40
|
+
};
|
|
17
41
|
|
|
18
42
|
/**
|
|
19
|
-
*
|
|
43
|
+
* Safely defines a property with comprehensive error handling
|
|
20
44
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'].includes(property);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return BUILT_IN_PROPERTIES.includes(property);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Creates a safe property descriptor for Puppeteer 23.x
|
|
33
|
-
*/
|
|
34
|
-
function createSafeDescriptor(descriptor, existingDescriptor) {
|
|
35
|
-
const safeDescriptor = { ...descriptor };
|
|
36
|
-
|
|
37
|
-
// Always ensure configurable is true unless specifically set to false
|
|
38
|
-
if (safeDescriptor.configurable !== false) {
|
|
39
|
-
safeDescriptor.configurable = true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Preserve existing enumerable state if not specified
|
|
43
|
-
if (safeDescriptor.enumerable === undefined && existingDescriptor) {
|
|
44
|
-
safeDescriptor.enumerable = existingDescriptor.enumerable;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Ensure writable is properly set for data descriptors
|
|
48
|
-
if ('value' in safeDescriptor && safeDescriptor.writable === undefined) {
|
|
49
|
-
safeDescriptor.writable = true;
|
|
45
|
+
function safeDefineProperty(target, property, descriptor, options = {}) {
|
|
46
|
+
if (BUILT_IN_PROPERTIES.has(property)) {
|
|
47
|
+
if (options.debug) console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
48
|
+
return false;
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
return safeDescriptor;
|
|
53
|
-
}
|
|
54
50
|
|
|
55
|
-
/**
|
|
56
|
-
* Safely defines or redefines a property with error handling for Puppeteer 23.x
|
|
57
|
-
* @param {Object} target - Target object
|
|
58
|
-
* @param {string} property - Property name
|
|
59
|
-
* @param {Object} descriptor - Property descriptor
|
|
60
|
-
* @param {boolean} forceDebug - Debug logging flag
|
|
61
|
-
*/
|
|
62
|
-
function safeDefineProperty(target, property, descriptor, forceDebug = false) {
|
|
63
51
|
try {
|
|
64
|
-
|
|
65
|
-
if (
|
|
52
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
53
|
+
if (existing?.configurable === false) {
|
|
54
|
+
if (options.debug) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`);
|
|
66
55
|
return false;
|
|
67
56
|
}
|
|
68
|
-
const existingDescriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (forceDebug) {
|
|
75
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Check if property is truly non-configurable
|
|
81
|
-
if (existingDescriptor.configurable === false) {
|
|
82
|
-
if (forceDebug) {
|
|
83
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Enhanced descriptor validation
|
|
90
|
-
const safeDescriptor = createSafeDescriptor(descriptor, existingDescriptor);
|
|
91
|
-
|
|
92
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
58
|
+
Object.defineProperty(target, property, {
|
|
59
|
+
...descriptor,
|
|
60
|
+
configurable: true
|
|
61
|
+
});
|
|
93
62
|
return true;
|
|
94
|
-
} catch (
|
|
95
|
-
if (
|
|
96
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
97
|
-
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (options.debug) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
98
65
|
return false;
|
|
99
66
|
}
|
|
100
67
|
}
|
|
101
68
|
|
|
102
69
|
/**
|
|
103
|
-
*
|
|
104
|
-
* Consolidated from canSafelyModifyProperty to avoid duplication
|
|
105
|
-
*/
|
|
106
|
-
function canModifyProperty(target, property, forceDebug = false) {
|
|
107
|
-
try {
|
|
108
|
-
// COMPREHENSIVE DEBUG: Log every canModifyProperty check
|
|
109
|
-
if (forceDebug) {
|
|
110
|
-
console.log(`[fingerprint] canModifyProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
111
|
-
if (property === 'href') {
|
|
112
|
-
console.log(`[fingerprint] CRITICAL: Checking if href can be modified!`);
|
|
113
|
-
console.log(`[fingerprint] Target details:`, {
|
|
114
|
-
isWindow: target === window,
|
|
115
|
-
isLocation: target === window.location,
|
|
116
|
-
constructorName: target.constructor?.name
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check if it's a built-in property that shouldn't be modified
|
|
122
|
-
if (isBuiltInProperty(target, property)) {
|
|
123
|
-
if (forceDebug) {
|
|
124
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
125
|
-
}
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
130
|
-
if (!descriptor) return true; // Property doesn't exist, can be added
|
|
131
|
-
|
|
132
|
-
return descriptor.configurable !== false;
|
|
133
|
-
} catch (checkErr) {
|
|
134
|
-
if (forceDebug) {
|
|
135
|
-
console.log(`[fingerprint] Property check failed for ${property}: ${checkErr.message}`);
|
|
136
|
-
}
|
|
137
|
-
return false; // If we can't check, assume we can't modify
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Safely executes fingerprint spoofing code with comprehensive error handling
|
|
143
|
-
* @param {Function} spoofFunction - Function to execute
|
|
144
|
-
* @param {string} description - Description of the spoofing operation
|
|
145
|
-
* @param {boolean} forceDebug - Debug logging flag
|
|
70
|
+
* Safely executes spoofing operations with error handling
|
|
146
71
|
*/
|
|
147
|
-
function
|
|
72
|
+
function safeSpoofingExecution(spoofFunction, description, options = {}) {
|
|
148
73
|
try {
|
|
149
74
|
spoofFunction();
|
|
150
75
|
return true;
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
const isCriticalError = spoofErr.message.includes('Cannot redefine property') ||
|
|
154
|
-
spoofErr.message.includes('non-configurable') ||
|
|
155
|
-
spoofErr.message.includes('Invalid property descriptor');
|
|
156
|
-
|
|
157
|
-
if (forceDebug) {
|
|
158
|
-
const errorLevel = isCriticalError ? 'CRITICAL' : 'WARNING';
|
|
159
|
-
console.log(`[fingerprint] ${errorLevel} - ${description} failed: ${spoofErr.message}`);
|
|
160
|
-
}
|
|
161
|
-
// Continue execution - don't let spoofing failures break the scan
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (options.debug) console.log(`[fingerprint] ${description} failed: ${err.message}`);
|
|
162
78
|
return false;
|
|
163
79
|
}
|
|
164
80
|
}
|
|
165
81
|
|
|
166
82
|
/**
|
|
167
83
|
* Generates realistic screen resolutions based on common monitor sizes
|
|
168
|
-
* @returns {object} Screen resolution object with width and height
|
|
169
84
|
*/
|
|
170
85
|
function getRealisticScreenResolution() {
|
|
171
86
|
const commonResolutions = [
|
|
172
|
-
{ width: 1920, height: 1080 },
|
|
173
|
-
{ width: 1366, height: 768 },
|
|
174
|
-
{ width: 1440, height: 900 },
|
|
175
|
-
{ width: 1536, height: 864 },
|
|
176
|
-
{ width: 1600, height: 900 },
|
|
177
|
-
{ width: 2560, height: 1440 },
|
|
178
|
-
{ width: 1280, height: 720 },
|
|
179
|
-
{ width: 3440, height: 1440 }
|
|
87
|
+
{ width: 1920, height: 1080 },
|
|
88
|
+
{ width: 1366, height: 768 },
|
|
89
|
+
{ width: 1440, height: 900 },
|
|
90
|
+
{ width: 1536, height: 864 },
|
|
91
|
+
{ width: 1600, height: 900 },
|
|
92
|
+
{ width: 2560, height: 1440 },
|
|
93
|
+
{ width: 1280, height: 720 },
|
|
94
|
+
{ width: 3440, height: 1440 }
|
|
180
95
|
];
|
|
181
|
-
|
|
182
96
|
return commonResolutions[Math.floor(Math.random() * commonResolutions.length)];
|
|
183
97
|
}
|
|
184
98
|
|
|
185
99
|
/**
|
|
186
|
-
* Generates
|
|
187
|
-
* This is used to spoof various navigator and screen properties to make
|
|
188
|
-
* the headless browser instance appear more like a regular user's browser
|
|
189
|
-
* and bypass fingerprint-based bot detection.
|
|
190
|
-
*
|
|
191
|
-
* @returns {object} An object containing the spoofed fingerprint properties
|
|
100
|
+
* Generates randomized but realistic browser fingerprint values
|
|
192
101
|
*/
|
|
193
102
|
function getRandomFingerprint() {
|
|
194
103
|
const resolution = getRealisticScreenResolution();
|
|
195
|
-
|
|
196
104
|
return {
|
|
197
105
|
deviceMemory: [4, 8, 16, 32][Math.floor(Math.random() * 4)],
|
|
198
106
|
hardwareConcurrency: [2, 4, 6, 8, 12, 16][Math.floor(Math.random() * 6)],
|
|
@@ -200,7 +108,7 @@ function getRandomFingerprint() {
|
|
|
200
108
|
width: resolution.width,
|
|
201
109
|
height: resolution.height,
|
|
202
110
|
availWidth: resolution.width,
|
|
203
|
-
availHeight: resolution.height - 40,
|
|
111
|
+
availHeight: resolution.height - 40,
|
|
204
112
|
colorDepth: 24,
|
|
205
113
|
pixelDepth: 24
|
|
206
114
|
},
|
|
@@ -213,1178 +121,432 @@ function getRandomFingerprint() {
|
|
|
213
121
|
}
|
|
214
122
|
|
|
215
123
|
/**
|
|
216
|
-
*
|
|
217
|
-
* Compatible with Puppeteer 23.x
|
|
218
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
219
|
-
* @param {object} siteConfig - The site configuration object
|
|
220
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
221
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
222
|
-
* @returns {Promise<void>}
|
|
124
|
+
* Creates mock Chrome runtime objects
|
|
223
125
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
126
|
+
function createMockChromeRuntime() {
|
|
127
|
+
return {
|
|
128
|
+
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
129
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
130
|
+
sendMessage: () => {},
|
|
131
|
+
connect: () => ({
|
|
132
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
133
|
+
postMessage: () => {},
|
|
134
|
+
disconnect: () => {}
|
|
135
|
+
}),
|
|
136
|
+
getManifest: () => ({ name: "Chrome", version: "131.0.0.0" }),
|
|
137
|
+
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
138
|
+
id: undefined
|
|
139
|
+
};
|
|
140
|
+
}
|
|
226
141
|
|
|
227
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Generates realistic Chrome loadTimes data
|
|
144
|
+
*/
|
|
145
|
+
function generateRealisticLoadTimes() {
|
|
146
|
+
const now = performance.now();
|
|
147
|
+
return {
|
|
148
|
+
commitLoadTime: now - Math.random() * 1000,
|
|
149
|
+
connectionInfo: 'http/1.1',
|
|
150
|
+
finishDocumentLoadTime: now - Math.random() * 500,
|
|
151
|
+
finishLoadTime: now - Math.random() * 100,
|
|
152
|
+
firstPaintAfterLoadTime: now - Math.random() * 50,
|
|
153
|
+
firstPaintTime: now - Math.random() * 200,
|
|
154
|
+
navigationType: 'Navigation',
|
|
155
|
+
npnNegotiatedProtocol: 'unknown',
|
|
156
|
+
requestTime: now - Math.random() * 2000,
|
|
157
|
+
startLoadTime: now - Math.random() * 1500,
|
|
158
|
+
wasAlternateProtocolAvailable: false,
|
|
159
|
+
wasFetchedViaSpdy: false,
|
|
160
|
+
wasNpnNegotiated: false
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generates Chrome CSI data
|
|
166
|
+
*/
|
|
167
|
+
function generateCSIData() {
|
|
168
|
+
return {
|
|
169
|
+
onloadT: Date.now(),
|
|
170
|
+
pageT: Math.random() * 1000,
|
|
171
|
+
startE: Date.now() - Math.random() * 2000,
|
|
172
|
+
tran: Math.floor(Math.random() * 20)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates mock fingerprinting objects
|
|
178
|
+
*/
|
|
179
|
+
function createFingerprintMocks() {
|
|
180
|
+
const mockResult = {
|
|
181
|
+
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
182
|
+
confidence: { score: 0.99 },
|
|
183
|
+
components: {
|
|
184
|
+
screen: { value: { width: 1920, height: 1080 } },
|
|
185
|
+
timezone: { value: DEFAULT_TIMEZONE },
|
|
186
|
+
language: { value: 'en-US' }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
fp: {
|
|
192
|
+
getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult,
|
|
193
|
+
get: (callback) => Promise.resolve(mockResult),
|
|
194
|
+
load: () => Promise.resolve(window.fp),
|
|
195
|
+
components: { screen: { value: { width: 1920, height: 1080 } } },
|
|
196
|
+
x64hash128: () => 'mock_hash',
|
|
197
|
+
tz: DEFAULT_TIMEZONE,
|
|
198
|
+
timezone: DEFAULT_TIMEZONE
|
|
199
|
+
},
|
|
200
|
+
FingerprintJS: {
|
|
201
|
+
load: () => Promise.resolve({
|
|
202
|
+
get: () => Promise.resolve(mockResult)
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
ClientJS: function() {
|
|
206
|
+
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substr(2, 9);
|
|
207
|
+
this.getBrowser = () => 'Chrome';
|
|
208
|
+
this.getOS = () => 'Windows';
|
|
209
|
+
this.fp = {};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Applies timezone spoofing
|
|
216
|
+
*/
|
|
217
|
+
function applyTimezoneSpoofing(timezone, options = {}) {
|
|
218
|
+
const tzConfig = TIMEZONE_CONFIG[timezone] || TIMEZONE_CONFIG[DEFAULT_TIMEZONE];
|
|
219
|
+
|
|
220
|
+
// Spoof Intl.DateTimeFormat
|
|
221
|
+
if (window.Intl?.DateTimeFormat) {
|
|
222
|
+
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
223
|
+
window.Intl.DateTimeFormat = function(...args) {
|
|
224
|
+
const instance = new OriginalDateTimeFormat(...args);
|
|
225
|
+
const originalResolvedOptions = instance.resolvedOptions;
|
|
226
|
+
|
|
227
|
+
instance.resolvedOptions = function() {
|
|
228
|
+
const opts = originalResolvedOptions.call(this);
|
|
229
|
+
opts.timeZone = timezone;
|
|
230
|
+
return opts;
|
|
231
|
+
};
|
|
232
|
+
return instance;
|
|
233
|
+
};
|
|
234
|
+
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
235
|
+
}
|
|
228
236
|
|
|
229
|
-
//
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
|
234
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
235
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
236
|
-
],
|
|
237
|
-
firefox: [
|
|
238
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
239
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
240
|
-
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
|
241
|
-
],
|
|
242
|
-
safari: [
|
|
243
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",
|
|
244
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"
|
|
245
|
-
]
|
|
237
|
+
// Spoof Date.getTimezoneOffset
|
|
238
|
+
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
|
239
|
+
Date.prototype.getTimezoneOffset = function() {
|
|
240
|
+
return tzConfig.offset;
|
|
246
241
|
};
|
|
247
242
|
|
|
248
|
-
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enhanced user agent spoofing with stealth protection
|
|
248
|
+
*/
|
|
249
|
+
async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
250
|
+
if (!siteConfig.userAgent) return;
|
|
251
|
+
|
|
252
|
+
if (forceDebug) console.log(`[debug] User agent spoofing: ${siteConfig.userAgent}`);
|
|
253
|
+
|
|
254
|
+
const selectedUserAgents = USER_AGENT_COLLECTIONS[siteConfig.userAgent.toLowerCase()];
|
|
249
255
|
const ua = selectedUserAgents ? selectedUserAgents[Math.floor(Math.random() * selectedUserAgents.length)] : null;
|
|
250
256
|
|
|
251
257
|
if (ua) {
|
|
252
258
|
await page.setUserAgent(ua);
|
|
253
259
|
|
|
254
|
-
|
|
255
|
-
if (forceDebug) console.log(`[debug] Applying enhanced stealth protection for ${currentUrl}`);
|
|
260
|
+
if (forceDebug) console.log(`[debug] Applying stealth protection for ${currentUrl}`);
|
|
256
261
|
|
|
257
262
|
try {
|
|
258
263
|
await page.evaluateOnNewDocument((userAgent, debugEnabled) => {
|
|
259
264
|
|
|
260
|
-
//
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return true;
|
|
265
|
-
} catch (spoofErr) {
|
|
266
|
-
if (debugEnabled) {
|
|
267
|
-
console.log(`[fingerprint] ${description} failed: ${spoofErr.message}`);
|
|
268
|
-
}
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Helper function for safe property definition (local scope)
|
|
274
|
-
function safeDefineProperty(target, property, descriptor) {
|
|
275
|
-
try {
|
|
276
|
-
// COMPREHENSIVE DEBUG: Log every property definition attempt
|
|
277
|
-
if (debugEnabled) {
|
|
278
|
-
console.log(`[fingerprint] safeDefineProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
279
|
-
if (property === 'href') {
|
|
280
|
-
console.log(`[fingerprint] CRITICAL: Attempting to define href property!`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Enhanced built-in property protection using global constant
|
|
285
|
-
if (property === 'href' || ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'].includes(property)) {
|
|
286
|
-
if (debugEnabled) {
|
|
287
|
-
console.log(`[fingerprint] BLOCKED: Built-in property ${property} blocked in safeDefineProperty`);
|
|
288
|
-
}
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const existingDescriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
293
|
-
|
|
294
|
-
if (existingDescriptor && existingDescriptor.configurable === false) {
|
|
295
|
-
if (debugEnabled) {
|
|
296
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const safeDescriptor = {
|
|
302
|
-
...descriptor,
|
|
303
|
-
configurable: true // Always use configurable: true for Puppeteer 23.x compatibility
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
307
|
-
return true;
|
|
308
|
-
} catch (defineErr) {
|
|
309
|
-
if (debugEnabled) {
|
|
310
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
311
|
-
}
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Validate properties before attempting to modify them (local scope)
|
|
317
|
-
function canModifyProperty(target, property) {
|
|
318
|
-
try {
|
|
319
|
-
// COMPREHENSIVE DEBUG: Log every canModifyProperty check
|
|
320
|
-
if (debugEnabled) {
|
|
321
|
-
console.log(`[fingerprint] canModifyProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
322
|
-
if (property === 'href') {
|
|
323
|
-
console.log(`[fingerprint] CRITICAL: Checking if href can be modified!`);
|
|
324
|
-
console.log(`[fingerprint] Target details:`, {
|
|
325
|
-
isWindow: target === window,
|
|
326
|
-
isLocation: target === window.location,
|
|
327
|
-
constructorName: target.constructor?.name
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Enhanced built-in property check with location object handling
|
|
333
|
-
const builtInProperties = ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'];
|
|
334
|
-
|
|
335
|
-
// Special handling for Location object properties
|
|
336
|
-
if (target === window.location || (target.constructor && target.constructor.name === 'Location')) {
|
|
337
|
-
const locationProperties = ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'];
|
|
338
|
-
if (locationProperties.includes(property)) {
|
|
339
|
-
if (debugEnabled) {
|
|
340
|
-
console.log(`[fingerprint] Skipping location property: ${property}`);
|
|
341
|
-
}
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (builtInProperties.includes(property)) {
|
|
347
|
-
if (debugEnabled) {
|
|
348
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
349
|
-
}
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
354
|
-
if (!descriptor) return true; // Property doesn't exist, can be added
|
|
355
|
-
|
|
356
|
-
return descriptor.configurable !== false;
|
|
357
|
-
} catch (checkErr) {
|
|
358
|
-
return false; // If we can't check, assume we can't modify
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// GLOBAL HREF PROTECTION: Override Object.defineProperty to block ALL href modifications
|
|
363
|
-
const originalDefineProperty = Object.defineProperty;
|
|
364
|
-
Object.defineProperty = function(target, property, descriptor) {
|
|
365
|
-
// Block ALL attempts to redefine href anywhere
|
|
366
|
-
if (property === 'href') {
|
|
367
|
-
if (debugEnabled) {
|
|
368
|
-
console.log(`[fingerprint] GLOBAL BLOCK: Prevented ${property} redefinition on:`, target.constructor?.name || 'unknown');
|
|
369
|
-
console.trace('[fingerprint] Call stack for blocked href:');
|
|
370
|
-
}
|
|
371
|
-
// Return false to indicate failure, but don't throw
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
265
|
+
// Apply inline error suppression first
|
|
266
|
+
(function() {
|
|
267
|
+
const originalConsoleError = console.error;
|
|
268
|
+
const originalWindowError = window.onerror;
|
|
374
269
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
270
|
+
function shouldSuppressFingerprintError(message) {
|
|
271
|
+
const patterns = [
|
|
272
|
+
/\.closest is not a function/i,
|
|
273
|
+
/\.querySelector is not a function/i,
|
|
274
|
+
/\.addEventListener is not a function/i,
|
|
275
|
+
/Cannot read propert(y|ies) of null \\(reading 'fp'\\)/i,
|
|
276
|
+
/Cannot read propert(y|ies) of undefined \\(reading 'fp'\\)/i,
|
|
277
|
+
/Cannot redefine property: href/i,
|
|
278
|
+
/Cannot redefine property: __webdriver_script_func/i,
|
|
279
|
+
/Cannot redefine property: webdriver/i,
|
|
280
|
+
/Cannot read propert(y|ies) of undefined \\(reading 'toLowerCase'\\)/i,
|
|
281
|
+
/\\.toLowerCase is not a function/i,
|
|
282
|
+
/fp is not defined/i,
|
|
283
|
+
/fingerprint is not defined/i,
|
|
284
|
+
/FingerprintJS is not defined/i,
|
|
285
|
+
/\\$ is not defined/i,
|
|
286
|
+
/jQuery is not defined/i,
|
|
287
|
+
/_ is not defined/i,
|
|
288
|
+
/Failed to load resource.*server responded with a status of [45]\\d{2}/i,
|
|
289
|
+
/Failed to fetch/i,
|
|
290
|
+
/(webdriver|callPhantom|_phantom|__nightmare|_selenium) is not defined/i,
|
|
291
|
+
/Failed to execute 'observe' on 'IntersectionObserver'.*parameter 1 is not of type 'Element'/i,
|
|
292
|
+
/tz check/i,
|
|
293
|
+
/new window\\.Error.*<anonymous>/i,
|
|
294
|
+
/Failed to load resource.*server responded with a status of 40[34]/i,
|
|
295
|
+
/Blocked script execution in 'about:blank'.*sandboxed.*allow-scripts/i,
|
|
296
|
+
/Page JavaScript error:/i,
|
|
297
|
+
/^[a-zA-Z0-9_$]+\[.*\]\s+is not a function/i,
|
|
298
|
+
/^[a-zA-Z0-9_$]+\(.*\)\s+is not a function/i,
|
|
299
|
+
/^[a-zA-Z0-9_$]+\.[a-zA-Z0-9_$]+.*is not a function/i
|
|
300
|
+
];
|
|
301
|
+
return patterns.some(pattern => pattern.test(String(message || '')));
|
|
382
302
|
}
|
|
383
303
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log(`[fingerprint] Original defineProperty failed for ${property}:`, err.message);
|
|
304
|
+
console.error = function(...args) {
|
|
305
|
+
const message = args.join(' ');
|
|
306
|
+
if (shouldSuppressFingerprintError(message)) {
|
|
307
|
+
if (debugEnabled) console.log("[fingerprint] Suppressed error:", message);
|
|
308
|
+
return;
|
|
390
309
|
}
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (!target) {
|
|
399
|
-
if (debugEnabled) {
|
|
400
|
-
console.log(`[fingerprint] NULL TARGET: Prevented property access on null target for property: ${property}`);
|
|
310
|
+
return originalConsoleError.apply(this, arguments);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
window.onerror = function(message, source, lineno, colno, error) {
|
|
314
|
+
if (shouldSuppressFingerprintError(message)) {
|
|
315
|
+
if (debugEnabled) console.log("[fingerprint] Suppressed window error:", message);
|
|
316
|
+
return true;
|
|
401
317
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// Add safe property access wrapper
|
|
408
|
-
window.safePropertyAccess = function(obj, property, defaultValue = null) {
|
|
409
|
-
try {
|
|
410
|
-
return (obj && obj[property] !== undefined) ? obj[property] : defaultValue;
|
|
411
|
-
} catch (err) {
|
|
412
|
-
return defaultValue;
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
// Enhanced null object protection for property access
|
|
417
|
-
const originalHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
418
|
-
Object.prototype.hasOwnProperty = function(property) {
|
|
419
|
-
if (this === null || this === undefined) {
|
|
420
|
-
if (debugEnabled && property === 'fp') {
|
|
421
|
-
console.log(`[fingerprint] NULL OBJECT: Prevented hasOwnProperty('${property}') on null/undefined`);
|
|
318
|
+
if (originalWindowError) {
|
|
319
|
+
return originalWindowError.apply(this, arguments);
|
|
422
320
|
}
|
|
423
321
|
return false;
|
|
424
|
-
}
|
|
425
|
-
return originalHasOwnProperty.call(this, property);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// Enhanced undefined object protection for method access
|
|
429
|
-
const originalPropertyAccess = Object.getOwnPropertyDescriptor;
|
|
430
|
-
|
|
431
|
-
// Global protection against undefined method access
|
|
432
|
-
const createSafeMethodProxy = (methodName) => {
|
|
433
|
-
return function(...args) {
|
|
434
|
-
if (debugEnabled) {
|
|
435
|
-
console.log(`[fingerprint] Safe ${methodName} proxy called with args:`, args);
|
|
436
|
-
}
|
|
437
|
-
// For alert specifically, show the message safely
|
|
438
|
-
if (methodName === 'alert' && args.length > 0) {
|
|
439
|
-
try {
|
|
440
|
-
window.alert(args[0]);
|
|
441
|
-
} catch (alertErr) {
|
|
442
|
-
if (debugEnabled) {
|
|
443
|
-
console.log(`[fingerprint] Safe alert failed, logging instead: ${args[0]}`);
|
|
444
|
-
}
|
|
445
|
-
console.log(`[Alert] ${args[0]}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// For other methods, just return a safe empty function
|
|
449
|
-
return undefined;
|
|
450
322
|
};
|
|
451
|
-
};
|
|
323
|
+
})();
|
|
452
324
|
|
|
453
|
-
// Create safe
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
window.safePrompt = createSafeMethodProxy('prompt');
|
|
457
|
-
|
|
458
|
-
// Global error handler for fingerprinting access errors
|
|
459
|
-
const originalErrorHandler = window.onerror;
|
|
460
|
-
window.onerror = function(message, source, lineno, colno, error) {
|
|
461
|
-
// Handle fingerprinting-related null access errors
|
|
462
|
-
if (typeof message === 'string' && (
|
|
463
|
-
message.includes("Cannot read properties of null (reading 'fp')") ||
|
|
464
|
-
message.includes("Cannot read property 'fp' of null") ||
|
|
465
|
-
message.includes("Cannot read properties of undefined (reading 'alert')") ||
|
|
466
|
-
message.includes("Cannot read property 'alert' of undefined") ||
|
|
467
|
-
message.includes("Cannot read properties of undefined (reading 'confirm')") ||
|
|
468
|
-
message.includes("Cannot read properties of undefined (reading 'prompt')") ||
|
|
469
|
-
message.includes("fp is not defined") ||
|
|
470
|
-
message.includes("alert is not defined") ||
|
|
471
|
-
message.includes("is not a function") ||
|
|
472
|
-
message.includes("Cannot read properties of undefined (reading") ||
|
|
473
|
-
message.includes("Cannot read properties of null (reading") ||
|
|
474
|
-
message.includes("fingerprint") && message.includes("null")
|
|
475
|
-
)) {
|
|
476
|
-
if (debugEnabled) {
|
|
477
|
-
console.log(`[fingerprint] Suppressed fingerprinting null access error: ${message}`);
|
|
478
|
-
}
|
|
479
|
-
return true; // Prevent error from showing in console
|
|
480
|
-
}
|
|
325
|
+
// Create safe property definition helper
|
|
326
|
+
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
327
|
+
const builtInProps = new Set(['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace']);
|
|
481
328
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return
|
|
329
|
+
if (builtInProps.has(property)) {
|
|
330
|
+
if (debugEnabled) console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
331
|
+
return false;
|
|
485
332
|
}
|
|
486
|
-
return false;
|
|
487
|
-
};
|
|
488
333
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// Override Error constructor to prevent CDP detection
|
|
496
|
-
window.Error = function(...args) {
|
|
497
|
-
const error = new OriginalError(...args);
|
|
498
|
-
|
|
499
|
-
// Get original stack descriptor to preserve behavior
|
|
500
|
-
const originalStackDescriptor = Object.getOwnPropertyDescriptor(error, 'stack') ||
|
|
501
|
-
Object.getOwnPropertyDescriptor(OriginalError.prototype, 'stack');
|
|
502
|
-
|
|
503
|
-
// Override stack property to prevent CDP detection via Error.stack getter
|
|
504
|
-
Object.defineProperty(error, 'stack', {
|
|
505
|
-
get: function() {
|
|
506
|
-
// This is the critical part - prevent CDP detection flag from being set
|
|
507
|
-
// Anti-bot systems set a flag when Error.stack getter is accessed
|
|
508
|
-
|
|
509
|
-
let stack;
|
|
510
|
-
if (originalStackDescriptor && originalStackDescriptor.get) {
|
|
511
|
-
try {
|
|
512
|
-
stack = originalStackDescriptor.get.call(this);
|
|
513
|
-
} catch (stackErr) {
|
|
514
|
-
stack = 'Error\n at unknown location';
|
|
515
|
-
}
|
|
516
|
-
} else if (originalStackDescriptor && originalStackDescriptor.value) {
|
|
517
|
-
stack = originalStackDescriptor.value;
|
|
518
|
-
} else {
|
|
519
|
-
// Fallback stack trace
|
|
520
|
-
stack = `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Clean automation traces from stack if present
|
|
524
|
-
if (typeof stack === 'string') {
|
|
525
|
-
stack = stack
|
|
526
|
-
.replace(/.*puppeteer.*\n?/gi, '')
|
|
527
|
-
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
528
|
-
.replace(/.*webdriver.*\n?/gi, '')
|
|
529
|
-
.replace(/.*automation.*\n?/gi, '')
|
|
530
|
-
.replace(/\n\s*\n/g, '\n')
|
|
531
|
-
.trim();
|
|
532
|
-
|
|
533
|
-
// Ensure we always have a valid stack
|
|
534
|
-
if (!stack) {
|
|
535
|
-
stack = `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return stack;
|
|
540
|
-
},
|
|
541
|
-
set: function(value) {
|
|
542
|
-
// Allow stack to be set normally
|
|
543
|
-
if (originalStackDescriptor && originalStackDescriptor.set) {
|
|
544
|
-
originalStackDescriptor.set.call(this, value);
|
|
545
|
-
} else {
|
|
546
|
-
// Create internal property if no setter exists
|
|
547
|
-
Object.defineProperty(this, '_internalStack', {
|
|
548
|
-
value: value,
|
|
549
|
-
writable: true,
|
|
550
|
-
configurable: true
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
configurable: true,
|
|
555
|
-
enumerable: false
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
return error;
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
// Preserve Error prototype and constructor properties
|
|
562
|
-
window.Error.prototype = OriginalError.prototype;
|
|
563
|
-
Object.setPrototypeOf(window.Error, OriginalError);
|
|
564
|
-
|
|
565
|
-
// Copy essential static properties
|
|
566
|
-
['captureStackTrace', 'stackTraceLimit', 'prepareStackTrace'].forEach(prop => {
|
|
567
|
-
if (OriginalError[prop]) {
|
|
568
|
-
try {
|
|
569
|
-
window.Error[prop] = OriginalError[prop];
|
|
570
|
-
} catch (propErr) {
|
|
571
|
-
// Ignore if property can't be copied
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// Enhanced Error.captureStackTrace protection
|
|
577
|
-
if (OriginalError.captureStackTrace) {
|
|
578
|
-
window.Error.captureStackTrace = function(targetObject, constructorOpt) {
|
|
579
|
-
try {
|
|
580
|
-
const result = OriginalError.captureStackTrace.call(this, targetObject, constructorOpt);
|
|
581
|
-
|
|
582
|
-
// Clean captured stack trace
|
|
583
|
-
if (targetObject && targetObject.stack && typeof targetObject.stack === 'string') {
|
|
584
|
-
targetObject.stack = targetObject.stack
|
|
585
|
-
.replace(/.*puppeteer.*\n?/gi, '')
|
|
586
|
-
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
587
|
-
.replace(/.*webdriver.*\n?/gi, '')
|
|
588
|
-
.replace(/.*automation.*\n?/gi, '')
|
|
589
|
-
.replace(/\n\s*\n/g, '\n')
|
|
590
|
-
.trim();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return result;
|
|
594
|
-
} catch (captureErr) {
|
|
595
|
-
if (debugEnabled) {
|
|
596
|
-
console.log('[fingerprint] captureStackTrace error handled:', captureErr.message);
|
|
597
|
-
}
|
|
598
|
-
return undefined;
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Prevent global CDP detection flag
|
|
604
|
-
try {
|
|
605
|
-
Object.defineProperty(window, 'cdpDetected', {
|
|
606
|
-
get: () => false,
|
|
607
|
-
set: () => false,
|
|
608
|
-
configurable: false,
|
|
609
|
-
enumerable: false
|
|
610
|
-
});
|
|
611
|
-
} catch (cdpPropErr) {
|
|
612
|
-
// Ignore if property already exists
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Additional protection: prevent common CDP detection patterns
|
|
616
|
-
const cdpDetectionStrings = ['cdpDetected', 'chromeDevtools', 'runtimeEvaluate'];
|
|
617
|
-
cdpDetectionStrings.forEach(str => {
|
|
618
|
-
try {
|
|
619
|
-
if (!window[str]) {
|
|
620
|
-
Object.defineProperty(window, str, {
|
|
621
|
-
get: () => false,
|
|
622
|
-
set: () => false,
|
|
623
|
-
configurable: false,
|
|
624
|
-
enumerable: false
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
} catch (strErr) {
|
|
628
|
-
// Ignore property definition errors
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
if (debugEnabled) {
|
|
633
|
-
console.log('[fingerprint] Simplified CDP detection prevention installed - Error constructor protected');
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
}, 'Simplified CDP Detection Prevention');
|
|
637
|
-
|
|
638
|
-
// Test function for CDP detection (only in debug mode)
|
|
639
|
-
if (debugEnabled) {
|
|
640
|
-
try {
|
|
641
|
-
let testCdpDetected = false;
|
|
642
|
-
const testError = new Error('test');
|
|
643
|
-
|
|
644
|
-
// Simulate anti-bot CDP detection attempt
|
|
645
|
-
Object.defineProperty(testError, 'stack', {
|
|
646
|
-
get() {
|
|
647
|
-
testCdpDetected = true;
|
|
648
|
-
return 'test stack trace';
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// Access stack - should NOT trigger detection with our patch
|
|
653
|
-
const testStack = testError.stack;
|
|
654
|
-
|
|
655
|
-
console.log(`[fingerprint] CDP protection test: ${testCdpDetected ? 'FAILED - Detection triggered!' : 'PASSED - Protection working'}`);
|
|
656
|
-
} catch (testErr) {
|
|
657
|
-
console.log('[fingerprint] CDP test error:', testErr.message);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// COMPREHENSIVE FINGERPRINTING MOCK OBJECTS
|
|
662
|
-
// Create enhanced mock fingerprinting objects that might be expected
|
|
663
|
-
window.fp = window.fp || {
|
|
664
|
-
getResult: (callback) => {
|
|
665
|
-
const result = {
|
|
666
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
667
|
-
confidence: { score: 0.99 },
|
|
668
|
-
components: {
|
|
669
|
-
screen: { value: { width: 1920, height: 1080 } },
|
|
670
|
-
timezone: { value: 'America/New_York' },
|
|
671
|
-
language: { value: 'en-US' }
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
if (typeof callback === 'function') {
|
|
675
|
-
try {
|
|
676
|
-
setTimeout(() => callback(result), 0);
|
|
677
|
-
} catch (callbackErr) {
|
|
678
|
-
if (debugEnabled) console.log(`[fingerprint] FP callback error: ${callbackErr.message}`);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return result;
|
|
682
|
-
},
|
|
683
|
-
get: (callback) => {
|
|
684
|
-
const result = {
|
|
685
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
686
|
-
confidence: { score: 0.99 },
|
|
687
|
-
components: {}
|
|
688
|
-
};
|
|
689
|
-
if (typeof callback === 'function') {
|
|
690
|
-
try {
|
|
691
|
-
setTimeout(() => callback(result), 0);
|
|
692
|
-
} catch (callbackErr) {
|
|
693
|
-
if (debugEnabled) console.log(`[fingerprint] FP.get callback error: ${callbackErr.message}`);
|
|
694
|
-
}
|
|
334
|
+
try {
|
|
335
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
336
|
+
if (existing?.configurable === false) {
|
|
337
|
+
if (debugEnabled) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`);
|
|
338
|
+
return false;
|
|
695
339
|
}
|
|
696
|
-
return Promise.resolve(result);
|
|
697
|
-
},
|
|
698
|
-
load: () => Promise.resolve(window.fp),
|
|
699
|
-
components: {
|
|
700
|
-
screen: { value: { width: 1920, height: 1080 } }
|
|
701
|
-
},
|
|
702
|
-
x64hash128: () => 'mock_hash',
|
|
703
|
-
tz: 'America/New_York', // Mock timezone
|
|
704
|
-
timezone: 'America/New_York'
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
// Enhanced timezone protection - create comprehensive timezone objects
|
|
708
|
-
window.timezone = window.timezone || 'America/New_York';
|
|
709
|
-
// Create comprehensive FingerprintJS mock objects
|
|
710
|
-
window.FingerprintJS = window.FingerprintJS || {
|
|
711
|
-
load: (options) => Promise.resolve({
|
|
712
|
-
get: (getOptions) => Promise.resolve({
|
|
713
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
714
|
-
confidence: { score: 0.99 },
|
|
715
|
-
components: {}
|
|
716
|
-
})
|
|
717
|
-
})
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
// Mock other common fingerprinting libraries that might access .fp
|
|
721
|
-
window.ClientJS = window.ClientJS || function() {
|
|
722
|
-
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substring(7);
|
|
723
|
-
this.getBrowser = () => 'Chrome';
|
|
724
|
-
this.getOS = () => 'Windows';
|
|
725
|
-
this.fp = {}; // Prevent null access
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
// Create safe proxy wrapper for fingerprinting objects
|
|
729
|
-
const createFingerprintProxy = (targetName) => {
|
|
730
|
-
if (!window[targetName]) {
|
|
731
|
-
window[targetName] = new Proxy({}, {
|
|
732
|
-
get: (target, prop) => {
|
|
733
|
-
if (debugEnabled && prop === 'fp') {
|
|
734
|
-
console.log(`[fingerprint] Safe proxy accessed: ${targetName}.fp`);
|
|
735
|
-
}
|
|
736
|
-
if (prop === 'fp') {
|
|
737
|
-
return {}; // Return safe empty object for .fp access
|
|
738
|
-
}
|
|
739
|
-
// Handle common method access on undefined objects
|
|
740
|
-
if (prop === 'alert') {
|
|
741
|
-
return window.safeAlert;
|
|
742
|
-
}
|
|
743
|
-
if (prop === 'confirm') {
|
|
744
|
-
return window.safeConfirm;
|
|
745
|
-
}
|
|
746
|
-
if (prop === 'prompt') {
|
|
747
|
-
return window.safePrompt;
|
|
748
|
-
}
|
|
749
|
-
// Return safe empty function for other method-like properties
|
|
750
|
-
if (typeof target[prop] === 'undefined' && prop.endsWith && (prop.endsWith('alert') || prop.endsWith('confirm') || prop.endsWith('prompt'))) {
|
|
751
|
-
return createSafeMethodProxy(prop);
|
|
752
|
-
}
|
|
753
|
-
return target[prop] || undefined;
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
// Create safe proxies for common fingerprinting object names
|
|
760
|
-
['fpjs', 'fingerprint', 'deviceFingerprint', 'browserFingerprint', 'fpCollector'].forEach(createFingerprintProxy);
|
|
761
340
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (!window[objName]) {
|
|
766
|
-
window[objName] = new Proxy({}, {
|
|
767
|
-
get: (target, prop) => {
|
|
768
|
-
if (debugEnabled && ['alert', 'confirm', 'prompt'].includes(prop)) {
|
|
769
|
-
console.log(`[fingerprint] Safe proxy method accessed: ${objName}.${prop}`);
|
|
770
|
-
}
|
|
771
|
-
if (prop === 'alert') {
|
|
772
|
-
return window.safeAlert;
|
|
773
|
-
}
|
|
774
|
-
if (prop === 'confirm') {
|
|
775
|
-
return window.safeConfirm;
|
|
776
|
-
}
|
|
777
|
-
if (prop === 'prompt') {
|
|
778
|
-
return window.safePrompt;
|
|
779
|
-
}
|
|
780
|
-
// Return safe function for any other method access
|
|
781
|
-
return () => undefined;
|
|
782
|
-
}
|
|
341
|
+
Object.defineProperty(target, property, {
|
|
342
|
+
...descriptor,
|
|
343
|
+
configurable: true
|
|
783
344
|
});
|
|
784
|
-
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
// Global protection for undefined objects accessing dialog methods
|
|
788
|
-
const originalAlert = window.alert;
|
|
789
|
-
const originalConfirm = window.confirm;
|
|
790
|
-
const originalPrompt = window.prompt;
|
|
791
|
-
|
|
792
|
-
// Ensure these methods are always available and safe
|
|
793
|
-
window.alert = window.alert || function(message) {
|
|
794
|
-
console.log(`[Alert] ${message}`);
|
|
795
|
-
};
|
|
796
|
-
window.confirm = window.confirm || function(message) {
|
|
797
|
-
console.log(`[Confirm] ${message}`);
|
|
798
|
-
return false;
|
|
799
|
-
};
|
|
800
|
-
window.prompt = window.prompt || function(message, defaultValue) {
|
|
801
|
-
console.log(`[Prompt] ${message}`);
|
|
802
|
-
return defaultValue || null;
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
window.timeZone = window.timeZone || 'America/New_York';
|
|
807
|
-
|
|
808
|
-
// Comprehensive Date prototype protection
|
|
809
|
-
const originalDateToString = Date.prototype.toString;
|
|
810
|
-
Date.prototype.toString = function() {
|
|
811
|
-
try {
|
|
812
|
-
return originalDateToString.call(this);
|
|
345
|
+
return true;
|
|
813
346
|
} catch (err) {
|
|
814
|
-
|
|
347
|
+
if (debugEnabled) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
348
|
+
return false;
|
|
815
349
|
}
|
|
816
|
-
}
|
|
350
|
+
}
|
|
817
351
|
|
|
818
|
-
|
|
819
|
-
|
|
352
|
+
// Safe execution wrapper
|
|
353
|
+
function safeExecute(fn, description) {
|
|
820
354
|
try {
|
|
821
|
-
|
|
355
|
+
fn();
|
|
356
|
+
return true;
|
|
822
357
|
} catch (err) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
// Override navigator.timezone related properties
|
|
828
|
-
if (navigator && !navigator.timezone) {
|
|
829
|
-
Object.defineProperty(navigator, 'timezone', {
|
|
830
|
-
get: () => 'America/New_York',
|
|
831
|
-
configurable: true
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Protect common timezone detection methods
|
|
836
|
-
window.getTimezone = window.getTimezone || (() => 'America/New_York');
|
|
837
|
-
window.getTimezoneOffset = window.getTimezoneOffset || (() => 300);
|
|
838
|
-
|
|
839
|
-
// Create jstz-like object (common timezone detection library)
|
|
840
|
-
window.jstz = window.jstz || {
|
|
841
|
-
determine: () => ({ name: () => 'America/New_York' }),
|
|
842
|
-
olson: { timezones: { 'America/New_York': true } }
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
// Create mock timezone object
|
|
846
|
-
window.tz = window.tz || {
|
|
847
|
-
name: 'America/New_York',
|
|
848
|
-
offset: -300,
|
|
849
|
-
abbr: 'EST',
|
|
850
|
-
dst: false,
|
|
851
|
-
getTimezoneOffset: () => 300
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// Ensure Intl.DateTimeFormat resolvedOptions returns safe values
|
|
855
|
-
if (window.Intl && window.Intl.DateTimeFormat) {
|
|
856
|
-
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
857
|
-
window.Intl.DateTimeFormat = function(...args) {
|
|
858
|
-
const instance = new OriginalDateTimeFormat(...args);
|
|
859
|
-
const originalResolvedOptions = instance.resolvedOptions;
|
|
860
|
-
|
|
861
|
-
instance.resolvedOptions = function() {
|
|
862
|
-
try {
|
|
863
|
-
const options = originalResolvedOptions.call(this);
|
|
864
|
-
// Ensure timezone is always set to prevent null errors
|
|
865
|
-
if (!options.timeZone) {
|
|
866
|
-
options.timeZone = 'America/New_York';
|
|
867
|
-
}
|
|
868
|
-
return options;
|
|
869
|
-
} catch (err) {
|
|
870
|
-
// Return safe default options if resolvedOptions fails
|
|
871
|
-
return {
|
|
872
|
-
locale: 'en-US',
|
|
873
|
-
timeZone: 'America/New_York',
|
|
874
|
-
calendar: 'gregory',
|
|
875
|
-
numberingSystem: 'latn'
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
return instance;
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
// Copy static methods
|
|
883
|
-
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
// JavaScript Library Protection - Create mock objects for common libraries
|
|
888
|
-
// jQuery protection
|
|
889
|
-
if (typeof window.$ === 'undefined') {
|
|
890
|
-
window.$ = function(selector) {
|
|
891
|
-
// Return a minimal jQuery-like object
|
|
892
|
-
return {
|
|
893
|
-
ready: function(fn) { if (typeof fn === 'function') setTimeout(fn, 0); return this; },
|
|
894
|
-
on: function() { return this; },
|
|
895
|
-
off: function() { return this; },
|
|
896
|
-
click: function() { return this; },
|
|
897
|
-
hide: function() { return this; },
|
|
898
|
-
show: function() { return this; },
|
|
899
|
-
css: function() { return this; },
|
|
900
|
-
attr: function() { return this; },
|
|
901
|
-
html: function() { return this; },
|
|
902
|
-
text: function() { return this; },
|
|
903
|
-
val: function() { return this; },
|
|
904
|
-
addClass: function() { return this; },
|
|
905
|
-
removeClass: function() { return this; },
|
|
906
|
-
length: 0,
|
|
907
|
-
each: function() { return this; }
|
|
908
|
-
};
|
|
909
|
-
};
|
|
910
|
-
// Common jQuery aliases
|
|
911
|
-
window.jQuery = window.$;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Other common library protections
|
|
915
|
-
window._ = window._ || { // Lodash/Underscore
|
|
916
|
-
forEach: function() {},
|
|
917
|
-
map: function() { return []; },
|
|
918
|
-
filter: function() { return []; },
|
|
919
|
-
find: function() { return undefined; }
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
window.moment = window.moment || function() { // Moment.js
|
|
923
|
-
return {
|
|
924
|
-
format: function() { return new Date().toISOString(); },
|
|
925
|
-
valueOf: function() { return Date.now(); }
|
|
926
|
-
};
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
// Enhanced console error handling for library errors
|
|
930
|
-
const originalConsoleError = console.error;
|
|
931
|
-
console.error = function(...args) {
|
|
932
|
-
const message = args.join(' ');
|
|
933
|
-
// Suppress common library error messages
|
|
934
|
-
if (typeof message === 'string' && (
|
|
935
|
-
message.includes('$ is not defined') ||
|
|
936
|
-
message.includes('jQuery is not defined') ||
|
|
937
|
-
message.includes('_ is not defined') ||
|
|
938
|
-
message.includes('moment is not defined') ||
|
|
939
|
-
message.includes('bootstrap is not defined') ||
|
|
940
|
-
message.includes('is not a function') ||
|
|
941
|
-
message.includes('Cannot read property') ||
|
|
942
|
-
message.includes('Cannot read properties of undefined') ||
|
|
943
|
-
message.includes('Cannot read properties of null') ||
|
|
944
|
-
message.includes('.closest is not a function') ||
|
|
945
|
-
message.includes('.toLowerCase') ||
|
|
946
|
-
message.includes('is not valid JSON')
|
|
947
|
-
)) {
|
|
948
|
-
if (debugEnabled) {
|
|
949
|
-
console.log(`[fingerprint] Suppressed library error: ${message}`);
|
|
950
|
-
}
|
|
951
|
-
return; // Don't log the error
|
|
952
|
-
}
|
|
953
|
-
// For all other errors, use original console.error
|
|
954
|
-
return originalConsoleError.apply(this, arguments);
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
// Enhanced TZ Check Protection - Handle timezone validation functions
|
|
958
|
-
window.tzCheck = window.tzCheck || function() {
|
|
959
|
-
return 'America/New_York';
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
window.checkTimezone = window.checkTimezone || function() {
|
|
963
|
-
return true;
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
window.validateTimezone = window.validateTimezone || function() {
|
|
967
|
-
return { valid: true, timezone: 'America/New_York' };
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
// Mock common timezone libraries and their methods
|
|
971
|
-
window.momentTimezone = window.momentTimezone || {
|
|
972
|
-
tz: {
|
|
973
|
-
guess: () => 'America/New_York',
|
|
974
|
-
names: () => ['America/New_York'],
|
|
975
|
-
zone: () => ({ name: 'America/New_York', abbr: 'EST' })
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
// Enhanced Intl timezone protection
|
|
980
|
-
if (window.Intl) {
|
|
981
|
-
// Override supportedLocalesOf to always return safe values
|
|
982
|
-
if (window.Intl.DateTimeFormat && window.Intl.DateTimeFormat.supportedLocalesOf) {
|
|
983
|
-
const originalSupportedLocales = window.Intl.DateTimeFormat.supportedLocalesOf;
|
|
984
|
-
window.Intl.DateTimeFormat.supportedLocalesOf = function(locales, options) {
|
|
985
|
-
try {
|
|
986
|
-
return originalSupportedLocales.call(this, locales, options);
|
|
987
|
-
} catch (err) {
|
|
988
|
-
return ['en-US'];
|
|
989
|
-
}
|
|
990
|
-
};
|
|
358
|
+
if (debugEnabled) console.log(`[fingerprint] ${description} failed: ${err.message}`);
|
|
359
|
+
return false;
|
|
991
360
|
}
|
|
992
|
-
|
|
993
|
-
// Add resolvedOptions protection to all Intl objects
|
|
994
|
-
['DateTimeFormat', 'NumberFormat', 'Collator'].forEach(intlType => {
|
|
995
|
-
if (window.Intl[intlType]) {
|
|
996
|
-
const OriginalIntl = window.Intl[intlType];
|
|
997
|
-
window.Intl[intlType] = function(...args) {
|
|
998
|
-
try {
|
|
999
|
-
return new OriginalIntl(...args);
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
// Return basic mock object for any Intl constructor failures
|
|
1002
|
-
return {
|
|
1003
|
-
resolvedOptions: () => ({
|
|
1004
|
-
locale: 'en-US',
|
|
1005
|
-
timeZone: 'America/New_York',
|
|
1006
|
-
calendar: 'gregory'
|
|
1007
|
-
}),
|
|
1008
|
-
format: () => new Date().toLocaleDateString()
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
|
-
Object.setPrototypeOf(window.Intl[intlType], OriginalIntl);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
361
|
}
|
|
1016
|
-
|
|
1017
|
-
// Global error handler enhancement for timezone-specific errors
|
|
1018
|
-
const originalWindowError = window.onerror;
|
|
1019
|
-
window.onerror = function(message, source, lineno, colno, error) {
|
|
1020
|
-
// Handle timezone-specific errors
|
|
1021
|
-
if (typeof message === 'string' && (
|
|
1022
|
-
message.includes('tz check') ||
|
|
1023
|
-
message.includes('timezone check') ||
|
|
1024
|
-
message.includes('tz is not defined') ||
|
|
1025
|
-
message.includes('timezone is not defined') ||
|
|
1026
|
-
message.includes('Invalid timezone') ||
|
|
1027
|
-
message.includes('TimeZone') ||
|
|
1028
|
-
message.includes('getTimezoneOffset')
|
|
1029
|
-
)) {
|
|
1030
|
-
if (debugEnabled) {
|
|
1031
|
-
console.log(`[fingerprint] Suppressed timezone error: ${message}`);
|
|
1032
|
-
}
|
|
1033
|
-
return true; // Prevent the error from showing
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Call original error handler for non-timezone errors
|
|
1037
|
-
if (originalWindowError) {
|
|
1038
|
-
return originalWindowError.apply(this, arguments);
|
|
1039
|
-
}
|
|
1040
|
-
return false;
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
// 1. Enhanced webdriver removal with safe descriptor manipulation
|
|
1046
|
-
safeExecuteSpoofing(() => {
|
|
1047
|
-
// Skip if navigator.webdriver is non-configurable
|
|
1048
|
-
if (!canModifyProperty(navigator, 'webdriver', debugEnabled)) {
|
|
1049
|
-
if (debugEnabled) {
|
|
1050
|
-
console.log('[fingerprint] Skipping non-configurable navigator.webdriver');
|
|
1051
|
-
}
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
362
|
|
|
363
|
+
// Remove webdriver properties
|
|
364
|
+
safeExecute(() => {
|
|
1055
365
|
try {
|
|
1056
366
|
delete navigator.webdriver;
|
|
1057
|
-
} catch (
|
|
1058
|
-
|
|
1059
|
-
try {
|
|
1060
|
-
navigator.webdriver = undefined;
|
|
1061
|
-
} catch (assignErr) {
|
|
1062
|
-
// Both deletion and assignment failed, skip
|
|
1063
|
-
if (debugEnabled) {
|
|
1064
|
-
console.log('[fingerprint] Cannot modify navigator.webdriver, skipping');
|
|
1065
|
-
}
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
safeDefineProperty(navigator, 'webdriver', {
|
|
1071
|
-
get: () => undefined,
|
|
1072
|
-
enumerable: false,
|
|
1073
|
-
configurable: true
|
|
1074
|
-
}, debugEnabled);
|
|
367
|
+
} catch (e) {}
|
|
368
|
+
safeDefinePropertyLocal(navigator, 'webdriver', { get: () => undefined });
|
|
1075
369
|
}, 'webdriver removal');
|
|
1076
|
-
|
|
1077
|
-
//
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
'spawn', 'emit', 'Buffer', 'domAutomation',
|
|
1085
|
-
'domAutomationController', '__lastWatirAlert', '__lastWatirConfirm',
|
|
1086
|
-
'__lastWatirPrompt', '_Selenium_IDE_Recorder', '_selenium', 'calledSelenium'
|
|
1087
|
-
];
|
|
1088
|
-
|
|
1089
|
-
safeExecuteSpoofing(() => {
|
|
1090
|
-
automationProps.forEach(prop => {
|
|
370
|
+
|
|
371
|
+
// Remove automation properties
|
|
372
|
+
safeExecute(() => {
|
|
373
|
+
const automationProps = [
|
|
374
|
+
'callPhantom', '_phantom', '__nightmare', '_selenium', '__selenium_unwrapped',
|
|
375
|
+
'__webdriver_evaluate', '__driver_evaluate', '__webdriver_script_function',
|
|
376
|
+
'spawn', 'emit', 'Buffer', 'domAutomation', 'domAutomationController'
|
|
377
|
+
];
|
|
1091
378
|
|
|
1092
|
-
|
|
1093
|
-
if (debugEnabled && (prop === 'href' || prop.includes('href'))) {
|
|
1094
|
-
console.log(`[fingerprint] WARNING: Processing href-related property: ${prop}`);
|
|
1095
|
-
}
|
|
379
|
+
automationProps.forEach(prop => {
|
|
1096
380
|
try {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// Additional safety check specifically for href
|
|
1106
|
-
if (prop === 'href') {
|
|
1107
|
-
if (debugEnabled) console.log(`[fingerprint] BLOCKING href property modification attempt`);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
if (!canModifyProperty(window, prop, debugEnabled) && !canModifyProperty(navigator, prop, debugEnabled)) {
|
|
1112
|
-
if (debugEnabled) {
|
|
1113
|
-
console.log(`[fingerprint] Skipping non-configurable automation property ${prop}`);
|
|
1114
|
-
}
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// Try to delete from both window and navigator
|
|
1119
|
-
try { delete window[prop]; } catch(e) {}
|
|
1120
|
-
try { delete navigator[prop]; } catch(e) {}
|
|
1121
|
-
|
|
1122
|
-
// Only try to redefine if we can
|
|
1123
|
-
if (canModifyProperty(window, prop, debugEnabled)) {
|
|
1124
|
-
safeDefineProperty(window, prop, {
|
|
1125
|
-
get: () => undefined,
|
|
1126
|
-
enumerable: false
|
|
1127
|
-
}, debugEnabled);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if (canModifyProperty(navigator, prop, debugEnabled)) {
|
|
1131
|
-
safeDefineProperty(navigator, prop, {
|
|
1132
|
-
get: () => undefined,
|
|
1133
|
-
enumerable: false
|
|
1134
|
-
}, debugEnabled);
|
|
1135
|
-
}
|
|
1136
|
-
} catch (propErr) {
|
|
1137
|
-
// Skip problematic properties
|
|
1138
|
-
if (debugEnabled) {
|
|
1139
|
-
console.log(`[fingerprint] Skipped automation property ${prop}: ${propErr.message}`);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
381
|
+
delete window[prop];
|
|
382
|
+
delete navigator[prop];
|
|
383
|
+
safeDefinePropertyLocal(window, prop, { get: () => undefined });
|
|
384
|
+
safeDefinePropertyLocal(navigator, prop, { get: () => undefined });
|
|
385
|
+
} catch (e) {}
|
|
1142
386
|
});
|
|
1143
387
|
}, 'automation properties removal');
|
|
1144
388
|
|
|
1145
|
-
//
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (!window.chrome || !window.chrome.runtime) {
|
|
389
|
+
// Simulate Chrome runtime
|
|
390
|
+
safeExecute(() => {
|
|
391
|
+
if (!window.chrome?.runtime) {
|
|
1149
392
|
window.chrome = {
|
|
1150
393
|
runtime: {
|
|
1151
394
|
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
1152
395
|
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
1153
396
|
sendMessage: () => {},
|
|
1154
|
-
connect: () => ({
|
|
1155
|
-
|
|
1156
|
-
postMessage: () => {},
|
|
1157
|
-
disconnect: () => {}
|
|
1158
|
-
}),
|
|
1159
|
-
getManifest: () => ({
|
|
1160
|
-
name: "Chrome",
|
|
1161
|
-
version: "131.0.0.0"
|
|
1162
|
-
}),
|
|
397
|
+
connect: () => ({ onMessage: { addListener: () => {}, removeListener: () => {} }, postMessage: () => {}, disconnect: () => {} }),
|
|
398
|
+
getManifest: () => ({ name: "Chrome", version: "131.0.0.0" }),
|
|
1163
399
|
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
1164
400
|
id: undefined
|
|
1165
401
|
},
|
|
1166
|
-
loadTimes: () =>
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
startLoadTime: performance.now() - Math.random() * 1500,
|
|
1177
|
-
wasAlternateProtocolAvailable: false,
|
|
1178
|
-
wasFetchedViaSpdy: false,
|
|
1179
|
-
wasNpnNegotiated: false
|
|
1180
|
-
}),
|
|
402
|
+
loadTimes: () => {
|
|
403
|
+
const now = performance.now();
|
|
404
|
+
return {
|
|
405
|
+
commitLoadTime: now - Math.random() * 1000,
|
|
406
|
+
connectionInfo: 'http/1.1',
|
|
407
|
+
finishDocumentLoadTime: now - Math.random() * 500,
|
|
408
|
+
finishLoadTime: now - Math.random() * 100,
|
|
409
|
+
navigationType: 'Navigation'
|
|
410
|
+
};
|
|
411
|
+
},
|
|
1181
412
|
csi: () => ({
|
|
1182
413
|
onloadT: Date.now(),
|
|
1183
414
|
pageT: Math.random() * 1000,
|
|
1184
|
-
startE: Date.now() - Math.random() * 2000
|
|
1185
|
-
|
|
1186
|
-
}),
|
|
1187
|
-
app: {
|
|
1188
|
-
isInstalled: false,
|
|
1189
|
-
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
|
1190
|
-
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
|
|
1191
|
-
}
|
|
415
|
+
startE: Date.now() - Math.random() * 2000
|
|
416
|
+
})
|
|
1192
417
|
};
|
|
1193
418
|
}
|
|
1194
419
|
}, 'Chrome runtime simulation');
|
|
1195
|
-
|
|
1196
|
-
//
|
|
1197
|
-
|
|
1198
|
-
const isChrome = userAgent.includes('Chrome');
|
|
1199
|
-
const isFirefox = userAgent.includes('Firefox');
|
|
1200
|
-
const isSafari = userAgent.includes('Safari') && !userAgent.includes('Chrome');
|
|
1201
|
-
|
|
420
|
+
|
|
421
|
+
// Spoof plugins based on user agent
|
|
422
|
+
safeExecute(() => {
|
|
1202
423
|
let plugins = [];
|
|
1203
|
-
if (
|
|
424
|
+
if (userAgent.includes('Chrome')) {
|
|
1204
425
|
plugins = [
|
|
1205
|
-
{ name: 'Chrome PDF Plugin',
|
|
1206
|
-
{ name: 'Chrome PDF Viewer',
|
|
1207
|
-
{ name: 'Native Client', length: 2, description: 'Native Client Executable', filename: 'internal-nacl-plugin' }
|
|
426
|
+
{ name: 'Chrome PDF Plugin', description: 'Portable Document Format', filename: 'internal-pdf-viewer' },
|
|
427
|
+
{ name: 'Chrome PDF Viewer', description: 'PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' }
|
|
1208
428
|
];
|
|
1209
|
-
} else if (
|
|
429
|
+
} else if (userAgent.includes('Firefox')) {
|
|
1210
430
|
plugins = [
|
|
1211
|
-
{ name: 'PDF.js',
|
|
1212
|
-
];
|
|
1213
|
-
} else if (isSafari) {
|
|
1214
|
-
plugins = [
|
|
1215
|
-
{ name: 'WebKit built-in PDF', length: 1, description: 'Portable Document Format', filename: 'internal-pdf-viewer' }
|
|
431
|
+
{ name: 'PDF.js', description: 'Portable Document Format', filename: 'internal-pdf-js' }
|
|
1216
432
|
];
|
|
1217
433
|
}
|
|
1218
|
-
|
|
1219
|
-
safeDefineProperty(navigator, 'plugins', {
|
|
1220
|
-
get: () => plugins
|
|
1221
|
-
}, debugEnabled);
|
|
434
|
+
safeDefinePropertyLocal(navigator, 'plugins', { get: () => plugins });
|
|
1222
435
|
}, 'plugins spoofing');
|
|
1223
|
-
|
|
1224
|
-
//
|
|
1225
|
-
|
|
1226
|
-
const languages = ['en-US', 'en'];
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
get: () => languages
|
|
1230
|
-
}, debugEnabled);
|
|
1231
|
-
|
|
1232
|
-
safeDefineProperty(navigator, 'language', {
|
|
1233
|
-
get: () => languages[0]
|
|
1234
|
-
}, debugEnabled);
|
|
436
|
+
|
|
437
|
+
// Spoof languages
|
|
438
|
+
safeExecute(() => {
|
|
439
|
+
const languages = ['en-US', 'en'];
|
|
440
|
+
safeDefinePropertyLocal(navigator, 'languages', { get: () => languages });
|
|
441
|
+
safeDefinePropertyLocal(navigator, 'language', { get: () => languages[0] });
|
|
1235
442
|
}, 'language spoofing');
|
|
1236
|
-
|
|
1237
|
-
//
|
|
1238
|
-
|
|
1239
|
-
const isFirefox = userAgent.includes('Firefox');
|
|
1240
|
-
const isSafari = userAgent.includes('Safari') && !userAgent.includes('Chrome');
|
|
1241
|
-
|
|
443
|
+
|
|
444
|
+
// Spoof vendor information
|
|
445
|
+
safeExecute(() => {
|
|
1242
446
|
let vendor = 'Google Inc.';
|
|
1243
447
|
let product = 'Gecko';
|
|
1244
448
|
|
|
1245
|
-
if (
|
|
449
|
+
if (userAgent.includes('Firefox')) {
|
|
1246
450
|
vendor = '';
|
|
1247
|
-
|
|
1248
|
-
} else if (isSafari) {
|
|
451
|
+
} else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
|
|
1249
452
|
vendor = 'Apple Computer, Inc.';
|
|
1250
|
-
product = 'Gecko';
|
|
1251
453
|
}
|
|
1252
454
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
}, debugEnabled);
|
|
1256
|
-
|
|
1257
|
-
safeDefineProperty(navigator, 'product', {
|
|
1258
|
-
get: () => product
|
|
1259
|
-
}, debugEnabled);
|
|
455
|
+
safeDefinePropertyLocal(navigator, 'vendor', { get: () => vendor });
|
|
456
|
+
safeDefinePropertyLocal(navigator, 'product', { get: () => product });
|
|
1260
457
|
}, 'vendor/product spoofing');
|
|
1261
|
-
|
|
1262
|
-
//
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
{ type: 'application/pdf', description: 'Portable Document Format', suffixes: 'pdf', enabledPlugin: navigator.plugins[0] },
|
|
1271
|
-
{ type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', suffixes: 'pdf', enabledPlugin: navigator.plugins[1] },
|
|
1272
|
-
{ type: 'application/x-nacl', description: 'Native Client Executable', suffixes: '', enabledPlugin: navigator.plugins[2] }
|
|
1273
|
-
];
|
|
1274
|
-
}
|
|
1275
|
-
return [];
|
|
1276
|
-
}
|
|
1277
|
-
}, debugEnabled);
|
|
1278
|
-
}, 'mimeTypes spoofing');
|
|
1279
|
-
|
|
1280
|
-
// 8. Enhanced permission API spoofing with safe handling
|
|
1281
|
-
safeExecuteSpoofing(() => {
|
|
1282
|
-
if (navigator.permissions && navigator.permissions.query) {
|
|
1283
|
-
const originalQuery = navigator.permissions.query;
|
|
1284
|
-
navigator.permissions.query = function(parameters) {
|
|
1285
|
-
const granted = ['camera', 'microphone', 'notifications'];
|
|
1286
|
-
const denied = ['midi', 'push', 'speaker'];
|
|
1287
|
-
const prompt = ['geolocation'];
|
|
1288
|
-
|
|
1289
|
-
if (granted.includes(parameters.name)) {
|
|
1290
|
-
return Promise.resolve({ state: 'granted', onchange: null });
|
|
1291
|
-
} else if (denied.includes(parameters.name)) {
|
|
1292
|
-
return Promise.resolve({ state: 'denied', onchange: null });
|
|
1293
|
-
} else if (prompt.includes(parameters.name)) {
|
|
1294
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
1295
|
-
}
|
|
1296
|
-
return originalQuery.apply(this, arguments);
|
|
1297
|
-
};
|
|
458
|
+
|
|
459
|
+
// Spoof MIME types
|
|
460
|
+
safeExecute(() => {
|
|
461
|
+
let mimeTypes = [];
|
|
462
|
+
if (userAgent.includes('Chrome')) {
|
|
463
|
+
mimeTypes = [
|
|
464
|
+
{ type: 'application/pdf', description: 'Portable Document Format', suffixes: 'pdf' },
|
|
465
|
+
{ type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', suffixes: 'pdf' }
|
|
466
|
+
];
|
|
1298
467
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
468
|
+
safeDefinePropertyLocal(navigator, 'mimeTypes', { get: () => mimeTypes });
|
|
469
|
+
}, 'mimeTypes spoofing');
|
|
470
|
+
|
|
471
|
+
// Enhanced Error.stack protection for CDP detection
|
|
472
|
+
safeExecute(() => {
|
|
473
|
+
const OriginalError = window.Error;
|
|
474
|
+
window.Error = function(...args) {
|
|
475
|
+
const error = new OriginalError(...args);
|
|
476
|
+
const originalStack = error.stack;
|
|
477
|
+
|
|
478
|
+
Object.defineProperty(error, 'stack', {
|
|
1306
479
|
get: function() {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
automationProps.forEach(prop => {
|
|
1315
|
-
try {
|
|
1316
|
-
// Use the same built-in property check for iframe context
|
|
1317
|
-
if (!canModifyProperty(win, prop, debugEnabled)) return;
|
|
1318
|
-
delete win[prop];
|
|
1319
|
-
safeDefineProperty(win, prop, {
|
|
1320
|
-
get: () => undefined,
|
|
1321
|
-
enumerable: false
|
|
1322
|
-
}, debugEnabled);
|
|
1323
|
-
} catch(e) {}
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
return win;
|
|
1327
|
-
}
|
|
1328
|
-
}, debugEnabled);
|
|
1329
|
-
}
|
|
1330
|
-
}, 'iframe contentWindow spoofing');
|
|
1331
|
-
|
|
1332
|
-
// 10. Enhanced connection information spoofing with safe handling
|
|
1333
|
-
safeExecuteSpoofing(() => {
|
|
1334
|
-
if (navigator.connection) {
|
|
1335
|
-
// Check if connection properties can be modified
|
|
1336
|
-
try {
|
|
1337
|
-
const connectionTest = Object.getOwnPropertyDescriptor(navigator.connection, 'rtt');
|
|
1338
|
-
if (connectionTest && connectionTest.configurable === false) {
|
|
1339
|
-
if (debugEnabled) {
|
|
1340
|
-
console.log('[fingerprint] Connection properties are non-configurable, skipping');
|
|
480
|
+
if (typeof originalStack === 'string') {
|
|
481
|
+
return originalStack
|
|
482
|
+
.replace(/.*puppeteer.*\n?/gi, '')
|
|
483
|
+
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
484
|
+
.replace(/.*webdriver.*\n?/gi, '')
|
|
485
|
+
.replace(/.*automation.*\n?/gi, '')
|
|
486
|
+
.trim() || `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
1341
487
|
}
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
488
|
+
return originalStack;
|
|
489
|
+
},
|
|
490
|
+
configurable: true
|
|
491
|
+
});
|
|
492
|
+
return error;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
window.Error.prototype = OriginalError.prototype;
|
|
496
|
+
Object.setPrototypeOf(window.Error, OriginalError);
|
|
497
|
+
|
|
498
|
+
// Copy static properties
|
|
499
|
+
['captureStackTrace', 'stackTraceLimit', 'prepareStackTrace'].forEach(prop => {
|
|
500
|
+
if (OriginalError[prop]) {
|
|
501
|
+
try { window.Error[prop] = OriginalError[prop]; } catch (e) {}
|
|
1349
502
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
503
|
+
});
|
|
504
|
+
}, 'Error stack protection');
|
|
505
|
+
|
|
506
|
+
// Create fingerprinting mock objects
|
|
507
|
+
safeExecute(() => {
|
|
508
|
+
const mockResult = {
|
|
509
|
+
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
510
|
+
confidence: { score: 0.99 },
|
|
511
|
+
components: {
|
|
512
|
+
screen: { value: { width: 1920, height: 1080 } },
|
|
513
|
+
timezone: { value: 'America/New_York' }
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
window.fp = window.fp || {
|
|
518
|
+
getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult,
|
|
519
|
+
get: (callback) => Promise.resolve(mockResult),
|
|
520
|
+
load: () => Promise.resolve(window.fp)
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
window.FingerprintJS = window.FingerprintJS || {
|
|
524
|
+
load: () => Promise.resolve({ get: () => Promise.resolve(mockResult) })
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
window.ClientJS = window.ClientJS || function() {
|
|
528
|
+
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substr(2, 9);
|
|
529
|
+
};
|
|
530
|
+
}, 'fingerprinting mocks');
|
|
531
|
+
|
|
532
|
+
// WebGL spoofing
|
|
533
|
+
safeExecute(() => {
|
|
1367
534
|
if (window.WebGLRenderingContext) {
|
|
1368
535
|
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
1369
536
|
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
1370
|
-
if (parameter === 37445)
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
if (parameter === 37446) { // UNMASKED_RENDERER_WEBGL
|
|
1374
|
-
return 'Intel Iris OpenGL Engine';
|
|
1375
|
-
}
|
|
537
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
538
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1376
539
|
return getParameter.call(this, parameter);
|
|
1377
540
|
};
|
|
1378
541
|
}
|
|
1379
|
-
}, 'WebGL
|
|
1380
|
-
|
|
1381
|
-
//
|
|
1382
|
-
|
|
542
|
+
}, 'WebGL spoofing');
|
|
543
|
+
|
|
544
|
+
// Canvas fingerprinting protection
|
|
545
|
+
safeExecute(() => {
|
|
1383
546
|
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1384
547
|
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
1385
548
|
const context = this.getContext('2d');
|
|
1386
549
|
if (context) {
|
|
1387
|
-
// Add subtle noise to canvas to prevent fingerprinting
|
|
1388
550
|
const imageData = context.getImageData(0, 0, this.width, this.height);
|
|
1389
551
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
1390
552
|
imageData.data[i] = imageData.data[i] + Math.floor(Math.random() * 3) - 1;
|
|
@@ -1393,361 +555,157 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
1393
555
|
}
|
|
1394
556
|
return originalToDataURL.apply(this, args);
|
|
1395
557
|
};
|
|
1396
|
-
}, 'canvas fingerprinting');
|
|
1397
|
-
|
|
1398
|
-
//
|
|
1399
|
-
|
|
1400
|
-
if (Error.captureStackTrace) {
|
|
1401
|
-
const originalCaptureStackTrace = Error.captureStackTrace;
|
|
1402
|
-
Error.captureStackTrace = function(targetObject, constructorOpt) {
|
|
1403
|
-
const result = originalCaptureStackTrace.call(this, targetObject, constructorOpt);
|
|
1404
|
-
if (targetObject.stack) {
|
|
1405
|
-
// Remove puppeteer-related stack traces
|
|
1406
|
-
targetObject.stack = targetObject.stack
|
|
1407
|
-
.split('\n')
|
|
1408
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('DevTools') && !line.includes('chrome-devtools'))
|
|
1409
|
-
.join('\n');
|
|
1410
|
-
}
|
|
1411
|
-
return result;
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
}, 'stack trace cleaning');
|
|
1415
|
-
|
|
1416
|
-
// 14. Patch toString methods with safe handling
|
|
1417
|
-
safeExecuteSpoofing(() => {
|
|
1418
|
-
Function.prototype.toString = new Proxy(Function.prototype.toString, {
|
|
1419
|
-
apply: function(target, thisArg, argumentsList) {
|
|
1420
|
-
const result = target.apply(thisArg, argumentsList);
|
|
1421
|
-
return result.replace(/puppeteer/gi, 'browser').replace(/headless/gi, 'chrome');
|
|
1422
|
-
}
|
|
1423
|
-
});
|
|
1424
|
-
}, 'toString method patching');
|
|
1425
|
-
|
|
1426
|
-
// 15. Spoof battery API with safe handling
|
|
1427
|
-
safeExecuteSpoofing(() => {
|
|
558
|
+
}, 'canvas fingerprinting protection');
|
|
559
|
+
|
|
560
|
+
// Battery API spoofing
|
|
561
|
+
safeExecute(() => {
|
|
1428
562
|
if (navigator.getBattery) {
|
|
1429
|
-
const originalGetBattery = navigator.getBattery;
|
|
1430
563
|
navigator.getBattery = function() {
|
|
1431
564
|
return Promise.resolve({
|
|
1432
565
|
charging: Math.random() > 0.5,
|
|
1433
566
|
chargingTime: Math.random() > 0.5 ? Infinity : Math.random() * 3600,
|
|
1434
567
|
dischargingTime: Math.random() * 7200,
|
|
1435
|
-
level: Math.random() * 0.99 + 0.01
|
|
1436
|
-
addEventListener: () => {},
|
|
1437
|
-
removeEventListener: () => {},
|
|
1438
|
-
dispatchEvent: () => true
|
|
568
|
+
level: Math.random() * 0.99 + 0.01
|
|
1439
569
|
});
|
|
1440
570
|
};
|
|
1441
571
|
}
|
|
1442
572
|
}, 'battery API spoofing');
|
|
1443
|
-
|
|
1444
|
-
// 16. Add realistic timing to console methods with safe handling
|
|
1445
|
-
safeExecuteSpoofing(() => {
|
|
1446
|
-
['debug', 'error', 'info', 'log', 'warn'].forEach(method => {
|
|
1447
|
-
const original = console[method];
|
|
1448
|
-
console[method] = function(...args) {
|
|
1449
|
-
// Add tiny random delay to mimic human-like console timing
|
|
1450
|
-
setTimeout(() => original.apply(console, args), Math.random() * 5);
|
|
1451
|
-
};
|
|
1452
|
-
});
|
|
1453
|
-
}, 'console timing');
|
|
1454
573
|
|
|
1455
|
-
//
|
|
1456
|
-
safeExecuteSpoofing(() => {
|
|
1457
|
-
if ('serviceWorker' in navigator) {
|
|
1458
|
-
const originalRegister = navigator.serviceWorker.register;
|
|
1459
|
-
const originalGetRegistration = navigator.serviceWorker.getRegistration;
|
|
1460
|
-
const originalGetRegistrations = navigator.serviceWorker.getRegistrations;
|
|
1461
|
-
|
|
1462
|
-
// Wrap register method to handle errors gracefully
|
|
1463
|
-
navigator.serviceWorker.register = function(scriptURL, options) {
|
|
1464
|
-
try {
|
|
1465
|
-
if (debugEnabled) {
|
|
1466
|
-
console.log('[fingerprint] Service worker registration intercepted:', scriptURL);
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
// Return a resolved promise to prevent registration errors
|
|
1470
|
-
return Promise.resolve({
|
|
1471
|
-
installing: null,
|
|
1472
|
-
waiting: null,
|
|
1473
|
-
active: {
|
|
1474
|
-
scriptURL: scriptURL,
|
|
1475
|
-
state: 'activated'
|
|
1476
|
-
},
|
|
1477
|
-
scope: options?.scope || '/',
|
|
1478
|
-
update: () => Promise.resolve(),
|
|
1479
|
-
unregister: () => Promise.resolve(true),
|
|
1480
|
-
addEventListener: () => {},
|
|
1481
|
-
removeEventListener: () => {},
|
|
1482
|
-
dispatchEvent: () => true
|
|
1483
|
-
});
|
|
1484
|
-
} catch (registerErr) {
|
|
1485
|
-
if (debugEnabled) {
|
|
1486
|
-
console.log('[fingerprint] Service worker register error handled:', registerErr.message);
|
|
1487
|
-
}
|
|
1488
|
-
// Return rejected promise to maintain normal error flow for sites that expect it
|
|
1489
|
-
return Promise.reject(new Error('ServiceWorker registration failed'));
|
|
1490
|
-
}
|
|
1491
|
-
};
|
|
1492
|
-
|
|
1493
|
-
// Wrap getRegistration to return mock registration
|
|
1494
|
-
navigator.serviceWorker.getRegistration = function(scope) {
|
|
1495
|
-
try {
|
|
1496
|
-
return Promise.resolve(null); // No existing registrations
|
|
1497
|
-
} catch (getRegErr) {
|
|
1498
|
-
return Promise.reject(getRegErr);
|
|
1499
|
-
}
|
|
1500
|
-
};
|
|
1501
|
-
|
|
1502
|
-
// Wrap getRegistrations to return empty array
|
|
1503
|
-
navigator.serviceWorker.getRegistrations = function() {
|
|
1504
|
-
try {
|
|
1505
|
-
return Promise.resolve([]); // No existing registrations
|
|
1506
|
-
} catch (getRegsErr) {
|
|
1507
|
-
return Promise.reject(getRegsErr);
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
}, 'service worker handling');
|
|
574
|
+
// Suppress common console errors
|
|
1512
575
|
|
|
1513
|
-
//
|
|
1514
|
-
|
|
1515
|
-
//
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
576
|
+
// Enhanced Mouse/Pointer Spoofing
|
|
577
|
+
safeExecute(() => {
|
|
578
|
+
// Spoof pointer capabilities
|
|
579
|
+
if (navigator.maxTouchPoints !== undefined) {
|
|
580
|
+
safeDefinePropertyLocal(navigator, 'maxTouchPoints', {
|
|
581
|
+
get: () => Math.random() > 0.7 ? 0 : Math.floor(Math.random() * 5) + 1
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Spoof mouse timing patterns to prevent behavioral fingerprinting
|
|
586
|
+
const originalAddEventListener = EventTarget.prototype.addEventListener;
|
|
587
|
+
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
588
|
+
if (type === 'mousemove' && typeof listener === 'function') {
|
|
589
|
+
const wrappedListener = function(event) {
|
|
590
|
+
// Add slight timing variation to prevent pattern detection
|
|
591
|
+
const delay = Math.random() * 2; // 0-2ms variation
|
|
592
|
+
setTimeout(() => listener.call(this, event), delay);
|
|
593
|
+
};
|
|
594
|
+
return originalAddEventListener.call(this, type, wrappedListener, options);
|
|
1529
595
|
}
|
|
1530
|
-
|
|
1531
|
-
return originalConsoleError.apply(this, arguments);
|
|
596
|
+
return originalAddEventListener.call(this, type, listener, options);
|
|
1532
597
|
};
|
|
1533
|
-
|
|
1534
|
-
//
|
|
1535
|
-
if (window.
|
|
1536
|
-
const
|
|
1537
|
-
window.
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
throw fetchErr;
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
return result;
|
|
1554
|
-
} catch (fetchWrapErr) {
|
|
1555
|
-
if (debugEnabled) {
|
|
1556
|
-
console.log('[fingerprint] Fetch wrapper error:', fetchWrapErr.message);
|
|
1557
|
-
}
|
|
1558
|
-
return originalFetch.apply(this, args);
|
|
1559
|
-
}
|
|
598
|
+
|
|
599
|
+
// Spoof PointerEvent if available
|
|
600
|
+
if (window.PointerEvent) {
|
|
601
|
+
const OriginalPointerEvent = window.PointerEvent;
|
|
602
|
+
window.PointerEvent = function(type, eventInitDict = {}) {
|
|
603
|
+
// Add realistic pointer properties
|
|
604
|
+
const enhancedDict = {
|
|
605
|
+
...eventInitDict,
|
|
606
|
+
pressure: eventInitDict.pressure || (Math.random() * 0.3 + 0.2), // 0.2-0.5
|
|
607
|
+
tangentialPressure: eventInitDict.tangentialPressure || 0,
|
|
608
|
+
tiltX: eventInitDict.tiltX || (Math.random() * 10 - 5), // -5 to 5
|
|
609
|
+
tiltY: eventInitDict.tiltY || (Math.random() * 10 - 5),
|
|
610
|
+
twist: eventInitDict.twist || Math.random() * 360,
|
|
611
|
+
pointerType: eventInitDict.pointerType || 'mouse'
|
|
612
|
+
};
|
|
613
|
+
return new OriginalPointerEvent(type, enhancedDict);
|
|
1560
614
|
};
|
|
1561
|
-
|
|
1562
|
-
// Preserve fetch properties
|
|
1563
|
-
Object.setPrototypeOf(window.fetch, originalFetch);
|
|
615
|
+
Object.setPrototypeOf(window.PointerEvent, OriginalPointerEvent);
|
|
1564
616
|
}
|
|
1565
617
|
|
|
1566
|
-
//
|
|
1567
|
-
if (window.
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const xhr = new OriginalXHR();
|
|
1572
|
-
const originalOpen = xhr.open;
|
|
1573
|
-
const originalSend = xhr.send;
|
|
1574
|
-
|
|
1575
|
-
// Wrap open method
|
|
1576
|
-
xhr.open = function(...args) {
|
|
1577
|
-
try {
|
|
1578
|
-
return originalOpen.apply(this, args);
|
|
1579
|
-
} catch (openErr) {
|
|
1580
|
-
if (debugEnabled) {
|
|
1581
|
-
console.log('[fingerprint] XHR open error handled:', openErr.message);
|
|
1582
|
-
}
|
|
1583
|
-
throw openErr;
|
|
1584
|
-
}
|
|
1585
|
-
};
|
|
1586
|
-
|
|
1587
|
-
// Wrap send method
|
|
1588
|
-
xhr.send = function(...args) {
|
|
1589
|
-
try {
|
|
1590
|
-
return originalSend.apply(this, args);
|
|
1591
|
-
} catch (sendErr) {
|
|
1592
|
-
if (debugEnabled) {
|
|
1593
|
-
console.log('[fingerprint] XHR send error handled:', sendErr.message);
|
|
1594
|
-
}
|
|
1595
|
-
throw sendErr;
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
|
|
1599
|
-
// Add error event listener to prevent uncaught network errors
|
|
1600
|
-
xhr.addEventListener('error', function(event) {
|
|
1601
|
-
if (debugEnabled) {
|
|
1602
|
-
console.log('[fingerprint] XHR network error event handled');
|
|
1603
|
-
}
|
|
1604
|
-
// Don't prevent default - let normal error handling work
|
|
1605
|
-
});
|
|
1606
|
-
|
|
1607
|
-
return xhr;
|
|
618
|
+
// Spoof touch capabilities for mobile detection evasion
|
|
619
|
+
if (!window.TouchEvent && Math.random() > 0.8) {
|
|
620
|
+
// 20% chance to add touch support to confuse fingerprinters
|
|
621
|
+
window.TouchEvent = function(type, eventInitDict = {}) {
|
|
622
|
+
return new MouseEvent(type, eventInitDict);
|
|
1608
623
|
};
|
|
1609
624
|
|
|
1610
|
-
|
|
1611
|
-
Object.setPrototypeOf(window.XMLHttpRequest, OriginalXHR);
|
|
1612
|
-
window.XMLHttpRequest.prototype = OriginalXHR.prototype;
|
|
625
|
+
safeDefinePropertyLocal(navigator, 'maxTouchPoints', { get: () => 1 });
|
|
1613
626
|
}
|
|
1614
627
|
|
|
1615
|
-
//
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// Call original error handler for all other errors
|
|
1632
|
-
if (originalErrorHandler) {
|
|
1633
|
-
return originalErrorHandler.apply(this, arguments);
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
return false;
|
|
1637
|
-
};
|
|
628
|
+
// Spoof mouse wheel behavior
|
|
629
|
+
const originalWheelEvent = window.WheelEvent;
|
|
630
|
+
if (originalWheelEvent) {
|
|
631
|
+
window.WheelEvent = function(type, eventInitDict = {}) {
|
|
632
|
+
const enhancedDict = {
|
|
633
|
+
...eventInitDict,
|
|
634
|
+
deltaMode: eventInitDict.deltaMode || 0,
|
|
635
|
+
deltaX: eventInitDict.deltaX || 0,
|
|
636
|
+
deltaY: eventInitDict.deltaY || (Math.random() * 100 - 50),
|
|
637
|
+
deltaZ: eventInitDict.deltaZ || 0
|
|
638
|
+
};
|
|
639
|
+
return new originalWheelEvent(type, enhancedDict);
|
|
640
|
+
};
|
|
641
|
+
}
|
|
1638
642
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
if (shouldSuppress) {
|
|
1652
|
-
if (debugEnabled) {
|
|
1653
|
-
console.log('[fingerprint] HTTP status promise rejection handled:', reason);
|
|
1654
|
-
}
|
|
1655
|
-
event.preventDefault();
|
|
643
|
+
}, 'enhanced mouse/pointer spoofing');
|
|
644
|
+
|
|
645
|
+
safeExecute(() => {
|
|
646
|
+
const originalConsoleError = console.error;
|
|
647
|
+
console.error = function(...args) {
|
|
648
|
+
const message = args.join(' ');
|
|
649
|
+
if (typeof message === 'string' && (
|
|
650
|
+
message.includes('Failed to load resource') ||
|
|
651
|
+
message.includes('is not defined') ||
|
|
652
|
+
message.includes('is not a function')
|
|
653
|
+
)) {
|
|
654
|
+
if (debugEnabled) console.log(`[fingerprint] Suppressed error: ${message}`);
|
|
1656
655
|
return;
|
|
1657
656
|
}
|
|
1658
|
-
|
|
1659
|
-
// Call original handler for non-network rejections
|
|
1660
|
-
if (originalUnhandledRejection) {
|
|
1661
|
-
return originalUnhandledRejection.apply(this, arguments);
|
|
1662
|
-
}
|
|
657
|
+
return originalConsoleError.apply(this, arguments);
|
|
1663
658
|
};
|
|
1664
|
-
|
|
1665
|
-
}, 'network error handling');
|
|
1666
|
-
|
|
659
|
+
}, 'console error suppression');
|
|
1667
660
|
|
|
1668
661
|
}, ua, forceDebug);
|
|
1669
662
|
} catch (stealthErr) {
|
|
1670
|
-
console.warn(`[
|
|
663
|
+
console.warn(`[stealth protection failed] ${currentUrl}: ${stealthErr.message}`);
|
|
1671
664
|
}
|
|
1672
665
|
}
|
|
1673
666
|
}
|
|
1674
667
|
|
|
1675
668
|
/**
|
|
1676
|
-
* Enhanced Brave browser spoofing
|
|
1677
|
-
* Compatible with Puppeteer 23.x
|
|
1678
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1679
|
-
* @param {object} siteConfig - The site configuration object
|
|
1680
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1681
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
1682
|
-
* @returns {Promise<void>}
|
|
669
|
+
* Enhanced Brave browser spoofing
|
|
1683
670
|
*/
|
|
1684
671
|
async function applyBraveSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
1685
672
|
if (!siteConfig.isBrave) return;
|
|
1686
673
|
|
|
1687
|
-
if (forceDebug) console.log(`[debug]
|
|
674
|
+
if (forceDebug) console.log(`[debug] Brave spoofing enabled for ${currentUrl}`);
|
|
1688
675
|
|
|
1689
676
|
await page.evaluateOnNewDocument((debugEnabled) => {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
677
|
+
try {
|
|
678
|
+
Object.defineProperty(navigator, 'brave', {
|
|
679
|
+
get: () => ({
|
|
680
|
+
isBrave: () => Promise.resolve(true),
|
|
681
|
+
setBadge: () => {},
|
|
682
|
+
clearBadge: () => {},
|
|
683
|
+
getAdBlockEnabled: () => Promise.resolve(true),
|
|
684
|
+
getShieldsEnabled: () => Promise.resolve(true)
|
|
685
|
+
}),
|
|
686
|
+
configurable: true
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
if (navigator.userAgent && !navigator.userAgent.includes('Brave')) {
|
|
690
|
+
Object.defineProperty(navigator, 'userAgent', {
|
|
691
|
+
get: () => navigator.userAgent.replace('Chrome/', 'Brave/').replace('Safari/537.36', 'Safari/537.36 Brave/1.60'),
|
|
1704
692
|
configurable: true
|
|
1705
|
-
};
|
|
1706
|
-
|
|
1707
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
1708
|
-
return true;
|
|
1709
|
-
} catch (defineErr) {
|
|
1710
|
-
if (debugEnabled) {
|
|
1711
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
1712
|
-
}
|
|
1713
|
-
return false;
|
|
693
|
+
});
|
|
1714
694
|
}
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// More comprehensive Brave spoofing with safe property handling
|
|
1718
|
-
safeDefinePropertyLocal(navigator, 'brave', {
|
|
1719
|
-
get: () => ({
|
|
1720
|
-
isBrave: () => Promise.resolve(true),
|
|
1721
|
-
setBadge: () => {},
|
|
1722
|
-
clearBadge: () => {},
|
|
1723
|
-
getAdBlockEnabled: () => Promise.resolve(true),
|
|
1724
|
-
getShieldsEnabled: () => Promise.resolve(true)
|
|
1725
|
-
})
|
|
1726
|
-
});
|
|
1727
|
-
|
|
1728
|
-
// Brave-specific user agent adjustments with safe handling
|
|
1729
|
-
if (navigator.userAgent && !navigator.userAgent.includes('Brave')) {
|
|
1730
|
-
safeDefinePropertyLocal(navigator, 'userAgent', {
|
|
1731
|
-
get: () => navigator.userAgent.replace('Chrome/', 'Brave/').replace('Safari/537.36', 'Safari/537.36 Brave/1.60')
|
|
1732
|
-
});
|
|
695
|
+
} catch (err) {
|
|
696
|
+
if (debugEnabled) console.log(`[fingerprint] Brave spoofing error: ${err.message}`);
|
|
1733
697
|
}
|
|
1734
698
|
}, forceDebug);
|
|
1735
699
|
}
|
|
1736
700
|
|
|
1737
701
|
/**
|
|
1738
|
-
* Enhanced fingerprint protection with
|
|
1739
|
-
* Compatible with Puppeteer 23.x
|
|
1740
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1741
|
-
* @param {object} siteConfig - The site configuration object
|
|
1742
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1743
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
1744
|
-
* @returns {Promise<void>}
|
|
702
|
+
* Enhanced fingerprint protection with realistic spoofing
|
|
1745
703
|
*/
|
|
1746
704
|
async function applyFingerprintProtection(page, siteConfig, forceDebug, currentUrl) {
|
|
1747
705
|
const fingerprintSetting = siteConfig.fingerprint_protection;
|
|
1748
706
|
if (!fingerprintSetting) return;
|
|
1749
707
|
|
|
1750
|
-
if (forceDebug) console.log(`[debug]
|
|
708
|
+
if (forceDebug) console.log(`[debug] Fingerprint protection enabled for ${currentUrl}`);
|
|
1751
709
|
|
|
1752
710
|
const spoof = fingerprintSetting === 'random' ? getRandomFingerprint() : {
|
|
1753
711
|
deviceMemory: 8,
|
|
@@ -1763,338 +721,207 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1763
721
|
try {
|
|
1764
722
|
await page.evaluateOnNewDocument(({ spoof, debugEnabled }) => {
|
|
1765
723
|
|
|
1766
|
-
// Use local versions of helper functions for this context
|
|
1767
724
|
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
1768
725
|
try {
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
if (existingDescriptor && existingDescriptor.configurable === false) {
|
|
1772
|
-
if (debugEnabled) {
|
|
1773
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
1774
|
-
}
|
|
1775
|
-
return false;
|
|
1776
|
-
}
|
|
726
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
727
|
+
if (existing?.configurable === false) return false;
|
|
1777
728
|
|
|
1778
|
-
|
|
729
|
+
Object.defineProperty(target, property, {
|
|
1779
730
|
...descriptor,
|
|
1780
|
-
configurable: true
|
|
1781
|
-
|
|
1782
|
-
};
|
|
1783
|
-
|
|
1784
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
1785
|
-
return true;
|
|
1786
|
-
} catch (defineErr) {
|
|
1787
|
-
if (debugEnabled) {
|
|
1788
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
1789
|
-
}
|
|
1790
|
-
return false;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
function safeExecuteSpoofingLocal(spoofFunction, description) {
|
|
1795
|
-
try {
|
|
1796
|
-
spoofFunction();
|
|
731
|
+
configurable: true
|
|
732
|
+
});
|
|
1797
733
|
return true;
|
|
1798
|
-
} catch (
|
|
1799
|
-
if (debugEnabled) {
|
|
1800
|
-
console.log(`[fingerprint] ${description} failed: ${spoofErr.message}`);
|
|
1801
|
-
}
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (debugEnabled) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
1802
736
|
return false;
|
|
1803
737
|
}
|
|
1804
738
|
}
|
|
1805
|
-
|
|
1806
|
-
// Enhanced property spoofing with more realistic values and safe handling
|
|
1807
|
-
safeExecuteSpoofingLocal(() => {
|
|
1808
|
-
safeDefinePropertyLocal(navigator, 'platform', {
|
|
1809
|
-
get: () => spoof.platform
|
|
1810
|
-
});
|
|
1811
|
-
}, 'platform spoofing');
|
|
1812
|
-
|
|
1813
|
-
safeExecuteSpoofingLocal(() => {
|
|
1814
|
-
safeDefinePropertyLocal(navigator, 'deviceMemory', {
|
|
1815
|
-
get: () => spoof.deviceMemory
|
|
1816
|
-
});
|
|
1817
|
-
}, 'deviceMemory spoofing');
|
|
1818
|
-
|
|
1819
|
-
safeExecuteSpoofingLocal(() => {
|
|
1820
|
-
safeDefinePropertyLocal(navigator, 'hardwareConcurrency', {
|
|
1821
|
-
get: () => spoof.hardwareConcurrency
|
|
1822
|
-
});
|
|
1823
|
-
}, 'hardwareConcurrency spoofing');
|
|
1824
|
-
|
|
1825
|
-
// Enhanced screen properties with safe handling
|
|
1826
|
-
safeExecuteSpoofingLocal(() => {
|
|
1827
|
-
['width', 'height', 'availWidth', 'availHeight', 'colorDepth', 'pixelDepth'].forEach(prop => {
|
|
1828
|
-
if (spoof.screen[prop] !== undefined) {
|
|
1829
|
-
safeDefinePropertyLocal(window.screen, prop, {
|
|
1830
|
-
get: () => spoof.screen[prop]
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
});
|
|
1834
|
-
}, 'screen properties spoofing');
|
|
1835
|
-
|
|
1836
|
-
// Enhanced language spoofing in fingerprint protection
|
|
1837
|
-
safeExecuteSpoofingLocal(() => {
|
|
1838
|
-
const languages = Array.isArray(spoof.language) ? spoof.language : [spoof.language, spoof.language.split('-')[0]];
|
|
1839
|
-
|
|
1840
|
-
safeDefinePropertyLocal(navigator, 'languages', {
|
|
1841
|
-
get: () => languages
|
|
1842
|
-
});
|
|
1843
|
-
|
|
1844
|
-
safeDefinePropertyLocal(navigator, 'language', {
|
|
1845
|
-
get: () => languages[0]
|
|
1846
|
-
});
|
|
1847
|
-
}, 'language spoofing in fingerprint protection');
|
|
1848
739
|
|
|
740
|
+
// Platform spoofing
|
|
741
|
+
safeDefinePropertyLocal(navigator, 'platform', { get: () => spoof.platform });
|
|
1849
742
|
|
|
1850
|
-
//
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
// Check if Intl.DateTimeFormat is available and configurable
|
|
1861
|
-
try {
|
|
1862
|
-
const intlDescriptor = Object.getOwnPropertyDescriptor(window, 'Intl');
|
|
1863
|
-
if (intlDescriptor && intlDescriptor.configurable === false) {
|
|
1864
|
-
if (debugEnabled) {
|
|
1865
|
-
console.log('[fingerprint] Intl object is non-configurable, skipping timezone spoofing');
|
|
1866
|
-
}
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
} catch (intlCheckErr) {
|
|
1870
|
-
if (debugEnabled) {
|
|
1871
|
-
console.log('[fingerprint] Cannot check Intl configurability, skipping timezone spoofing');
|
|
1872
|
-
}
|
|
1873
|
-
return;
|
|
743
|
+
// Memory spoofing
|
|
744
|
+
safeDefinePropertyLocal(navigator, 'deviceMemory', { get: () => spoof.deviceMemory });
|
|
745
|
+
safeDefinePropertyLocal(navigator, 'hardwareConcurrency', { get: () => spoof.hardwareConcurrency });
|
|
746
|
+
|
|
747
|
+
// Screen properties spoofing
|
|
748
|
+
['width', 'height', 'availWidth', 'availHeight', 'colorDepth', 'pixelDepth'].forEach(prop => {
|
|
749
|
+
if (spoof.screen[prop] !== undefined) {
|
|
750
|
+
safeDefinePropertyLocal(window.screen, prop, { get: () => spoof.screen[prop] });
|
|
1874
751
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
// Validate timezone before setting
|
|
1889
|
-
if (spoof.timezone && typeof spoof.timezone === 'string') {
|
|
1890
|
-
options.timeZone = spoof.timezone;
|
|
1891
|
-
}
|
|
1892
|
-
return options;
|
|
1893
|
-
} catch (optionsErr) {
|
|
1894
|
-
if (debugEnabled) {
|
|
1895
|
-
console.log('[fingerprint] resolvedOptions error, using original:', optionsErr.message);
|
|
1896
|
-
}
|
|
1897
|
-
return originalResolvedOptions.call(this);
|
|
1898
|
-
}
|
|
1899
|
-
};
|
|
1900
|
-
return instance;
|
|
1901
|
-
} catch (instanceErr) {
|
|
1902
|
-
if (debugEnabled) {
|
|
1903
|
-
console.log('[fingerprint] DateTimeFormat instance error, using original:', instanceErr.message);
|
|
1904
|
-
}
|
|
1905
|
-
return new originalDateTimeFormat(...args);
|
|
1906
|
-
}
|
|
1907
|
-
};
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Language spoofing
|
|
755
|
+
const languages = Array.isArray(spoof.language) ? spoof.language : [spoof.language, spoof.language.split('-')[0]];
|
|
756
|
+
safeDefinePropertyLocal(navigator, 'languages', { get: () => languages });
|
|
757
|
+
safeDefinePropertyLocal(navigator, 'language', { get: () => languages[0] });
|
|
758
|
+
|
|
759
|
+
// Timezone spoofing
|
|
760
|
+
if (spoof.timezone && window.Intl?.DateTimeFormat) {
|
|
761
|
+
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
762
|
+
window.Intl.DateTimeFormat = function(...args) {
|
|
763
|
+
const instance = new OriginalDateTimeFormat(...args);
|
|
764
|
+
const originalResolvedOptions = instance.resolvedOptions;
|
|
1908
765
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
}
|
|
766
|
+
instance.resolvedOptions = function() {
|
|
767
|
+
const opts = originalResolvedOptions.call(this);
|
|
768
|
+
opts.timeZone = spoof.timezone;
|
|
769
|
+
return opts;
|
|
770
|
+
};
|
|
771
|
+
return instance;
|
|
772
|
+
};
|
|
773
|
+
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
774
|
+
|
|
775
|
+
// Timezone offset spoofing
|
|
776
|
+
const timezoneOffsets = {
|
|
777
|
+
'America/New_York': 300,
|
|
778
|
+
'America/Los_Angeles': 480,
|
|
779
|
+
'Europe/London': 0,
|
|
780
|
+
'America/Chicago': 360
|
|
781
|
+
};
|
|
1925
782
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
const tzDescriptor = Object.getOwnPropertyDescriptor(Date.prototype, 'getTimezoneOffset');
|
|
1932
|
-
if (tzDescriptor && tzDescriptor.configurable === false) {
|
|
1933
|
-
if (debugEnabled) {
|
|
1934
|
-
console.log('[fingerprint] getTimezoneOffset is non-configurable, skipping');
|
|
1935
|
-
}
|
|
1936
|
-
} else {
|
|
1937
|
-
Date.prototype.getTimezoneOffset = function() {
|
|
1938
|
-
try {
|
|
1939
|
-
// Validate timezone and return appropriate offset
|
|
1940
|
-
const timezoneOffsets = {
|
|
1941
|
-
'America/New_York': 300, // EST offset
|
|
1942
|
-
'America/Los_Angeles': 480, // PST offset
|
|
1943
|
-
'Europe/London': 0, // GMT offset
|
|
1944
|
-
'America/Chicago': 360 // CST offset
|
|
1945
|
-
};
|
|
1946
|
-
|
|
1947
|
-
if (spoof.timezone && timezoneOffsets.hasOwnProperty(spoof.timezone)) {
|
|
1948
|
-
return timezoneOffsets[spoof.timezone];
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// Fallback to original if timezone not recognized
|
|
1952
|
-
return originalGetTimezoneOffset.call(this);
|
|
1953
|
-
} catch (tzOffsetErr) {
|
|
1954
|
-
if (debugEnabled) {
|
|
1955
|
-
console.log('[fingerprint] getTimezoneOffset error, using original:', tzOffsetErr.message);
|
|
1956
|
-
}
|
|
1957
|
-
return originalGetTimezoneOffset.call(this);
|
|
1958
|
-
}
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
} catch (timezoneOffsetErr) {
|
|
1962
|
-
if (debugEnabled) {
|
|
1963
|
-
console.log('[fingerprint] Timezone offset spoofing failed:', timezoneOffsetErr.message);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
}, 'timezone spoofing');
|
|
783
|
+
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
|
784
|
+
Date.prototype.getTimezoneOffset = function() {
|
|
785
|
+
return timezoneOffsets[spoof.timezone] || originalGetTimezoneOffset.call(this);
|
|
786
|
+
};
|
|
787
|
+
}
|
|
1967
788
|
|
|
1968
|
-
//
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
if (spoof.doNotTrack !== undefined) {
|
|
1977
|
-
safeDefinePropertyLocal(navigator, 'doNotTrack', {
|
|
1978
|
-
get: () => spoof.doNotTrack
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
}, 'cookie/DNT spoofing');
|
|
789
|
+
// Cookie and DNT spoofing
|
|
790
|
+
if (spoof.cookieEnabled !== undefined) {
|
|
791
|
+
safeDefinePropertyLocal(navigator, 'cookieEnabled', { get: () => spoof.cookieEnabled });
|
|
792
|
+
}
|
|
793
|
+
if (spoof.doNotTrack !== undefined) {
|
|
794
|
+
safeDefinePropertyLocal(navigator, 'doNotTrack', { get: () => spoof.doNotTrack });
|
|
795
|
+
}
|
|
1982
796
|
|
|
1983
797
|
}, { spoof, debugEnabled: forceDebug });
|
|
1984
798
|
} catch (err) {
|
|
1985
|
-
console.warn(`[
|
|
799
|
+
console.warn(`[fingerprint protection failed] ${currentUrl}: ${err.message}`);
|
|
1986
800
|
}
|
|
1987
801
|
}
|
|
1988
802
|
|
|
1989
803
|
/**
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1992
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1993
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1994
|
-
* @returns {Promise<void>}
|
|
804
|
+
* Simulate human-like behavior
|
|
805
|
+
* Enhanced with realistic mouse patterns and timing
|
|
1995
806
|
*/
|
|
1996
807
|
async function simulateHumanBehavior(page, forceDebug) {
|
|
1997
808
|
try {
|
|
1998
809
|
await page.evaluateOnNewDocument((debugEnabled) => {
|
|
1999
810
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
811
|
+
try {
|
|
812
|
+
// Enhanced human-like mouse simulation with realistic patterns
|
|
813
|
+
let mouseX = Math.random() * (window.innerWidth - 100) + 50;
|
|
814
|
+
let mouseY = Math.random() * (window.innerHeight - 100) + 50;
|
|
815
|
+
let lastMoveTime = Date.now();
|
|
816
|
+
let moveCount = 0;
|
|
817
|
+
|
|
818
|
+
// Realistic mouse movement patterns
|
|
819
|
+
const movePatterns = {
|
|
820
|
+
linear: () => ({
|
|
821
|
+
deltaX: (Math.random() - 0.5) * 4,
|
|
822
|
+
deltaY: (Math.random() - 0.5) * 4
|
|
823
|
+
}),
|
|
824
|
+
curved: () => {
|
|
825
|
+
const angle = moveCount * 0.1;
|
|
826
|
+
return {
|
|
827
|
+
deltaX: Math.sin(angle) * 3 + (Math.random() - 0.5) * 2,
|
|
828
|
+
deltaY: Math.cos(angle) * 3 + (Math.random() - 0.5) * 2
|
|
829
|
+
};
|
|
830
|
+
},
|
|
831
|
+
jittery: () => ({
|
|
832
|
+
deltaX: (Math.random() - 0.5) * 8,
|
|
833
|
+
deltaY: (Math.random() - 0.5) * 8
|
|
834
|
+
})
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const patterns = Object.keys(movePatterns);
|
|
838
|
+
let currentPattern = patterns[Math.floor(Math.random() * patterns.length)];
|
|
839
|
+
let patternChangeCounter = 0;
|
|
2016
840
|
|
|
2017
841
|
const moveInterval = setInterval(() => {
|
|
2018
|
-
|
|
2019
|
-
|
|
842
|
+
const now = Date.now();
|
|
843
|
+
const timeDelta = now - lastMoveTime;
|
|
844
|
+
|
|
845
|
+
// Change pattern occasionally
|
|
846
|
+
if (patternChangeCounter++ > 20 + Math.random() * 30) {
|
|
847
|
+
currentPattern = patterns[Math.floor(Math.random() * patterns.length)];
|
|
848
|
+
patternChangeCounter = 0;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Apply movement pattern
|
|
852
|
+
const movement = movePatterns[currentPattern]();
|
|
853
|
+
mouseX += movement.deltaX;
|
|
854
|
+
mouseY += movement.deltaY;
|
|
2020
855
|
|
|
2021
|
-
|
|
2022
|
-
|
|
856
|
+
// Keep within bounds with padding
|
|
857
|
+
mouseX = Math.max(10, Math.min(window.innerWidth - 10, mouseX));
|
|
858
|
+
mouseY = Math.max(10, Math.min(window.innerHeight - 10, mouseY));
|
|
2023
859
|
|
|
2024
860
|
try {
|
|
861
|
+
// Dispatch realistic mouse events with varying timing
|
|
2025
862
|
document.dispatchEvent(new MouseEvent('mousemove', {
|
|
2026
863
|
clientX: mouseX,
|
|
2027
864
|
clientY: mouseY,
|
|
2028
|
-
bubbles: true
|
|
865
|
+
bubbles: true,
|
|
866
|
+
cancelable: true,
|
|
867
|
+
view: window,
|
|
868
|
+
detail: 0,
|
|
869
|
+
buttons: 0,
|
|
870
|
+
button: 0
|
|
2029
871
|
}));
|
|
2030
|
-
|
|
2031
|
-
//
|
|
2032
|
-
|
|
2033
|
-
}, 1000 + Math.random() * 2000);
|
|
2034
|
-
|
|
2035
|
-
// Simulate occasional clicks and scrolls with safe handling
|
|
2036
|
-
setTimeout(() => {
|
|
2037
|
-
try {
|
|
2038
|
-
if (Math.random() > 0.7) {
|
|
872
|
+
|
|
873
|
+
// Occasionally simulate clicks for more realistic behavior
|
|
874
|
+
if (Math.random() > 0.995) { // Very rare clicks
|
|
2039
875
|
document.dispatchEvent(new MouseEvent('click', {
|
|
2040
876
|
clientX: mouseX,
|
|
2041
877
|
clientY: mouseY,
|
|
2042
|
-
bubbles: true
|
|
878
|
+
bubbles: true,
|
|
879
|
+
cancelable: true,
|
|
880
|
+
view: window
|
|
2043
881
|
}));
|
|
2044
882
|
}
|
|
2045
883
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
// Ignore interaction errors
|
|
2052
|
-
}
|
|
2053
|
-
}, 5000 + Math.random() * 10000);
|
|
884
|
+
moveCount++;
|
|
885
|
+
lastMoveTime = now;
|
|
886
|
+
|
|
887
|
+
} catch (e) {}
|
|
888
|
+
}, 50 + Math.random() * 100); // More frequent, realistic timing (50-150ms)
|
|
2054
889
|
|
|
2055
|
-
// Stop
|
|
890
|
+
// Stop after 45 seconds with gradual slowdown
|
|
2056
891
|
setTimeout(() => {
|
|
2057
|
-
try {
|
|
892
|
+
try {
|
|
2058
893
|
clearInterval(moveInterval);
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
}
|
|
894
|
+
if (debugEnabled) console.log('[fingerprint] Enhanced mouse simulation completed');
|
|
895
|
+
} catch (e) {}
|
|
896
|
+
}, 45000);
|
|
897
|
+
|
|
898
|
+
} catch (err) {
|
|
899
|
+
if (debugEnabled) console.log(`[fingerprint] Human behavior simulation failed: ${err.message}`);
|
|
900
|
+
}
|
|
2064
901
|
|
|
2065
902
|
}, forceDebug);
|
|
2066
903
|
} catch (err) {
|
|
2067
|
-
if (forceDebug) console.log(`[debug] Human behavior simulation failed: ${err.message}`);
|
|
904
|
+
if (forceDebug) console.log(`[debug] Human behavior simulation setup failed: ${err.message}`);
|
|
2068
905
|
}
|
|
2069
906
|
}
|
|
2070
907
|
|
|
2071
908
|
/**
|
|
2072
|
-
*
|
|
2073
|
-
* Compatible with Puppeteer 23.x
|
|
2074
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
2075
|
-
* @param {object} siteConfig - The site configuration object
|
|
2076
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
2077
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
2078
|
-
* @returns {Promise<void>}
|
|
909
|
+
* Main function that applies all fingerprint spoofing techniques
|
|
2079
910
|
*/
|
|
2080
911
|
async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
try {
|
|
2095
|
-
await applyFingerprintProtection(page, siteConfig, forceDebug, currentUrl);
|
|
2096
|
-
} catch (fpErr) {
|
|
2097
|
-
if (forceDebug) console.log(`[debug] Fingerprint protection failed for ${currentUrl}: ${fpErr.message}`);
|
|
912
|
+
|
|
913
|
+
const techniques = [
|
|
914
|
+
{ fn: applyUserAgentSpoofing, name: 'User agent spoofing' },
|
|
915
|
+
{ fn: applyBraveSpoofing, name: 'Brave spoofing' },
|
|
916
|
+
{ fn: applyFingerprintProtection, name: 'Fingerprint protection' }
|
|
917
|
+
];
|
|
918
|
+
|
|
919
|
+
for (const { fn, name } of techniques) {
|
|
920
|
+
try {
|
|
921
|
+
await fn(page, siteConfig, forceDebug, currentUrl);
|
|
922
|
+
} catch (err) {
|
|
923
|
+
if (forceDebug) console.log(`[debug] ${name} failed for ${currentUrl}: ${err.message}`);
|
|
924
|
+
}
|
|
2098
925
|
}
|
|
2099
926
|
|
|
2100
927
|
// Add human behavior simulation if user agent spoofing is enabled
|
|
@@ -2107,6 +934,12 @@ async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, current
|
|
|
2107
934
|
}
|
|
2108
935
|
}
|
|
2109
936
|
|
|
937
|
+
// Legacy compatibility function - maintained for backwards compatibility
|
|
938
|
+
function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
|
|
939
|
+
return safeSpoofingExecution(spoofFunction, description, { debug: forceDebug });
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
|
|
2110
943
|
module.exports = {
|
|
2111
944
|
getRandomFingerprint,
|
|
2112
945
|
getRealisticScreenResolution,
|
|
@@ -2116,7 +949,10 @@ module.exports = {
|
|
|
2116
949
|
applyAllFingerprintSpoofing,
|
|
2117
950
|
simulateHumanBehavior,
|
|
2118
951
|
safeDefineProperty,
|
|
2119
|
-
safeExecuteSpoofing,
|
|
952
|
+
safeExecuteSpoofing, // Legacy compatibility
|
|
953
|
+
safeSpoofingExecution,
|
|
954
|
+
createFingerprintMocks,
|
|
955
|
+
applyTimezoneSpoofing,
|
|
2120
956
|
DEFAULT_PLATFORM,
|
|
2121
957
|
DEFAULT_TIMEZONE
|
|
2122
958
|
};
|