@zaplier/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/index.cjs +11092 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +1204 -0
  6. package/dist/index.esm.js +11059 -0
  7. package/dist/index.esm.js.map +1 -0
  8. package/dist/index.js +3517 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/sdk.js +11098 -0
  11. package/dist/sdk.js.map +1 -0
  12. package/dist/sdk.min.js +7 -0
  13. package/dist/src/index.d.ts +15 -0
  14. package/dist/src/index.d.ts.map +1 -0
  15. package/dist/src/modules/anti-adblock.d.ts +108 -0
  16. package/dist/src/modules/anti-adblock.d.ts.map +1 -0
  17. package/dist/src/modules/bot-detection.d.ts +15 -0
  18. package/dist/src/modules/bot-detection.d.ts.map +1 -0
  19. package/dist/src/modules/fingerprint/accessibility.d.ts +155 -0
  20. package/dist/src/modules/fingerprint/accessibility.d.ts.map +1 -0
  21. package/dist/src/modules/fingerprint/audio.d.ts +16 -0
  22. package/dist/src/modules/fingerprint/audio.d.ts.map +1 -0
  23. package/dist/src/modules/fingerprint/browser-apis.d.ts +108 -0
  24. package/dist/src/modules/fingerprint/browser-apis.d.ts.map +1 -0
  25. package/dist/src/modules/fingerprint/browser.d.ts +14 -0
  26. package/dist/src/modules/fingerprint/browser.d.ts.map +1 -0
  27. package/dist/src/modules/fingerprint/canvas.d.ts +14 -0
  28. package/dist/src/modules/fingerprint/canvas.d.ts.map +1 -0
  29. package/dist/src/modules/fingerprint/confidence.d.ts +89 -0
  30. package/dist/src/modules/fingerprint/confidence.d.ts.map +1 -0
  31. package/dist/src/modules/fingerprint/datetime-locale.d.ts +76 -0
  32. package/dist/src/modules/fingerprint/datetime-locale.d.ts.map +1 -0
  33. package/dist/src/modules/fingerprint/device-signals.d.ts +29 -0
  34. package/dist/src/modules/fingerprint/device-signals.d.ts.map +1 -0
  35. package/dist/src/modules/fingerprint/dom-blockers.d.ts +56 -0
  36. package/dist/src/modules/fingerprint/dom-blockers.d.ts.map +1 -0
  37. package/dist/src/modules/fingerprint/font-preferences.d.ts +55 -0
  38. package/dist/src/modules/fingerprint/font-preferences.d.ts.map +1 -0
  39. package/dist/src/modules/fingerprint/fonts-enhanced.d.ts +43 -0
  40. package/dist/src/modules/fingerprint/fonts-enhanced.d.ts.map +1 -0
  41. package/dist/src/modules/fingerprint/fonts.d.ts +14 -0
  42. package/dist/src/modules/fingerprint/fonts.d.ts.map +1 -0
  43. package/dist/src/modules/fingerprint/hardware.d.ts +40 -0
  44. package/dist/src/modules/fingerprint/hardware.d.ts.map +1 -0
  45. package/dist/src/modules/fingerprint/hashing.d.ts +28 -0
  46. package/dist/src/modules/fingerprint/hashing.d.ts.map +1 -0
  47. package/dist/src/modules/fingerprint/incognito.d.ts +6 -0
  48. package/dist/src/modules/fingerprint/incognito.d.ts.map +1 -0
  49. package/dist/src/modules/fingerprint/math-enhanced.d.ts +70 -0
  50. package/dist/src/modules/fingerprint/math-enhanced.d.ts.map +1 -0
  51. package/dist/src/modules/fingerprint/math.d.ts +32 -0
  52. package/dist/src/modules/fingerprint/math.d.ts.map +1 -0
  53. package/dist/src/modules/fingerprint/plugins-enhanced.d.ts +97 -0
  54. package/dist/src/modules/fingerprint/plugins-enhanced.d.ts.map +1 -0
  55. package/dist/src/modules/fingerprint/screen.d.ts +15 -0
  56. package/dist/src/modules/fingerprint/screen.d.ts.map +1 -0
  57. package/dist/src/modules/fingerprint/storage.d.ts +45 -0
  58. package/dist/src/modules/fingerprint/storage.d.ts.map +1 -0
  59. package/dist/src/modules/fingerprint/system.d.ts +40 -0
  60. package/dist/src/modules/fingerprint/system.d.ts.map +1 -0
  61. package/dist/src/modules/fingerprint/webgl.d.ts +15 -0
  62. package/dist/src/modules/fingerprint/webgl.d.ts.map +1 -0
  63. package/dist/src/modules/fingerprint.d.ts +35 -0
  64. package/dist/src/modules/fingerprint.d.ts.map +1 -0
  65. package/dist/src/modules/global-interface.d.ts +141 -0
  66. package/dist/src/modules/global-interface.d.ts.map +1 -0
  67. package/dist/src/modules/heatmap.d.ts +140 -0
  68. package/dist/src/modules/heatmap.d.ts.map +1 -0
  69. package/dist/src/modules/incognito-detection.d.ts +23 -0
  70. package/dist/src/modules/incognito-detection.d.ts.map +1 -0
  71. package/dist/src/modules/session-replay.d.ts +132 -0
  72. package/dist/src/modules/session-replay.d.ts.map +1 -0
  73. package/dist/src/modules/user-agent.d.ts +35 -0
  74. package/dist/src/modules/user-agent.d.ts.map +1 -0
  75. package/dist/src/sdk.d.ts +227 -0
  76. package/dist/src/sdk.d.ts.map +1 -0
  77. package/dist/src/types/config.d.ts +44 -0
  78. package/dist/src/types/config.d.ts.map +1 -0
  79. package/dist/src/types/detection.d.ts +114 -0
  80. package/dist/src/types/detection.d.ts.map +1 -0
  81. package/dist/src/types/events.d.ts +174 -0
  82. package/dist/src/types/events.d.ts.map +1 -0
  83. package/dist/src/types/fingerprint.d.ts +157 -0
  84. package/dist/src/types/fingerprint.d.ts.map +1 -0
  85. package/dist/src/types/index.d.ts +83 -0
  86. package/dist/src/types/index.d.ts.map +1 -0
  87. package/dist/src/types/methods.d.ts +83 -0
  88. package/dist/src/types/methods.d.ts.map +1 -0
  89. package/dist/src/types/visitor.d.ts +90 -0
  90. package/dist/src/types/visitor.d.ts.map +1 -0
  91. package/dist/src/utils/browser.d.ts +79 -0
  92. package/dist/src/utils/browser.d.ts.map +1 -0
  93. package/dist/src/utils/lazy-loader.d.ts +60 -0
  94. package/dist/src/utils/lazy-loader.d.ts.map +1 -0
  95. package/dist/src/utils/webgl-cache.d.ts +43 -0
  96. package/dist/src/utils/webgl-cache.d.ts.map +1 -0
  97. package/package.json +82 -0
package/dist/index.js ADDED
@@ -0,0 +1,3517 @@
1
+ /*!
2
+ * RabbitTracker SDK v3.0.0
3
+ * Advanced privacy-first tracking with fingerprinting and security detection
4
+ * (c) 2025 RabbitTracker Team
5
+ * Released under the MIT License
6
+ * https://rabbitracker.com
7
+ */
8
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ /**
13
+ * MurmurHash3 x64 Implementation
14
+ * Based on FingerprintJS hashing utility for consistent fingerprint generation
15
+ *
16
+ * Provides 128-bit hash output using the x64 variant of MurmurHash3
17
+ */
18
+ /**
19
+ * Adds two 64-bit numbers
20
+ */
21
+ function x64Add(a, b) {
22
+ const low = (a[1] + b[1]) & 0xffffffff;
23
+ const high = (a[0] + b[0] + (low < a[1] ? 1 : 0)) & 0xffffffff;
24
+ return [high, low];
25
+ }
26
+ /**
27
+ * Multiplies two 64-bit numbers
28
+ */
29
+ function x64Multiply(a, b) {
30
+ const al = a[1] & 0xffff;
31
+ const ah = a[1] >>> 16;
32
+ const bl = b[1] & 0xffff;
33
+ const bh = b[1] >>> 16;
34
+ const rl = al * bl;
35
+ const rm = (al * bh + ah * bl) & 0xffffffff;
36
+ const rh = ah * bh + (rm >>> 16);
37
+ const low = (rl + ((rm & 0xffff) << 16)) & 0xffffffff;
38
+ const high = (a[0] * b[1] + a[1] * b[0] + rh) & 0xffffffff;
39
+ return [high, low];
40
+ }
41
+ /**
42
+ * Rotates a 64-bit number left by the specified number of bits
43
+ */
44
+ function x64Rotl(value, shift) {
45
+ shift %= 64;
46
+ if (shift === 0)
47
+ return value;
48
+ if (shift < 32) {
49
+ const high = (value[0] << shift) | (value[1] >>> (32 - shift));
50
+ const low = (value[1] << shift) | (value[0] >>> (32 - shift));
51
+ return [high & 0xffffffff, low & 0xffffffff];
52
+ }
53
+ else {
54
+ shift -= 32;
55
+ const high = (value[1] << shift) | (value[0] >>> (32 - shift));
56
+ const low = (value[0] << shift) | (value[1] >>> (32 - shift));
57
+ return [high & 0xffffffff, low & 0xffffffff];
58
+ }
59
+ }
60
+ /**
61
+ * XORs two 64-bit numbers
62
+ */
63
+ function x64Xor(a, b) {
64
+ return [a[0] ^ b[0], a[1] ^ b[1]];
65
+ }
66
+ /**
67
+ * Finalizes a 64-bit hash value
68
+ */
69
+ function x64Fmix(hash) {
70
+ let h = hash;
71
+ h = x64Xor(h, [0, h[0] >>> 1]);
72
+ h = x64Multiply(h, [0xff51afd7, 0xed558ccd]);
73
+ h = x64Xor(h, [0, h[0] >>> 1]);
74
+ h = x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53]);
75
+ h = x64Xor(h, [0, h[0] >>> 1]);
76
+ return h;
77
+ }
78
+ /**
79
+ * Converts a string to array of 32-bit integers (little-endian)
80
+ */
81
+ function stringToUint32Array(str) {
82
+ const arr = [];
83
+ for (let i = 0; i < str.length; i += 4) {
84
+ let value = 0;
85
+ for (let j = Math.min(3, str.length - i - 1); j >= 0; j--) {
86
+ value = (value << 8) | str.charCodeAt(i + j);
87
+ }
88
+ arr.push(value);
89
+ }
90
+ return arr;
91
+ }
92
+ /**
93
+ * MurmurHash3 x64 128-bit hash function
94
+ * Returns a 128-bit hash as a hex string
95
+ */
96
+ function x64hash128(input, seed = 0) {
97
+ const data = stringToUint32Array(input);
98
+ const nblocks = Math.floor(data.length / 4);
99
+ let h1 = [0, seed];
100
+ let h2 = [0, seed];
101
+ const c1 = [0x87c37b91, 0x114253d5];
102
+ const c2 = [0x4cf5ad43, 0x2745937f];
103
+ // Process 16-byte blocks
104
+ for (let i = 0; i < nblocks; i++) {
105
+ const k1Index = i * 4;
106
+ const k2Index = i * 4 + 2;
107
+ let k1 = [data[k1Index + 1] || 0, data[k1Index] || 0];
108
+ let k2 = [data[k2Index + 1] || 0, data[k2Index] || 0];
109
+ k1 = x64Multiply(k1, c1);
110
+ k1 = x64Rotl(k1, 31);
111
+ k1 = x64Multiply(k1, c2);
112
+ h1 = x64Xor(h1, k1);
113
+ h1 = x64Rotl(h1, 27);
114
+ h1 = x64Add(h1, h2);
115
+ h1 = x64Add(x64Multiply(h1, [0, 5]), [0, 0x52dce729]);
116
+ k2 = x64Multiply(k2, c2);
117
+ k2 = x64Rotl(k2, 33);
118
+ k2 = x64Multiply(k2, c1);
119
+ h2 = x64Xor(h2, k2);
120
+ h2 = x64Rotl(h2, 31);
121
+ h2 = x64Add(h2, h1);
122
+ h2 = x64Add(x64Multiply(h2, [0, 5]), [0, 0x38495ab5]);
123
+ }
124
+ // Process remaining bytes
125
+ const tailIndex = nblocks * 4;
126
+ const remainingBytes = data.length - tailIndex;
127
+ let k1 = [0, 0];
128
+ let k2 = [0, 0];
129
+ if (remainingBytes >= 12) {
130
+ k2 = x64Xor(k2, [data[tailIndex + 3] || 0, 0]);
131
+ }
132
+ if (remainingBytes >= 8) {
133
+ k2 = x64Xor(k2, [0, data[tailIndex + 2] || 0]);
134
+ k2 = x64Multiply(k2, c2);
135
+ k2 = x64Rotl(k2, 33);
136
+ k2 = x64Multiply(k2, c1);
137
+ h2 = x64Xor(h2, k2);
138
+ }
139
+ if (remainingBytes >= 4) {
140
+ k1 = x64Xor(k1, [data[tailIndex + 1] || 0, 0]);
141
+ }
142
+ if (remainingBytes >= 1) {
143
+ k1 = x64Xor(k1, [0, data[tailIndex] || 0]);
144
+ k1 = x64Multiply(k1, c1);
145
+ k1 = x64Rotl(k1, 31);
146
+ k1 = x64Multiply(k1, c2);
147
+ h1 = x64Xor(h1, k1);
148
+ }
149
+ // Finalization
150
+ h1 = x64Xor(h1, [0, input.length]);
151
+ h2 = x64Xor(h2, [0, input.length]);
152
+ h1 = x64Add(h1, h2);
153
+ h2 = x64Add(h2, h1);
154
+ h1 = x64Fmix(h1);
155
+ h2 = x64Fmix(h2);
156
+ h1 = x64Add(h1, h2);
157
+ h2 = x64Add(h2, h1);
158
+ // Convert to hex string
159
+ const hex1 = h1[0].toString(16).padStart(8, '0') + h1[1].toString(16).padStart(8, '0');
160
+ const hex2 = h2[0].toString(16).padStart(8, '0') + h2[1].toString(16).padStart(8, '0');
161
+ return hex1 + hex2;
162
+ }
163
+ /**
164
+ * Simple 32-bit hash for basic use cases
165
+ */
166
+ function hash32(input) {
167
+ let hash = 0;
168
+ if (input.length === 0)
169
+ return hash.toString(36);
170
+ for (let i = 0; i < input.length; i++) {
171
+ const char = input.charCodeAt(i);
172
+ hash = ((hash << 5) - hash) + char;
173
+ hash = hash & hash; // Convert to 32-bit integer
174
+ }
175
+ return Math.abs(hash).toString(36);
176
+ }
177
+ /**
178
+ * Hash fingerprint components into a stable identifier
179
+ */
180
+ function hashFingerprint(components) {
181
+ // Convert components to canonical string representation
182
+ const canonical = JSON.stringify(components, Object.keys(components).sort());
183
+ // Use MurmurHash3 x64 for consistent hashing
184
+ return x64hash128(canonical);
185
+ }
186
+ /**
187
+ * Generate a visitor ID from fingerprint hash
188
+ */
189
+ function generateVisitorId(fingerprintHash) {
190
+ // Use first 16 characters of the hash for visitor ID
191
+ return 'vis_' + fingerprintHash.substring(0, 16);
192
+ }
193
+
194
+ /**
195
+ * Canvas Fingerprinting
196
+ * Based on FingerprintJS canvas component with incognito detection
197
+ */
198
+ /**
199
+ * Text to render for canvas fingerprinting
200
+ */
201
+ const CANVAS_TEXT = 'RabbitTracker Canvas 🎨 🔒 2024';
202
+ /**
203
+ * Geometric shapes for canvas fingerprinting
204
+ */
205
+ function drawGeometry(ctx) {
206
+ // Set up styles
207
+ ctx.fillStyle = 'rgb(102, 204, 0)';
208
+ ctx.fillRect(10, 10, 50, 50);
209
+ ctx.fillStyle = '#f60';
210
+ ctx.fillRect(70, 10, 50, 50);
211
+ // Draw circle
212
+ ctx.beginPath();
213
+ ctx.arc(50, 80, 20, 0, Math.PI * 2, true);
214
+ ctx.closePath();
215
+ ctx.fill();
216
+ // Draw triangle
217
+ ctx.beginPath();
218
+ ctx.moveTo(100, 80);
219
+ ctx.lineTo(120, 120);
220
+ ctx.lineTo(80, 120);
221
+ ctx.closePath();
222
+ ctx.stroke();
223
+ // Add gradient
224
+ const gradient = ctx.createLinearGradient(0, 0, 150, 150);
225
+ gradient.addColorStop(0, 'red');
226
+ gradient.addColorStop(1, 'blue');
227
+ ctx.fillStyle = gradient;
228
+ ctx.fillRect(130, 10, 50, 50);
229
+ }
230
+ /**
231
+ * Draw text with various styles
232
+ */
233
+ function drawText(ctx) {
234
+ // Text with emoji and special characters
235
+ ctx.textBaseline = 'top';
236
+ ctx.font = '14px Arial, sans-serif';
237
+ ctx.fillStyle = '#000';
238
+ ctx.fillText(CANVAS_TEXT, 4, 140);
239
+ // Different font
240
+ ctx.font = '12px Georgia, serif';
241
+ ctx.fillStyle = '#666';
242
+ ctx.fillText('Georgia Font Test', 4, 160);
243
+ // Bold text
244
+ ctx.font = 'bold 16px Helvetica';
245
+ ctx.fillStyle = '#333';
246
+ ctx.fillText('Bold Helvetica', 4, 180);
247
+ // Apply transformations
248
+ ctx.save();
249
+ ctx.scale(1.2, 0.8);
250
+ ctx.font = '10px Courier New';
251
+ ctx.fillStyle = '#999';
252
+ ctx.fillText('Transformed Text', 4, 220);
253
+ ctx.restore();
254
+ }
255
+ /**
256
+ * Detect potential incognito mode through canvas inconsistencies
257
+ */
258
+ function detectInconsistencies(textData, geometryData, ctx) {
259
+ try {
260
+ // Test if getImageData works consistently
261
+ const imageData = ctx.getImageData(0, 0, 1, 1);
262
+ // In some browsers in incognito mode, canvas operations might be slightly different
263
+ // This is a heuristic check
264
+ if (textData.length < 100 || geometryData.length < 100) {
265
+ return true; // Suspiciously short data
266
+ }
267
+ // Check for specific patterns that might indicate canvas blocking
268
+ const hasExpectedPatterns = textData.includes('data:image/png') &&
269
+ geometryData.includes('data:image/png');
270
+ return !hasExpectedPatterns;
271
+ }
272
+ catch (error) {
273
+ // If getImageData fails, it might indicate blocking
274
+ return true;
275
+ }
276
+ }
277
+ /**
278
+ * Generate canvas fingerprint
279
+ */
280
+ async function getCanvasFingerprint() {
281
+ const startTime = performance.now();
282
+ try {
283
+ // Create canvas element
284
+ const canvas = document.createElement('canvas');
285
+ canvas.width = 200;
286
+ canvas.height = 250;
287
+ const ctx = canvas.getContext('2d');
288
+ if (!ctx) {
289
+ throw new Error('Canvas 2D context not available');
290
+ }
291
+ // Test canvas winding (different behavior across browsers)
292
+ ctx.rect(0, 0, 10, 10);
293
+ ctx.rect(2, 2, 6, 6);
294
+ const isClockwise = ctx.isPointInPath(5, 5, 'evenodd');
295
+ // Draw text
296
+ drawText(ctx);
297
+ const textData = canvas.toDataURL();
298
+ // Clear and draw geometry
299
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
300
+ drawGeometry(ctx);
301
+ const geometryData = canvas.toDataURL();
302
+ // Check for inconsistencies that might indicate incognito mode
303
+ const isInconsistent = detectInconsistencies(textData, geometryData, ctx);
304
+ const endTime = performance.now();
305
+ const result = {
306
+ text: hash32(textData),
307
+ geometry: hash32(geometryData),
308
+ winding: isClockwise,
309
+ isInconsistent
310
+ };
311
+ return {
312
+ value: result,
313
+ duration: endTime - startTime
314
+ };
315
+ }
316
+ catch (error) {
317
+ return {
318
+ value: {
319
+ text: 'error',
320
+ geometry: 'error',
321
+ winding: false,
322
+ isInconsistent: true
323
+ },
324
+ duration: performance.now() - startTime,
325
+ error: error instanceof Error ? error.message : 'Canvas fingerprinting failed'
326
+ };
327
+ }
328
+ }
329
+ /**
330
+ * Check if canvas fingerprinting is available
331
+ */
332
+ function isCanvasAvailable$1() {
333
+ try {
334
+ const canvas = document.createElement('canvas');
335
+ const ctx = canvas.getContext('2d');
336
+ return ctx !== null && typeof ctx.fillText === 'function';
337
+ }
338
+ catch {
339
+ return false;
340
+ }
341
+ }
342
+
343
+ /**
344
+ * WebGL Fingerprinting
345
+ * Based on FingerprintJS WebGL component for GPU and driver detection
346
+ */
347
+ /**
348
+ * WebGL parameter constants for fingerprinting
349
+ */
350
+ const WEBGL_PARAMS = {
351
+ VENDOR: 0x1f00,
352
+ RENDERER: 0x1f01,
353
+ VERSION: 0x1f02,
354
+ SHADING_LANGUAGE_VERSION: 0x8b8c,
355
+ MAX_VERTEX_ATTRIBS: 0x8869,
356
+ MAX_TEXTURE_SIZE: 0x0d33,
357
+ MAX_RENDERBUFFER_SIZE: 0x84e8,
358
+ MAX_VIEWPORT_DIMS: 0x0d3a,
359
+ ALIASED_LINE_WIDTH_RANGE: 0x846e,
360
+ ALIASED_POINT_SIZE_RANGE: 0x846d,
361
+ MAX_FRAGMENT_UNIFORM_VECTORS: 0x8dfd,
362
+ MAX_VERTEX_UNIFORM_VECTORS: 0x8dfb,
363
+ MAX_VARYING_VECTORS: 0x8dfc,
364
+ MAX_VERTEX_TEXTURE_IMAGE_UNITS: 0x8b4c,
365
+ MAX_TEXTURE_IMAGE_UNITS: 0x8872,
366
+ MAX_COMBINED_TEXTURE_IMAGE_UNITS: 0x8b4d,
367
+ };
368
+ /**
369
+ * Get WebGL context with fallback
370
+ */
371
+ function getWebGLContext() {
372
+ const canvas = document.createElement('canvas');
373
+ // Try different context types
374
+ const contextTypes = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl'];
375
+ for (const contextType of contextTypes) {
376
+ try {
377
+ const gl = canvas.getContext(contextType);
378
+ if (gl)
379
+ return gl;
380
+ }
381
+ catch {
382
+ // Continue trying other context types
383
+ }
384
+ }
385
+ return null;
386
+ }
387
+ /**
388
+ * Get WebGL extensions
389
+ */
390
+ function getWebGLExtensions(gl) {
391
+ const extensions = [];
392
+ try {
393
+ const supportedExtensions = gl.getSupportedExtensions();
394
+ if (supportedExtensions) {
395
+ extensions.push(...supportedExtensions.sort());
396
+ }
397
+ }
398
+ catch {
399
+ // Extensions not available
400
+ }
401
+ return extensions;
402
+ }
403
+ /**
404
+ * Get WebGL parameter value safely
405
+ */
406
+ function getWebGLParameter(gl, param) {
407
+ try {
408
+ return gl.getParameter(param);
409
+ }
410
+ catch {
411
+ return null;
412
+ }
413
+ }
414
+ /**
415
+ * Get shader precision information
416
+ */
417
+ function getShaderPrecision(gl) {
418
+ const vertex = [];
419
+ const fragment = [];
420
+ try {
421
+ const precisionTypes = [gl.LOW_FLOAT, gl.MEDIUM_FLOAT, gl.HIGH_FLOAT, gl.LOW_INT, gl.MEDIUM_INT, gl.HIGH_INT];
422
+ const precisionNames = ['LOW_FLOAT', 'MEDIUM_FLOAT', 'HIGH_FLOAT', 'LOW_INT', 'MEDIUM_INT', 'HIGH_INT'];
423
+ for (let i = 0; i < precisionTypes.length; i++) {
424
+ const precisionType = precisionTypes[i];
425
+ if (precisionType === undefined)
426
+ continue;
427
+ const vertexPrecision = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, precisionType);
428
+ const fragmentPrecision = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, precisionType);
429
+ if (vertexPrecision) {
430
+ vertex.push(`${precisionNames[i]}:${vertexPrecision.precision},${vertexPrecision.rangeMin},${vertexPrecision.rangeMax}`);
431
+ }
432
+ if (fragmentPrecision) {
433
+ fragment.push(`${precisionNames[i]}:${fragmentPrecision.precision},${fragmentPrecision.rangeMin},${fragmentPrecision.rangeMax}`);
434
+ }
435
+ }
436
+ }
437
+ catch {
438
+ // Precision format not available
439
+ }
440
+ return {
441
+ vertex: vertex.join(';'),
442
+ fragment: fragment.join(';')
443
+ };
444
+ }
445
+ /**
446
+ * Collect WebGL parameters for fingerprinting
447
+ */
448
+ function collectWebGLParameters(gl) {
449
+ const parameters = {};
450
+ // Collect standard parameters
451
+ Object.entries(WEBGL_PARAMS).forEach(([name, param]) => {
452
+ const value = getWebGLParameter(gl, param);
453
+ if (value !== null) {
454
+ // Convert arrays to strings for consistent serialization
455
+ parameters[name] = Array.isArray(value) ? value.join(',') : value;
456
+ }
457
+ });
458
+ // Additional parameters that might be available
459
+ const additionalParams = [
460
+ 'UNMASKED_VENDOR_WEBGL',
461
+ 'UNMASKED_RENDERER_WEBGL'
462
+ ];
463
+ additionalParams.forEach(paramName => {
464
+ try {
465
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
466
+ if (debugInfo) {
467
+ const param = debugInfo[paramName];
468
+ if (param !== undefined) {
469
+ const value = gl.getParameter(param);
470
+ if (value) {
471
+ parameters[paramName] = value;
472
+ }
473
+ }
474
+ }
475
+ }
476
+ catch {
477
+ // Debug renderer info not available
478
+ }
479
+ });
480
+ return parameters;
481
+ }
482
+ /**
483
+ * Generate WebGL fingerprint
484
+ */
485
+ async function getWebGLFingerprint() {
486
+ const startTime = performance.now();
487
+ try {
488
+ const gl = getWebGLContext();
489
+ if (!gl) {
490
+ throw new Error('WebGL not available');
491
+ }
492
+ // Get basic WebGL information
493
+ const vendor = getWebGLParameter(gl, gl.VENDOR) || 'unknown';
494
+ const renderer = getWebGLParameter(gl, gl.RENDERER) || 'unknown';
495
+ const version = getWebGLParameter(gl, gl.VERSION) || 'unknown';
496
+ // Get unmasked vendor/renderer if available (more specific GPU info)
497
+ let unmaskedVendor = vendor;
498
+ let unmaskedRenderer = renderer;
499
+ try {
500
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
501
+ if (debugInfo) {
502
+ unmaskedVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || vendor;
503
+ unmaskedRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || renderer;
504
+ }
505
+ }
506
+ catch {
507
+ // Debug renderer info blocked or not available
508
+ }
509
+ // Collect extensions
510
+ const extensions = getWebGLExtensions(gl);
511
+ // Collect parameters
512
+ const parameters = collectWebGLParameters(gl);
513
+ // Get shader precision
514
+ const shaderPrecision = getShaderPrecision(gl);
515
+ const endTime = performance.now();
516
+ const result = {
517
+ vendor: unmaskedVendor,
518
+ renderer: unmaskedRenderer,
519
+ version,
520
+ extensions,
521
+ parameters,
522
+ shaderPrecision
523
+ };
524
+ return {
525
+ value: result,
526
+ duration: endTime - startTime
527
+ };
528
+ }
529
+ catch (error) {
530
+ return {
531
+ value: {
532
+ vendor: 'unknown',
533
+ renderer: 'unknown',
534
+ version: 'unknown',
535
+ extensions: [],
536
+ parameters: {},
537
+ shaderPrecision: { vertex: '', fragment: '' }
538
+ },
539
+ duration: performance.now() - startTime,
540
+ error: error instanceof Error ? error.message : 'WebGL fingerprinting failed'
541
+ };
542
+ }
543
+ }
544
+ /**
545
+ * Check if WebGL fingerprinting is available
546
+ */
547
+ function isWebGLAvailable() {
548
+ try {
549
+ const canvas = document.createElement('canvas');
550
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
551
+ return gl !== null;
552
+ }
553
+ catch {
554
+ return false;
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Audio Fingerprinting
560
+ * Based on FingerprintJS audio component using AudioContext
561
+ */
562
+ /**
563
+ * Audio configuration for fingerprinting
564
+ */
565
+ const AUDIO_CONFIG = {
566
+ SAMPLE_RATE: 44100,
567
+ DURATION: 0.1, // 100ms
568
+ FREQUENCY: 1000, // 1kHz tone
569
+ COMPRESSOR_THRESHOLD: -50,
570
+ COMPRESSOR_KNEE: 40,
571
+ COMPRESSOR_RATIO: 12,
572
+ COMPRESSOR_ATTACK: 0.003,
573
+ COMPRESSOR_RELEASE: 0.25,
574
+ };
575
+ /**
576
+ * Create audio context with fallbacks
577
+ */
578
+ function createAudioContext() {
579
+ try {
580
+ const AudioContextClass = window.AudioContext || window.webkitAudioContext;
581
+ if (!AudioContextClass)
582
+ return null;
583
+ return new AudioContextClass();
584
+ }
585
+ catch {
586
+ return null;
587
+ }
588
+ }
589
+ /**
590
+ * Generate oscillator fingerprint
591
+ */
592
+ async function generateOscillatorFingerprint(audioContext) {
593
+ const oscillator = audioContext.createOscillator();
594
+ const compressor = audioContext.createDynamicsCompressor();
595
+ const buffer = audioContext.createBuffer(1, audioContext.sampleRate * AUDIO_CONFIG.DURATION, audioContext.sampleRate);
596
+ // Configure oscillator
597
+ oscillator.type = 'triangle';
598
+ oscillator.frequency.setValueAtTime(AUDIO_CONFIG.FREQUENCY, audioContext.currentTime);
599
+ // Configure compressor
600
+ compressor.threshold.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_THRESHOLD, audioContext.currentTime);
601
+ compressor.knee.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_KNEE, audioContext.currentTime);
602
+ compressor.ratio.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_RATIO, audioContext.currentTime);
603
+ compressor.attack.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_ATTACK, audioContext.currentTime);
604
+ compressor.release.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_RELEASE, audioContext.currentTime);
605
+ // Connect nodes
606
+ oscillator.connect(compressor);
607
+ compressor.connect(audioContext.destination);
608
+ // Start and stop oscillator
609
+ oscillator.start(audioContext.currentTime);
610
+ oscillator.stop(audioContext.currentTime + AUDIO_CONFIG.DURATION);
611
+ // Wait for processing and get buffer data
612
+ await new Promise(resolve => setTimeout(resolve, AUDIO_CONFIG.DURATION * 1000 + 50));
613
+ // Generate fingerprint from audio characteristics
614
+ const channelData = buffer.getChannelData(0);
615
+ const samples = Array.from(channelData.slice(0, 100)); // First 100 samples
616
+ return hash32(samples.join(','));
617
+ }
618
+ /**
619
+ * Generate compressor fingerprint
620
+ */
621
+ async function generateCompressorFingerprint(audioContext) {
622
+ try {
623
+ const oscillator = audioContext.createOscillator();
624
+ const compressor = audioContext.createDynamicsCompressor();
625
+ const gain = audioContext.createGain();
626
+ // Create offline context for deterministic results
627
+ const offlineContext = new OfflineAudioContext(1, audioContext.sampleRate * AUDIO_CONFIG.DURATION, audioContext.sampleRate);
628
+ const offlineOscillator = offlineContext.createOscillator();
629
+ const offlineCompressor = offlineContext.createDynamicsCompressor();
630
+ // Configure nodes
631
+ offlineOscillator.type = 'triangle';
632
+ offlineOscillator.frequency.setValueAtTime(AUDIO_CONFIG.FREQUENCY, offlineContext.currentTime);
633
+ offlineCompressor.threshold.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_THRESHOLD, offlineContext.currentTime);
634
+ offlineCompressor.knee.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_KNEE, offlineContext.currentTime);
635
+ offlineCompressor.ratio.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_RATIO, offlineContext.currentTime);
636
+ offlineCompressor.attack.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_ATTACK, offlineContext.currentTime);
637
+ offlineCompressor.release.setValueAtTime(AUDIO_CONFIG.COMPRESSOR_RELEASE, offlineContext.currentTime);
638
+ // Connect and render
639
+ offlineOscillator.connect(offlineCompressor);
640
+ offlineCompressor.connect(offlineContext.destination);
641
+ offlineOscillator.start();
642
+ offlineOscillator.stop(AUDIO_CONFIG.DURATION);
643
+ const renderedBuffer = await offlineContext.startRendering();
644
+ const channelData = renderedBuffer.getChannelData(0);
645
+ // Create fingerprint from specific samples
646
+ const fingerprintSamples = [
647
+ channelData[0] || 0,
648
+ channelData[Math.floor(channelData.length * 0.25)] || 0,
649
+ channelData[Math.floor(channelData.length * 0.5)] || 0,
650
+ channelData[Math.floor(channelData.length * 0.75)] || 0,
651
+ channelData[channelData.length - 1] || 0
652
+ ];
653
+ return hash32(fingerprintSamples.join(','));
654
+ }
655
+ catch (error) {
656
+ // Fallback for browsers without OfflineAudioContext
657
+ return hash32('compressor_fallback');
658
+ }
659
+ }
660
+ /**
661
+ * Generate audio fingerprint
662
+ */
663
+ async function getAudioFingerprint() {
664
+ const startTime = performance.now();
665
+ try {
666
+ const audioContext = createAudioContext();
667
+ if (!audioContext) {
668
+ throw new Error('AudioContext not available');
669
+ }
670
+ // Resume context if needed (Chrome autoplay policy)
671
+ if (audioContext.state === 'suspended') {
672
+ try {
673
+ await audioContext.resume();
674
+ }
675
+ catch {
676
+ // Cannot resume context
677
+ }
678
+ }
679
+ // Generate fingerprints
680
+ const [oscillatorHash, compressorHash] = await Promise.all([
681
+ generateOscillatorFingerprint(audioContext).catch(() => 'oscillator_error'),
682
+ generateCompressorFingerprint(audioContext).catch(() => 'compressor_error')
683
+ ]);
684
+ // Close audio context to free resources
685
+ try {
686
+ await audioContext.close();
687
+ }
688
+ catch {
689
+ // Context close failed
690
+ }
691
+ const endTime = performance.now();
692
+ const result = {
693
+ oscillator: oscillatorHash,
694
+ compressor: compressorHash,
695
+ sampleRate: audioContext.sampleRate,
696
+ maxChannelCount: audioContext.destination.maxChannelCount
697
+ };
698
+ return {
699
+ value: result,
700
+ duration: endTime - startTime
701
+ };
702
+ }
703
+ catch (error) {
704
+ return {
705
+ value: {
706
+ oscillator: 'error',
707
+ compressor: 'error',
708
+ sampleRate: 0,
709
+ maxChannelCount: 0
710
+ },
711
+ duration: performance.now() - startTime,
712
+ error: error instanceof Error ? error.message : 'Audio fingerprinting failed'
713
+ };
714
+ }
715
+ }
716
+ /**
717
+ * Check if audio fingerprinting is available
718
+ */
719
+ function isAudioAvailable() {
720
+ try {
721
+ const AudioContextClass = window.AudioContext || window.webkitAudioContext;
722
+ return typeof AudioContextClass === 'function';
723
+ }
724
+ catch {
725
+ return false;
726
+ }
727
+ }
728
+
729
+ /**
730
+ * Font Detection Fingerprinting
731
+ * Based on FingerprintJS font detection techniques
732
+ */
733
+ /**
734
+ * Font list for detection (commonly available system fonts)
735
+ */
736
+ const FONT_LIST = [
737
+ // Windows fonts
738
+ 'Arial', 'Arial Black', 'Calibri', 'Cambria', 'Comic Sans MS', 'Consolas',
739
+ 'Courier New', 'Georgia', 'Impact', 'Lucida Console', 'Lucida Sans Unicode',
740
+ 'Microsoft Sans Serif', 'Palatino Linotype', 'Segoe UI', 'Tahoma', 'Times New Roman',
741
+ 'Trebuchet MS', 'Verdana',
742
+ // macOS fonts
743
+ 'American Typewriter', 'Avenir', 'Baskerville', 'Big Caslon', 'Brush Script MT',
744
+ 'Copperplate', 'Didot', 'Futura', 'Gill Sans', 'Helvetica', 'Helvetica Neue',
745
+ 'Hoefler Text', 'Lucida Grande', 'Marker Felt', 'Optima', 'Papyrus',
746
+ 'Phosphate', 'Rockwell', 'Savoye LET', 'SignPainter', 'Skia', 'Snell Roundhand',
747
+ 'System Font', 'Zapfino',
748
+ // Linux fonts
749
+ 'DejaVu Sans', 'DejaVu Sans Mono', 'DejaVu Serif', 'Droid Sans', 'Droid Sans Mono',
750
+ 'Droid Serif', 'Liberation Sans', 'Liberation Sans Narrow', 'Liberation Serif',
751
+ 'Ubuntu', 'Ubuntu Mono',
752
+ // Android fonts
753
+ 'Roboto', 'Roboto Condensed', 'Roboto Mono', 'Roboto Slab', 'Droid Sans',
754
+ 'Droid Sans Mono', 'Droid Serif',
755
+ // iOS fonts
756
+ 'Helvetica Neue', 'Arial', 'Helvetica', 'Courier New', 'Times New Roman',
757
+ 'San Francisco', 'Avenir Next',
758
+ // Common web fonts
759
+ 'Open Sans', 'Lato', 'Montserrat', 'Source Sans Pro', 'Raleway', 'PT Sans',
760
+ 'Lora', 'Playfair Display', 'Oswald', 'Slabo 27px', 'Fira Sans',
761
+ // East Asian fonts
762
+ 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'MS Gothic', 'MS Mincho',
763
+ 'SimSun', 'SimHei', 'Microsoft YaHei', 'Malgun Gothic', 'Apple SD Gothic Neo'
764
+ ];
765
+ /**
766
+ * Fallback fonts for measurement
767
+ */
768
+ const FALLBACK_FONTS = ['monospace', 'sans-serif', 'serif'];
769
+ /**
770
+ * Test text for font measurement
771
+ */
772
+ const TEST_TEXT = 'mmMwWLliI0O&1 ※';
773
+ /**
774
+ * Font measurement using canvas
775
+ */
776
+ function measureText(text, fontFamily) {
777
+ const canvas = document.createElement('canvas');
778
+ const ctx = canvas.getContext('2d');
779
+ if (!ctx) {
780
+ return { width: 0, height: 0 };
781
+ }
782
+ ctx.font = `12px ${fontFamily}`;
783
+ const metrics = ctx.measureText(text);
784
+ return {
785
+ width: metrics.width,
786
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
787
+ };
788
+ }
789
+ /**
790
+ * Font measurement using DOM (fallback method)
791
+ */
792
+ function measureTextDOM(text, fontFamily) {
793
+ const element = document.createElement('span');
794
+ element.style.position = 'absolute';
795
+ element.style.left = '-9999px';
796
+ element.style.top = '-9999px';
797
+ element.style.fontSize = '12px';
798
+ element.style.fontFamily = fontFamily;
799
+ element.style.whiteSpace = 'nowrap';
800
+ element.textContent = text;
801
+ document.body.appendChild(element);
802
+ const rect = element.getBoundingClientRect();
803
+ const result = {
804
+ width: rect.width,
805
+ height: rect.height
806
+ };
807
+ document.body.removeChild(element);
808
+ return result;
809
+ }
810
+ /**
811
+ * Detect if a font is available by comparing measurements
812
+ */
813
+ function isFontAvailable(fontName, method = 'canvas') {
814
+ const measureFunc = method === 'canvas' ? measureText : measureTextDOM;
815
+ // Measure with fallback fonts first
816
+ const fallbackMeasurements = FALLBACK_FONTS.map(fallback => measureFunc(TEST_TEXT, fallback));
817
+ // Measure with target font + fallback
818
+ const targetMeasurements = FALLBACK_FONTS.map(fallback => measureFunc(TEST_TEXT, `${fontName}, ${fallback}`));
819
+ // Compare measurements - if font is available, measurements should differ
820
+ for (let i = 0; i < fallbackMeasurements.length; i++) {
821
+ const fallback = fallbackMeasurements[i];
822
+ const target = targetMeasurements[i];
823
+ if (fallback && target) {
824
+ // Allow small tolerance for measurement differences
825
+ const widthDiff = Math.abs(fallback.width - target.width);
826
+ const heightDiff = Math.abs(fallback.height - target.height);
827
+ if (widthDiff > 0.1 || heightDiff > 0.1) {
828
+ return true; // Font likely available
829
+ }
830
+ }
831
+ }
832
+ return false;
833
+ }
834
+ /**
835
+ * Advanced font detection with measurements
836
+ */
837
+ function detectFontsWithMeasurements() {
838
+ const available = [];
839
+ const measurements = {};
840
+ for (const font of FONT_LIST) {
841
+ try {
842
+ const measurement = measureText(TEST_TEXT, font);
843
+ measurements[font] = measurement;
844
+ if (isFontAvailable(font, 'canvas')) {
845
+ available.push(font);
846
+ }
847
+ }
848
+ catch (error) {
849
+ // Skip fonts that cause errors
850
+ continue;
851
+ }
852
+ }
853
+ return { available, measurements };
854
+ }
855
+ /**
856
+ * Basic font detection (faster but less detailed)
857
+ */
858
+ function detectFontsBasic() {
859
+ const available = [];
860
+ for (const font of FONT_LIST) {
861
+ try {
862
+ if (isFontAvailable(font, 'dom')) {
863
+ available.push(font);
864
+ }
865
+ }
866
+ catch (error) {
867
+ // Skip fonts that cause errors
868
+ continue;
869
+ }
870
+ }
871
+ return available;
872
+ }
873
+ /**
874
+ * Generate font fingerprint
875
+ */
876
+ async function getFontFingerprint(useAdvanced = true) {
877
+ const startTime = performance.now();
878
+ try {
879
+ let result;
880
+ if (useAdvanced && isCanvasAvailable()) {
881
+ // Advanced detection with measurements
882
+ const { available, measurements } = detectFontsWithMeasurements();
883
+ result = {
884
+ available: available.sort(),
885
+ method: 'advanced',
886
+ measurements
887
+ };
888
+ }
889
+ else {
890
+ // Basic detection
891
+ const available = detectFontsBasic();
892
+ result = {
893
+ available: available.sort(),
894
+ method: 'basic'
895
+ };
896
+ }
897
+ const endTime = performance.now();
898
+ return {
899
+ value: result,
900
+ duration: endTime - startTime
901
+ };
902
+ }
903
+ catch (error) {
904
+ return {
905
+ value: {
906
+ available: [],
907
+ method: 'basic'
908
+ },
909
+ duration: performance.now() - startTime,
910
+ error: error instanceof Error ? error.message : 'Font detection failed'
911
+ };
912
+ }
913
+ }
914
+ /**
915
+ * Check if canvas is available for font measurement
916
+ */
917
+ function isCanvasAvailable() {
918
+ try {
919
+ const canvas = document.createElement('canvas');
920
+ const ctx = canvas.getContext('2d');
921
+ return ctx !== null && typeof ctx.measureText === 'function';
922
+ }
923
+ catch {
924
+ return false;
925
+ }
926
+ }
927
+ /**
928
+ * Check if font detection is available
929
+ */
930
+ function isFontDetectionAvailable() {
931
+ try {
932
+ // Check if we can create DOM elements for measurement
933
+ const element = document.createElement('span');
934
+ return element && typeof element.style === 'object';
935
+ }
936
+ catch {
937
+ return false;
938
+ }
939
+ }
940
+
941
+ /**
942
+ * Screen and Viewport Fingerprinting
943
+ * Based on FingerprintJS screen components
944
+ */
945
+ /**
946
+ * Get screen orientation information
947
+ */
948
+ function getScreenOrientation() {
949
+ try {
950
+ // Modern API
951
+ if (screen.orientation) {
952
+ return {
953
+ angle: screen.orientation.angle,
954
+ type: screen.orientation.type
955
+ };
956
+ }
957
+ // Legacy API
958
+ const orientation = screen.mozOrientation ||
959
+ screen.msOrientation ||
960
+ screen.webkitOrientation;
961
+ if (orientation !== undefined) {
962
+ return {
963
+ angle: orientation,
964
+ type: 'unknown'
965
+ };
966
+ }
967
+ // Fallback: estimate from dimensions
968
+ const isLandscape = screen.width > screen.height;
969
+ return {
970
+ angle: isLandscape ? 90 : 0,
971
+ type: isLandscape ? 'landscape-primary' : 'portrait-primary'
972
+ };
973
+ }
974
+ catch {
975
+ return undefined;
976
+ }
977
+ }
978
+ /**
979
+ * Get device pixel ratio with fallbacks
980
+ */
981
+ function getPixelRatio() {
982
+ try {
983
+ return window.devicePixelRatio || 1;
984
+ }
985
+ catch {
986
+ return 1;
987
+ }
988
+ }
989
+ /**
990
+ * Get available screen dimensions (excluding taskbars, docks, etc.)
991
+ */
992
+ function getAvailableScreenDimensions() {
993
+ try {
994
+ return {
995
+ width: screen.availWidth || screen.width,
996
+ height: screen.availHeight || screen.height
997
+ };
998
+ }
999
+ catch {
1000
+ return {
1001
+ width: screen.width || 0,
1002
+ height: screen.height || 0
1003
+ };
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Get viewport dimensions
1008
+ */
1009
+ function getViewportDimensions() {
1010
+ try {
1011
+ // Use document.documentElement for more accurate measurements
1012
+ const element = document.documentElement;
1013
+ return {
1014
+ width: Math.max(element.clientWidth || 0, window.innerWidth || 0),
1015
+ height: Math.max(element.clientHeight || 0, window.innerHeight || 0)
1016
+ };
1017
+ }
1018
+ catch {
1019
+ return {
1020
+ width: window.innerWidth || 0,
1021
+ height: window.innerHeight || 0
1022
+ };
1023
+ }
1024
+ }
1025
+ /**
1026
+ * Generate screen fingerprint
1027
+ */
1028
+ async function getScreenFingerprint() {
1029
+ const startTime = performance.now();
1030
+ try {
1031
+ // Get screen dimensions
1032
+ const screenWidth = screen.width || 0;
1033
+ const screenHeight = screen.height || 0;
1034
+ const colorDepth = screen.colorDepth || screen.pixelDepth || 0;
1035
+ // Get available screen space
1036
+ const available = getAvailableScreenDimensions();
1037
+ // Get viewport dimensions
1038
+ const viewport = getViewportDimensions();
1039
+ // Get pixel ratio
1040
+ const pixelRatio = getPixelRatio();
1041
+ // Get orientation
1042
+ const orientation = getScreenOrientation();
1043
+ const endTime = performance.now();
1044
+ const result = {
1045
+ // Screen properties
1046
+ width: screenWidth,
1047
+ height: screenHeight,
1048
+ colorDepth,
1049
+ pixelRatio,
1050
+ // Viewport properties
1051
+ viewportWidth: viewport.width,
1052
+ viewportHeight: viewport.height,
1053
+ // Available screen space
1054
+ availableWidth: available.width,
1055
+ availableHeight: available.height
1056
+ };
1057
+ // Add optional properties conditionally
1058
+ if (orientation) {
1059
+ result.orientation = orientation;
1060
+ }
1061
+ return {
1062
+ value: result,
1063
+ duration: endTime - startTime
1064
+ };
1065
+ }
1066
+ catch (error) {
1067
+ return {
1068
+ value: {
1069
+ width: 0,
1070
+ height: 0,
1071
+ colorDepth: 0,
1072
+ pixelRatio: 1,
1073
+ viewportWidth: 0,
1074
+ viewportHeight: 0,
1075
+ availableWidth: 0,
1076
+ availableHeight: 0
1077
+ },
1078
+ duration: performance.now() - startTime,
1079
+ error: error instanceof Error ? error.message : 'Screen fingerprinting failed'
1080
+ };
1081
+ }
1082
+ }
1083
+ /**
1084
+ * Check if screen fingerprinting is available
1085
+ */
1086
+ function isScreenAvailable() {
1087
+ try {
1088
+ return typeof screen === 'object' &&
1089
+ typeof screen.width === 'number' &&
1090
+ typeof screen.height === 'number';
1091
+ }
1092
+ catch {
1093
+ return false;
1094
+ }
1095
+ }
1096
+
1097
+ /**
1098
+ * Browser and System Information Fingerprinting
1099
+ * Based on FingerprintJS browser component with enhanced detection
1100
+ */
1101
+ /**
1102
+ * Get touch support information
1103
+ */
1104
+ function getTouchSupport() {
1105
+ try {
1106
+ let maxTouchPoints = 0;
1107
+ let touchEvent = false;
1108
+ let touchStart = false;
1109
+ // Check maxTouchPoints
1110
+ if ('maxTouchPoints' in navigator) {
1111
+ maxTouchPoints = navigator.maxTouchPoints;
1112
+ }
1113
+ else if ('msMaxTouchPoints' in navigator) {
1114
+ maxTouchPoints = navigator.msMaxTouchPoints;
1115
+ }
1116
+ // Check touch events
1117
+ try {
1118
+ touchEvent = 'TouchEvent' in window;
1119
+ }
1120
+ catch {
1121
+ touchEvent = 'ontouchstart' in window;
1122
+ }
1123
+ // Check touch start
1124
+ touchStart = 'ontouchstart' in window;
1125
+ return { maxTouchPoints, touchEvent, touchStart };
1126
+ }
1127
+ catch {
1128
+ return { maxTouchPoints: 0, touchEvent: false, touchStart: false };
1129
+ }
1130
+ }
1131
+ /**
1132
+ * Get plugin information (mostly for legacy browser detection)
1133
+ */
1134
+ function getPluginInfo() {
1135
+ try {
1136
+ if (!navigator.plugins) {
1137
+ return { length: 0, names: [] };
1138
+ }
1139
+ const plugins = Array.from(navigator.plugins);
1140
+ const names = plugins
1141
+ .map(plugin => plugin.name)
1142
+ .filter(name => name)
1143
+ .sort();
1144
+ return {
1145
+ length: navigator.plugins.length,
1146
+ names: names.slice(0, 10) // Limit to first 10 to avoid huge fingerprints
1147
+ };
1148
+ }
1149
+ catch {
1150
+ return { length: 0, names: [] };
1151
+ }
1152
+ }
1153
+ /**
1154
+ * Get language and locale information
1155
+ */
1156
+ function getLanguageInfo() {
1157
+ try {
1158
+ const language = navigator.language || '';
1159
+ let languages = [];
1160
+ if (navigator.languages) {
1161
+ languages = Array.from(navigator.languages);
1162
+ }
1163
+ else if (navigator.language) {
1164
+ languages = [navigator.language];
1165
+ }
1166
+ // Add fallback language properties
1167
+ const additionalLanguages = [
1168
+ navigator.userLanguage,
1169
+ navigator.browserLanguage,
1170
+ navigator.systemLanguage
1171
+ ].filter(Boolean);
1172
+ languages = [...new Set([...languages, ...additionalLanguages])];
1173
+ return { language, languages };
1174
+ }
1175
+ catch {
1176
+ return { language: '', languages: [] };
1177
+ }
1178
+ }
1179
+ /**
1180
+ * Get timezone information
1181
+ */
1182
+ function getTimezoneInfo() {
1183
+ try {
1184
+ let timezone = '';
1185
+ // Modern API
1186
+ if (Intl && Intl.DateTimeFormat) {
1187
+ try {
1188
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1189
+ }
1190
+ catch {
1191
+ // Fallback
1192
+ }
1193
+ }
1194
+ // Fallback: use timezone offset
1195
+ if (!timezone) {
1196
+ const offset = new Date().getTimezoneOffset();
1197
+ const sign = offset > 0 ? '-' : '+';
1198
+ const hours = Math.floor(Math.abs(offset) / 60);
1199
+ const minutes = Math.abs(offset) % 60;
1200
+ timezone = `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
1201
+ }
1202
+ const timezoneOffset = new Date().getTimezoneOffset();
1203
+ return { timezone, timezoneOffset };
1204
+ }
1205
+ catch {
1206
+ return { timezone: '', timezoneOffset: 0 };
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Get hardware information
1211
+ */
1212
+ function getHardwareInfo() {
1213
+ try {
1214
+ const hardwareConcurrency = navigator.hardwareConcurrency || 0;
1215
+ // Device memory (Chrome-specific)
1216
+ let deviceMemory;
1217
+ if ('deviceMemory' in navigator) {
1218
+ deviceMemory = navigator.deviceMemory;
1219
+ }
1220
+ const result = { hardwareConcurrency };
1221
+ if (deviceMemory !== undefined) {
1222
+ result.deviceMemory = deviceMemory;
1223
+ }
1224
+ return result;
1225
+ }
1226
+ catch {
1227
+ return { hardwareConcurrency: 0 };
1228
+ }
1229
+ }
1230
+ /**
1231
+ * Get platform information with enhanced detection
1232
+ */
1233
+ function getPlatformInfo() {
1234
+ try {
1235
+ const platform = navigator.platform || '';
1236
+ const userAgent = navigator.userAgent || '';
1237
+ return { platform, userAgent };
1238
+ }
1239
+ catch {
1240
+ return { platform: '', userAgent: '' };
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Get browser capabilities
1245
+ */
1246
+ function getBrowserCapabilities() {
1247
+ try {
1248
+ const cookieEnabled = navigator.cookieEnabled !== false;
1249
+ // Do Not Track
1250
+ let doNotTrack = null;
1251
+ if ('doNotTrack' in navigator) {
1252
+ doNotTrack = navigator.doNotTrack;
1253
+ }
1254
+ else if ('msDoNotTrack' in navigator) {
1255
+ doNotTrack = navigator.msDoNotTrack;
1256
+ }
1257
+ else if ('mozDoNotTrack' in window) {
1258
+ doNotTrack = window.mozDoNotTrack;
1259
+ }
1260
+ return { cookieEnabled, doNotTrack };
1261
+ }
1262
+ catch {
1263
+ return { cookieEnabled: true, doNotTrack: null };
1264
+ }
1265
+ }
1266
+ /**
1267
+ * Generate browser fingerprint
1268
+ */
1269
+ async function getBrowserFingerprint() {
1270
+ const startTime = performance.now();
1271
+ try {
1272
+ // Collect all browser information
1273
+ const languageInfo = getLanguageInfo();
1274
+ const timezoneInfo = getTimezoneInfo();
1275
+ const platformInfo = getPlatformInfo();
1276
+ const hardwareInfo = getHardwareInfo();
1277
+ const capabilities = getBrowserCapabilities();
1278
+ const pluginInfo = getPluginInfo();
1279
+ const touchSupport = getTouchSupport();
1280
+ const endTime = performance.now();
1281
+ const result = {
1282
+ // Language and locale
1283
+ language: languageInfo.language,
1284
+ languages: languageInfo.languages,
1285
+ // Timezone
1286
+ timezone: timezoneInfo.timezone,
1287
+ timezoneOffset: timezoneInfo.timezoneOffset,
1288
+ // Platform information
1289
+ platform: platformInfo.platform,
1290
+ userAgent: platformInfo.userAgent,
1291
+ // Hardware information
1292
+ hardwareConcurrency: hardwareInfo.hardwareConcurrency,
1293
+ // Browser capabilities
1294
+ cookieEnabled: capabilities.cookieEnabled,
1295
+ doNotTrack: capabilities.doNotTrack,
1296
+ // Plugin information
1297
+ plugins: pluginInfo,
1298
+ // Touch support
1299
+ touchSupport
1300
+ };
1301
+ // Add optional properties conditionally
1302
+ if (hardwareInfo.deviceMemory !== undefined) {
1303
+ result.deviceMemory = hardwareInfo.deviceMemory;
1304
+ }
1305
+ return {
1306
+ value: result,
1307
+ duration: endTime - startTime
1308
+ };
1309
+ }
1310
+ catch (error) {
1311
+ return {
1312
+ value: {
1313
+ language: '',
1314
+ languages: [],
1315
+ timezone: '',
1316
+ timezoneOffset: 0,
1317
+ platform: '',
1318
+ userAgent: '',
1319
+ hardwareConcurrency: 0,
1320
+ cookieEnabled: true,
1321
+ doNotTrack: null,
1322
+ plugins: { length: 0, names: [] },
1323
+ touchSupport: { maxTouchPoints: 0, touchEvent: false, touchStart: false }
1324
+ },
1325
+ duration: performance.now() - startTime,
1326
+ error: error instanceof Error ? error.message : 'Browser fingerprinting failed'
1327
+ };
1328
+ }
1329
+ }
1330
+ /**
1331
+ * Check if browser fingerprinting is available
1332
+ */
1333
+ function isBrowserFingerprintingAvailable() {
1334
+ try {
1335
+ return typeof navigator === 'object' && navigator !== null;
1336
+ }
1337
+ catch {
1338
+ return false;
1339
+ }
1340
+ }
1341
+
1342
+ /**
1343
+ * Fingerprint Orchestrator
1344
+ * Main fingerprinting engine that coordinates all components
1345
+ * Based on FingerprintJS architecture with privacy-first approach
1346
+ */
1347
+ // Import individual fingerprinting components
1348
+ /**
1349
+ * Default fingerprint options (cookieless - memory cache only)
1350
+ */
1351
+ const DEFAULT_OPTIONS = {
1352
+ gdprMode: false,
1353
+ includeInvasive: true,
1354
+ timeout: 5000,
1355
+ excludeComponents: []
1356
+ };
1357
+ /**
1358
+ * Session-only cache for fingerprint components (memory only, no persistence)
1359
+ */
1360
+ const sessionCache = new Map();
1361
+ /**
1362
+ * Check if component should be included based on options
1363
+ */
1364
+ function shouldIncludeComponent(component, options) {
1365
+ // Check if component is excluded
1366
+ if (options.excludeComponents.includes(component)) {
1367
+ return false;
1368
+ }
1369
+ // In GDPR mode, exclude invasive components unless explicitly allowed
1370
+ if (options.gdprMode && !options.includeInvasive) {
1371
+ const invasiveComponents = ['canvas', 'webgl', 'audio'];
1372
+ if (invasiveComponents.includes(component)) {
1373
+ return false;
1374
+ }
1375
+ }
1376
+ return true;
1377
+ }
1378
+ /**
1379
+ * Get cached component data from session cache (memory only)
1380
+ */
1381
+ function getSessionCachedComponent(key) {
1382
+ return sessionCache.get(key) || null;
1383
+ }
1384
+ /**
1385
+ * Cache component data in session memory only
1386
+ */
1387
+ function setSessionCachedComponent(key, data) {
1388
+ sessionCache.set(key, data);
1389
+ }
1390
+ /**
1391
+ * Collect fingerprint component with timeout and session caching
1392
+ */
1393
+ async function collectComponent(componentType, collector, options) {
1394
+ const cacheKey = `${componentType}_${options.gdprMode}`;
1395
+ // Check session cache first (memory only)
1396
+ const cached = getSessionCachedComponent(cacheKey);
1397
+ if (cached) {
1398
+ return cached;
1399
+ }
1400
+ try {
1401
+ // Create timeout promise
1402
+ const timeoutPromise = new Promise((_, reject) => {
1403
+ setTimeout(() => reject(new Error(`${componentType} timeout`)), options.timeout);
1404
+ });
1405
+ // Race collection vs timeout
1406
+ const result = await Promise.race([collector(), timeoutPromise]);
1407
+ // Cache successful result in session memory only
1408
+ setSessionCachedComponent(cacheKey, result);
1409
+ return result;
1410
+ }
1411
+ catch (error) {
1412
+ // Return null on error (component will be marked as failed)
1413
+ return null;
1414
+ }
1415
+ }
1416
+ /**
1417
+ * Main fingerprint collection function
1418
+ */
1419
+ async function collectFingerprint(options = {}) {
1420
+ const startTime = performance.now();
1421
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1422
+ const collectedComponents = [];
1423
+ const failedComponents = [];
1424
+ const fingerprintData = {
1425
+ collectionTime: startTime,
1426
+ gdprMode: opts.gdprMode,
1427
+ components: []
1428
+ };
1429
+ // Collect Canvas fingerprint
1430
+ if (shouldIncludeComponent('canvas', opts) && isCanvasAvailable$1()) {
1431
+ const result = await collectComponent('canvas', getCanvasFingerprint, opts);
1432
+ if (result) {
1433
+ fingerprintData.canvas = result;
1434
+ collectedComponents.push('canvas');
1435
+ }
1436
+ else {
1437
+ failedComponents.push({ component: 'canvas', error: 'Collection failed or timeout' });
1438
+ }
1439
+ }
1440
+ // Collect WebGL fingerprint
1441
+ if (shouldIncludeComponent('webgl', opts) && isWebGLAvailable()) {
1442
+ const result = await collectComponent('webgl', getWebGLFingerprint, opts);
1443
+ if (result) {
1444
+ fingerprintData.webgl = result;
1445
+ collectedComponents.push('webgl');
1446
+ }
1447
+ else {
1448
+ failedComponents.push({ component: 'webgl', error: 'Collection failed or timeout' });
1449
+ }
1450
+ }
1451
+ // Collect Audio fingerprint
1452
+ if (shouldIncludeComponent('audio', opts) && isAudioAvailable()) {
1453
+ const result = await collectComponent('audio', getAudioFingerprint, opts);
1454
+ if (result) {
1455
+ fingerprintData.audio = result;
1456
+ collectedComponents.push('audio');
1457
+ }
1458
+ else {
1459
+ failedComponents.push({ component: 'audio', error: 'Collection failed or timeout' });
1460
+ }
1461
+ }
1462
+ // Collect Font fingerprint
1463
+ if (shouldIncludeComponent('fonts', opts) && isFontDetectionAvailable()) {
1464
+ const result = await collectComponent('fonts', () => getFontFingerprint(!opts.gdprMode), opts);
1465
+ if (result) {
1466
+ fingerprintData.fonts = result;
1467
+ collectedComponents.push('fonts');
1468
+ }
1469
+ else {
1470
+ failedComponents.push({ component: 'fonts', error: 'Collection failed or timeout' });
1471
+ }
1472
+ }
1473
+ // Collect Screen fingerprint (always available)
1474
+ if (shouldIncludeComponent('screen', opts) && isScreenAvailable()) {
1475
+ const result = await collectComponent('screen', getScreenFingerprint, opts);
1476
+ if (result) {
1477
+ fingerprintData.screen = result;
1478
+ collectedComponents.push('screen');
1479
+ }
1480
+ else {
1481
+ failedComponents.push({ component: 'screen', error: 'Collection failed or timeout' });
1482
+ }
1483
+ }
1484
+ // Collect Browser fingerprint (always available)
1485
+ if (shouldIncludeComponent('browser', opts) && isBrowserFingerprintingAvailable()) {
1486
+ const result = await collectComponent('browser', getBrowserFingerprint, opts);
1487
+ if (result) {
1488
+ fingerprintData.browser = result;
1489
+ collectedComponents.push('browser');
1490
+ }
1491
+ else {
1492
+ failedComponents.push({ component: 'browser', error: 'Collection failed or timeout' });
1493
+ }
1494
+ }
1495
+ // Generate hash from collected components
1496
+ const componentValues = {};
1497
+ collectedComponents.forEach(component => {
1498
+ const componentData = fingerprintData[component];
1499
+ if (componentData && componentData.value) {
1500
+ componentValues[component] = componentData.value;
1501
+ }
1502
+ });
1503
+ // Create final fingerprint hash
1504
+ const hash = hashFingerprint(componentValues);
1505
+ // Finalize fingerprint data
1506
+ const finalData = {
1507
+ ...fingerprintData,
1508
+ hash,
1509
+ components: collectedComponents,
1510
+ collectionTime: performance.now() - startTime
1511
+ };
1512
+ const result = {
1513
+ success: collectedComponents.length > 0,
1514
+ collectedComponents,
1515
+ failedComponents
1516
+ };
1517
+ if (collectedComponents.length > 0) {
1518
+ result.data = finalData;
1519
+ }
1520
+ else {
1521
+ result.error = 'No components could be collected';
1522
+ }
1523
+ return result;
1524
+ }
1525
+ /**
1526
+ * Generate visitor ID from fingerprint
1527
+ */
1528
+ function generateVisitorIdFromFingerprint(fingerprintData) {
1529
+ return generateVisitorId(fingerprintData.hash);
1530
+ }
1531
+ /**
1532
+ * Get a lightweight fingerprint (GDPR-compliant)
1533
+ */
1534
+ async function getLightweightFingerprint() {
1535
+ return collectFingerprint({
1536
+ gdprMode: true,
1537
+ includeInvasive: false,
1538
+ excludeComponents: ['canvas', 'webgl', 'audio'],
1539
+ timeout: 2000
1540
+ });
1541
+ }
1542
+ /**
1543
+ * Get a complete fingerprint (enhanced mode)
1544
+ */
1545
+ async function getCompleteFingerprint() {
1546
+ return collectFingerprint({
1547
+ gdprMode: false,
1548
+ includeInvasive: true,
1549
+ timeout: 5000
1550
+ });
1551
+ }
1552
+ /**
1553
+ * Clear session fingerprint cache (memory only)
1554
+ */
1555
+ function clearFingerprintCache() {
1556
+ sessionCache.clear();
1557
+ }
1558
+ /**
1559
+ * Check if fingerprinting is available
1560
+ */
1561
+ function isFingerprintingAvailable() {
1562
+ // At minimum, we need screen and browser fingerprinting
1563
+ return isScreenAvailable() && isBrowserFingerprintingAvailable();
1564
+ }
1565
+ /**
1566
+ * Get available fingerprinting components
1567
+ */
1568
+ function getAvailableComponents() {
1569
+ const available = [];
1570
+ if (isCanvasAvailable$1())
1571
+ available.push('canvas');
1572
+ if (isWebGLAvailable())
1573
+ available.push('webgl');
1574
+ if (isAudioAvailable())
1575
+ available.push('audio');
1576
+ if (isFontDetectionAvailable())
1577
+ available.push('fonts');
1578
+ if (isScreenAvailable())
1579
+ available.push('screen');
1580
+ if (isBrowserFingerprintingAvailable())
1581
+ available.push('browser');
1582
+ return available;
1583
+ }
1584
+
1585
+ /**
1586
+ * RabbitTracker SDK v3.0.0
1587
+ * 100% Cookieless Analytics Tracking
1588
+ *
1589
+ * Main SDK class for client-side tracking integration
1590
+ */
1591
+ /**
1592
+ * Default internal configuration
1593
+ */
1594
+ const DEFAULT_CONFIG = {
1595
+ apiEndpoint: 'https://api.rabbitracker.com',
1596
+ retryEnabled: true,
1597
+ maxRetries: 3,
1598
+ retryDelays: [1000, 2000, 4000],
1599
+ autoTrack: true,
1600
+ dedupeEnabled: true,
1601
+ fingerprintEnabled: true,
1602
+ presenceTracking: false,
1603
+ heartbeatInterval: 30000,
1604
+ scrollTracking: true,
1605
+ scrollDepthTracking: true,
1606
+ gdprCompliant: true,
1607
+ enhancedTracking: 'ask',
1608
+ replaySampling: 0.1,
1609
+ replayMaskInputs: true
1610
+ };
1611
+ /**
1612
+ * Main RabbitTracker SDK Class
1613
+ */
1614
+ class RabbitTrackerSDK {
1615
+ constructor(userConfig) {
1616
+ this.version = '3.0.0';
1617
+ this.isInitialized = false;
1618
+ this.eventQueue = [];
1619
+ /**
1620
+ * Heatmap API
1621
+ */
1622
+ this.heatmap = {
1623
+ enable: () => {
1624
+ this.config.heatmap = true;
1625
+ if (this.config.debug) {
1626
+ console.log('[RabbitTracker] Heatmap enabled');
1627
+ }
1628
+ },
1629
+ disable: () => {
1630
+ this.config.heatmap = false;
1631
+ if (this.config.debug) {
1632
+ console.log('[RabbitTracker] Heatmap disabled');
1633
+ }
1634
+ }
1635
+ };
1636
+ /**
1637
+ * Replay API
1638
+ */
1639
+ this.replay = {
1640
+ enable: () => {
1641
+ this.config.replay = true;
1642
+ if (this.config.debug) {
1643
+ console.log('[RabbitTracker] Replay enabled');
1644
+ }
1645
+ },
1646
+ disable: () => {
1647
+ this.config.replay = false;
1648
+ if (this.config.debug) {
1649
+ console.log('[RabbitTracker] Replay disabled');
1650
+ }
1651
+ },
1652
+ addFunnelStep: (stepData) => {
1653
+ this.sendEvent({
1654
+ eventType: 'funnel_step',
1655
+ eventName: 'funnel_step',
1656
+ customData: stepData
1657
+ });
1658
+ },
1659
+ markConversion: (data) => {
1660
+ this.trackConversion('funnel_conversion', data.value, data.currency, data);
1661
+ }
1662
+ };
1663
+ // Validate required config
1664
+ if (!userConfig.token) {
1665
+ throw new Error('RabbitTracker: token is required');
1666
+ }
1667
+ // Merge user config with defaults
1668
+ this.config = {
1669
+ ...DEFAULT_CONFIG,
1670
+ ...userConfig,
1671
+ gdprMode: userConfig.gdprMode ?? false
1672
+ };
1673
+ // Auto-initialize if in browser
1674
+ if (typeof window !== 'undefined') {
1675
+ this.initialize();
1676
+ }
1677
+ }
1678
+ /**
1679
+ * Initialize SDK
1680
+ */
1681
+ async initialize() {
1682
+ try {
1683
+ if (this.config.debug) {
1684
+ console.log('[RabbitTracker] Initializing SDK v' + this.version);
1685
+ }
1686
+ // Check localhost restriction
1687
+ if (!this.config.allow_localhost && this.isLocalhost()) {
1688
+ if (this.config.debug) {
1689
+ console.log('[RabbitTracker] Skipping tracking on localhost');
1690
+ }
1691
+ return;
1692
+ }
1693
+ // Collect fingerprint
1694
+ await this.collectFingerprint();
1695
+ // Auto-track page view if enabled
1696
+ if (this.config.autoTrack) {
1697
+ this.trackPageView();
1698
+ }
1699
+ this.isInitialized = true;
1700
+ // Process queued events
1701
+ this.processEventQueue();
1702
+ if (this.config.debug) {
1703
+ console.log('[RabbitTracker] SDK initialized successfully', {
1704
+ visitorId: this.visitorId,
1705
+ gdprMode: this.config.gdprMode
1706
+ });
1707
+ }
1708
+ }
1709
+ catch (error) {
1710
+ console.error('[RabbitTracker] Initialization failed:', error);
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Collect fingerprint and generate visitor ID
1715
+ */
1716
+ async collectFingerprint() {
1717
+ try {
1718
+ // Use appropriate fingerprinting based on GDPR mode
1719
+ const result = this.config.gdprMode
1720
+ ? await getLightweightFingerprint()
1721
+ : await getCompleteFingerprint();
1722
+ if (result.success && result.data) {
1723
+ this.fingerprint = result.data;
1724
+ this.visitorId = generateVisitorIdFromFingerprint(result.data);
1725
+ if (this.config.debug) {
1726
+ console.log('[RabbitTracker] Fingerprint collected:', {
1727
+ components: result.collectedComponents,
1728
+ visitorId: this.visitorId,
1729
+ gdprMode: this.config.gdprMode
1730
+ });
1731
+ }
1732
+ }
1733
+ else {
1734
+ throw new Error('Fingerprint collection failed');
1735
+ }
1736
+ }
1737
+ catch (error) {
1738
+ if (this.config.debug) {
1739
+ console.error('[RabbitTracker] Fingerprint collection failed:', error);
1740
+ }
1741
+ // Fallback to basic fingerprint
1742
+ this.visitorId = 'fallback_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
1743
+ }
1744
+ }
1745
+ /**
1746
+ * Check if running on localhost
1747
+ */
1748
+ isLocalhost() {
1749
+ if (typeof window === 'undefined')
1750
+ return false;
1751
+ return window.location.hostname === 'localhost' ||
1752
+ window.location.hostname === '127.0.0.1' ||
1753
+ window.location.hostname.startsWith('192.168.') ||
1754
+ window.location.hostname.startsWith('10.') ||
1755
+ window.location.hostname.includes('local');
1756
+ }
1757
+ /**
1758
+ * Send event to backend
1759
+ */
1760
+ async sendEvent(eventData) {
1761
+ if (!this.isInitialized) {
1762
+ this.eventQueue.push(eventData);
1763
+ return;
1764
+ }
1765
+ try {
1766
+ const payload = {
1767
+ workspaceId: this.config.token,
1768
+ fingerprint: this.fingerprint?.hash || this.visitorId,
1769
+ fingerprintComponents: this.fingerprint?.components || {},
1770
+ ...eventData,
1771
+ timestamp: new Date().toISOString(),
1772
+ url: window.location.href,
1773
+ referrer: document.referrer,
1774
+ pageTitle: document.title,
1775
+ userAgent: navigator.userAgent,
1776
+ screenWidth: window.screen.width,
1777
+ screenHeight: window.screen.height,
1778
+ viewportWidth: window.innerWidth,
1779
+ viewportHeight: window.innerHeight,
1780
+ gdprMode: this.config.gdprMode
1781
+ };
1782
+ const response = await this.makeRequest('/tracking/event', payload);
1783
+ if (response.sessionId) {
1784
+ this.sessionId = response.sessionId;
1785
+ }
1786
+ return response;
1787
+ }
1788
+ catch (error) {
1789
+ if (this.config.debug) {
1790
+ console.error('[RabbitTracker] Event sending failed:', error);
1791
+ }
1792
+ // Retry logic
1793
+ if (this.config.retryEnabled) {
1794
+ // Queue for retry (simplified)
1795
+ setTimeout(() => this.sendEvent(eventData), 5000);
1796
+ }
1797
+ }
1798
+ }
1799
+ /**
1800
+ * Make HTTP request to backend
1801
+ */
1802
+ async makeRequest(endpoint, data) {
1803
+ const url = `${this.config.apiEndpoint}${endpoint}`;
1804
+ const response = await fetch(url, {
1805
+ method: 'POST',
1806
+ headers: {
1807
+ 'Content-Type': 'application/json',
1808
+ },
1809
+ body: JSON.stringify(data)
1810
+ });
1811
+ if (!response.ok) {
1812
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1813
+ }
1814
+ return response.json();
1815
+ }
1816
+ /**
1817
+ * Process queued events
1818
+ */
1819
+ processEventQueue() {
1820
+ while (this.eventQueue.length > 0) {
1821
+ const event = this.eventQueue.shift();
1822
+ this.sendEvent(event);
1823
+ }
1824
+ }
1825
+ // Public API Methods
1826
+ /**
1827
+ * Track generic event
1828
+ */
1829
+ track(eventType, eventData) {
1830
+ this.sendEvent({
1831
+ eventType,
1832
+ eventName: eventType,
1833
+ customData: eventData
1834
+ });
1835
+ }
1836
+ /**
1837
+ * Track custom event
1838
+ */
1839
+ trackCustomEvent(eventName, metadata) {
1840
+ this.sendEvent({
1841
+ eventType: 'custom',
1842
+ eventName,
1843
+ customData: metadata
1844
+ });
1845
+ return true;
1846
+ }
1847
+ /**
1848
+ * Track conversion event
1849
+ */
1850
+ trackConversion(eventName, value, currency, metadata) {
1851
+ this.sendEvent({
1852
+ eventType: 'conversion',
1853
+ eventName,
1854
+ revenue: value,
1855
+ currency: currency || 'USD',
1856
+ customData: metadata
1857
+ });
1858
+ return true;
1859
+ }
1860
+ /**
1861
+ * Track page view
1862
+ */
1863
+ trackPageView(data) {
1864
+ this.sendEvent({
1865
+ eventType: 'page_view',
1866
+ eventName: 'page_view',
1867
+ ...data
1868
+ });
1869
+ }
1870
+ /**
1871
+ * Track purchase
1872
+ */
1873
+ trackPurchase(data) {
1874
+ this.sendEvent({
1875
+ eventType: 'purchase',
1876
+ eventName: 'purchase',
1877
+ revenue: data.value || data.revenue,
1878
+ currency: data.currency || 'USD',
1879
+ quantity: data.quantity || 1,
1880
+ productId: data.productId,
1881
+ customData: {
1882
+ productName: data.productName,
1883
+ orderId: data.orderId,
1884
+ ...data.customData
1885
+ }
1886
+ });
1887
+ }
1888
+ /**
1889
+ * Track add to cart
1890
+ */
1891
+ trackAddToCart(data) {
1892
+ this.sendEvent({
1893
+ eventType: 'add_to_cart',
1894
+ eventName: 'add_to_cart',
1895
+ revenue: data?.price,
1896
+ quantity: data?.quantity || 1,
1897
+ productId: data?.productId,
1898
+ customData: {
1899
+ productName: data?.productName,
1900
+ ...data?.customData
1901
+ }
1902
+ });
1903
+ }
1904
+ /**
1905
+ * Track view content
1906
+ */
1907
+ trackViewContent(data) {
1908
+ this.sendEvent({
1909
+ eventType: 'view_content',
1910
+ eventName: 'view_content',
1911
+ productId: data?.productId,
1912
+ customData: {
1913
+ productName: data?.productName,
1914
+ category: data?.category,
1915
+ ...data?.customData
1916
+ }
1917
+ });
1918
+ }
1919
+ /**
1920
+ * Track initiate checkout
1921
+ */
1922
+ trackInitiateCheckout(data) {
1923
+ this.sendEvent({
1924
+ eventType: 'initiate_checkout',
1925
+ eventName: 'initiate_checkout',
1926
+ revenue: data?.value,
1927
+ currency: data?.currency || 'USD',
1928
+ quantity: data?.numItems,
1929
+ customData: data?.customData
1930
+ });
1931
+ }
1932
+ /**
1933
+ * Track lead
1934
+ */
1935
+ trackLead(data) {
1936
+ this.sendEvent({
1937
+ eventType: 'lead',
1938
+ eventName: 'lead',
1939
+ customData: data
1940
+ });
1941
+ }
1942
+ /**
1943
+ * Identify user
1944
+ */
1945
+ identify(userData) {
1946
+ this.sendEvent({
1947
+ eventType: 'identify',
1948
+ eventName: 'identify',
1949
+ email: userData.email,
1950
+ phone: userData.phone,
1951
+ customData: userData
1952
+ });
1953
+ }
1954
+ /**
1955
+ * Track SPA navigation
1956
+ */
1957
+ trackSPANavigation(url, title) {
1958
+ this.trackPageView({
1959
+ url: url || window.location.href,
1960
+ pageTitle: title || document.title
1961
+ });
1962
+ }
1963
+ /**
1964
+ * Reset scroll tracking
1965
+ */
1966
+ resetScrollTracking() {
1967
+ // Implementation for scroll tracking reset
1968
+ if (this.config.debug) {
1969
+ console.log('[RabbitTracker] Scroll tracking reset');
1970
+ }
1971
+ }
1972
+ /**
1973
+ * Start presence tracking
1974
+ */
1975
+ startPresenceTracking() {
1976
+ this.config.presenceTracking = true;
1977
+ if (this.config.debug) {
1978
+ console.log('[RabbitTracker] Presence tracking started');
1979
+ }
1980
+ }
1981
+ /**
1982
+ * Stop presence tracking
1983
+ */
1984
+ stopPresenceTracking() {
1985
+ this.config.presenceTracking = false;
1986
+ if (this.config.debug) {
1987
+ console.log('[RabbitTracker] Presence tracking stopped');
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Enable enhanced tracking
1992
+ */
1993
+ enableEnhancedTracking() {
1994
+ this.config.enhancedTracking = 'true';
1995
+ this.config.gdprMode = false;
1996
+ // Re-collect fingerprint with enhanced mode
1997
+ this.collectFingerprint();
1998
+ if (this.config.debug) {
1999
+ console.log('[RabbitTracker] Enhanced tracking enabled');
2000
+ }
2001
+ return true;
2002
+ }
2003
+ /**
2004
+ * Disable enhanced tracking
2005
+ */
2006
+ disableEnhancedTracking() {
2007
+ this.config.enhancedTracking = 'false';
2008
+ this.config.gdprMode = true;
2009
+ if (this.config.debug) {
2010
+ console.log('[RabbitTracker] Enhanced tracking disabled');
2011
+ }
2012
+ }
2013
+ /**
2014
+ * Check if enhanced mode is active
2015
+ */
2016
+ isEnhancedMode() {
2017
+ return this.config.enhancedTracking === 'true' && !this.config.gdprMode;
2018
+ }
2019
+ /**
2020
+ * Get user data
2021
+ */
2022
+ getUserData() {
2023
+ return {
2024
+ sessionId: this.sessionId || '',
2025
+ location: {}, // Will be populated by backend
2026
+ device: {
2027
+ userAgent: navigator.userAgent,
2028
+ language: navigator.language,
2029
+ screen: {
2030
+ width: window.screen.width,
2031
+ height: window.screen.height
2032
+ }
2033
+ },
2034
+ journey: [], // Will be populated by backend
2035
+ fingerprint: this.fingerprint?.hash
2036
+ };
2037
+ }
2038
+ /**
2039
+ * Get conversion likelihood (mock implementation)
2040
+ */
2041
+ getConversionLikelihood() {
2042
+ return {
2043
+ score: 0.5,
2044
+ factors: ['Requires backend analysis']
2045
+ };
2046
+ }
2047
+ /**
2048
+ * Get user segment (mock implementation)
2049
+ */
2050
+ getUserSegment() {
2051
+ return {
2052
+ type: 'medium_intent',
2053
+ confidence: 0.5
2054
+ };
2055
+ }
2056
+ }
2057
+
2058
+ /**
2059
+ * Bot Detection Module
2060
+ * Comprehensive client-side bot detection based on multiple techniques
2061
+ * Detects headless browsers, automation tools, and suspicious behavior
2062
+ */
2063
+ /**
2064
+ * Known bot user agent patterns
2065
+ */
2066
+ const BOT_PATTERNS = [
2067
+ // Headless browsers
2068
+ /HeadlessChrome/i,
2069
+ /PhantomJS/i,
2070
+ /SlimerJS/i,
2071
+ // Automation tools
2072
+ /Selenium/i,
2073
+ /WebDriver/i,
2074
+ /ChromeDriver/i,
2075
+ /GeckoDriver/i,
2076
+ // Crawlers and bots
2077
+ /bot/i,
2078
+ /crawler/i,
2079
+ /spider/i,
2080
+ /scraper/i,
2081
+ // Specific bots
2082
+ /Googlebot/i,
2083
+ /Bingbot/i,
2084
+ /Slurp/i,
2085
+ /DuckDuckBot/i,
2086
+ /Baiduspider/i,
2087
+ /YandexBot/i,
2088
+ /facebookexternalhit/i,
2089
+ /Twitterbot/i,
2090
+ /LinkedInBot/i,
2091
+ /WhatsApp/i,
2092
+ /SkypeUriPreview/i
2093
+ ];
2094
+ /**
2095
+ * Suspicious user agent patterns
2096
+ */
2097
+ const SUSPICIOUS_PATTERNS = [
2098
+ /^Mozilla\/5\.0$/,
2099
+ /Headless/i,
2100
+ /automated/i,
2101
+ /testing/i,
2102
+ /^$/
2103
+ ];
2104
+ /**
2105
+ * Detect WebDriver presence
2106
+ */
2107
+ function detectWebDriver() {
2108
+ try {
2109
+ // Check for webdriver property
2110
+ if (navigator.webdriver === true) {
2111
+ return {
2112
+ detected: true,
2113
+ confidence: 95,
2114
+ method: 'navigator.webdriver_true'
2115
+ };
2116
+ }
2117
+ // Check for webdriver in window
2118
+ if (window.webdriver === true) {
2119
+ return {
2120
+ detected: true,
2121
+ confidence: 90,
2122
+ method: 'window.webdriver_true'
2123
+ };
2124
+ }
2125
+ // Check for selenium-specific properties
2126
+ const seleniumKeys = [
2127
+ '_selenium',
2128
+ 'callSelenium',
2129
+ '_Selenium_IDE_Recorder',
2130
+ 'callPhantom',
2131
+ '_phantom',
2132
+ '__phantomas',
2133
+ '__fxdriver_evaluate',
2134
+ '__fxdriver_unwrapped',
2135
+ '_fxdriver_evaluate',
2136
+ '_fxdriver_unwrapped'
2137
+ ];
2138
+ for (const key of seleniumKeys) {
2139
+ if (key in window || key in document) {
2140
+ return {
2141
+ detected: true,
2142
+ confidence: 85,
2143
+ method: `selenium_property_${key}`
2144
+ };
2145
+ }
2146
+ }
2147
+ // Check for webdriver in document
2148
+ if (document.documentElement.getAttribute('webdriver')) {
2149
+ return {
2150
+ detected: true,
2151
+ confidence: 80,
2152
+ method: 'document_webdriver_attribute'
2153
+ };
2154
+ }
2155
+ return {
2156
+ detected: false,
2157
+ confidence: 0,
2158
+ method: 'webdriver_not_detected'
2159
+ };
2160
+ }
2161
+ catch (error) {
2162
+ return {
2163
+ detected: false,
2164
+ confidence: 0,
2165
+ method: 'webdriver_error',
2166
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2167
+ };
2168
+ }
2169
+ }
2170
+ /**
2171
+ * Detect headless browser characteristics
2172
+ */
2173
+ function detectHeadlessBrowser() {
2174
+ try {
2175
+ let suspiciousCount = 0;
2176
+ const issues = [];
2177
+ // Check for missing window properties
2178
+ if (!window.outerHeight || !window.outerWidth) {
2179
+ suspiciousCount++;
2180
+ issues.push('missing_outer_dimensions');
2181
+ }
2182
+ // Check for plugins
2183
+ if (navigator.plugins.length === 0) {
2184
+ suspiciousCount++;
2185
+ issues.push('no_plugins');
2186
+ }
2187
+ // Check for languages
2188
+ if (!navigator.languages || navigator.languages.length === 0) {
2189
+ suspiciousCount++;
2190
+ issues.push('no_languages');
2191
+ }
2192
+ // Check for inconsistent user agent
2193
+ const userAgent = navigator.userAgent;
2194
+ if (!userAgent || userAgent.length < 50) {
2195
+ suspiciousCount++;
2196
+ issues.push('short_user_agent');
2197
+ }
2198
+ // Check for notification permission (often undefined in headless)
2199
+ if (typeof Notification?.permission === 'undefined') {
2200
+ suspiciousCount++;
2201
+ issues.push('no_notification_permission');
2202
+ }
2203
+ // Check for missing performance API
2204
+ if (!window.performance || !window.performance.timing) {
2205
+ suspiciousCount++;
2206
+ issues.push('missing_performance_api');
2207
+ }
2208
+ // Check Chrome-specific headless indicators
2209
+ if (userAgent.includes('Chrome')) {
2210
+ // Check for missing chrome object
2211
+ if (!window.chrome) {
2212
+ suspiciousCount++;
2213
+ issues.push('missing_chrome_object');
2214
+ }
2215
+ // Check for headless in user agent
2216
+ if (userAgent.includes('HeadlessChrome')) {
2217
+ return {
2218
+ detected: true,
2219
+ confidence: 95,
2220
+ method: 'chrome_headless_user_agent'
2221
+ };
2222
+ }
2223
+ }
2224
+ const confidence = Math.min(suspiciousCount * 15, 90);
2225
+ return {
2226
+ detected: suspiciousCount >= 3,
2227
+ confidence,
2228
+ method: 'headless_characteristics',
2229
+ details: { issues, suspiciousCount }
2230
+ };
2231
+ }
2232
+ catch (error) {
2233
+ return {
2234
+ detected: false,
2235
+ confidence: 0,
2236
+ method: 'headless_detection_error',
2237
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2238
+ };
2239
+ }
2240
+ }
2241
+ /**
2242
+ * Detect automation tools
2243
+ */
2244
+ function detectAutomationTools() {
2245
+ try {
2246
+ const automationIndicators = [
2247
+ 'webdriver',
2248
+ 'selenium',
2249
+ 'phantomjs',
2250
+ 'slimerjs',
2251
+ 'chromedriver',
2252
+ 'geckodriver',
2253
+ 'automation',
2254
+ 'puppeteer'
2255
+ ];
2256
+ const detectedTools = [];
2257
+ // Check window properties
2258
+ for (const tool of automationIndicators) {
2259
+ if (window[tool] || window[`_${tool}`] || window[`__${tool}`]) {
2260
+ detectedTools.push(tool);
2261
+ }
2262
+ }
2263
+ // Check document properties
2264
+ for (const tool of automationIndicators) {
2265
+ if (document[tool] || document[`_${tool}`] || document[`__${tool}`]) {
2266
+ detectedTools.push(tool);
2267
+ }
2268
+ }
2269
+ // Check for automation-specific errors
2270
+ try {
2271
+ // Use safer method to test navigation object access
2272
+ const nav = window.navigator;
2273
+ if (!nav) {
2274
+ detectedTools.push('navigation_missing');
2275
+ }
2276
+ }
2277
+ catch (error) {
2278
+ const errorMessage = error instanceof Error ? error.message : '';
2279
+ if (errorMessage.includes('automation') || errorMessage.includes('webdriver')) {
2280
+ detectedTools.push('navigation_automation_error');
2281
+ }
2282
+ }
2283
+ if (detectedTools.length > 0) {
2284
+ return {
2285
+ detected: true,
2286
+ confidence: Math.min(detectedTools.length * 30, 95),
2287
+ method: 'automation_tools_detected',
2288
+ details: { tools: detectedTools }
2289
+ };
2290
+ }
2291
+ return {
2292
+ detected: false,
2293
+ confidence: 0,
2294
+ method: 'no_automation_tools'
2295
+ };
2296
+ }
2297
+ catch (error) {
2298
+ return {
2299
+ detected: false,
2300
+ confidence: 0,
2301
+ method: 'automation_detection_error',
2302
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2303
+ };
2304
+ }
2305
+ }
2306
+ /**
2307
+ * Detect DOM blockers (ad blockers and privacy tools)
2308
+ */
2309
+ function detectDOMBlockers() {
2310
+ try {
2311
+ const blockerSelectors = [
2312
+ // Ad blocker elements
2313
+ '#ad-banner',
2314
+ '.advertisement',
2315
+ '.google-ads',
2316
+ '.adsense',
2317
+ // Analytics blockers
2318
+ '#google-analytics',
2319
+ '.analytics',
2320
+ '[src*="google-analytics"]',
2321
+ '[src*="googletagmanager"]',
2322
+ // Social media blockers
2323
+ '.facebook-widget',
2324
+ '.twitter-widget',
2325
+ '.social-share'
2326
+ ];
2327
+ let blockedCount = 0;
2328
+ for (const selector of blockerSelectors) {
2329
+ const element = document.querySelector(selector);
2330
+ if (!element) {
2331
+ blockedCount++;
2332
+ }
2333
+ }
2334
+ // Test if we can create tracking pixels
2335
+ try {
2336
+ const pixel = document.createElement('img');
2337
+ pixel.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
2338
+ pixel.style.display = 'none';
2339
+ document.body.appendChild(pixel);
2340
+ setTimeout(() => {
2341
+ if (pixel.parentNode) {
2342
+ pixel.parentNode.removeChild(pixel);
2343
+ }
2344
+ }, 100);
2345
+ }
2346
+ catch {
2347
+ blockedCount++;
2348
+ }
2349
+ const confidence = Math.min(blockedCount * 20, 80);
2350
+ return {
2351
+ detected: blockedCount >= 2,
2352
+ confidence,
2353
+ method: 'dom_blockers',
2354
+ details: { blockedCount, totalTests: blockerSelectors.length + 1 }
2355
+ };
2356
+ }
2357
+ catch (error) {
2358
+ return {
2359
+ detected: false,
2360
+ confidence: 0,
2361
+ method: 'dom_blocker_error',
2362
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2363
+ };
2364
+ }
2365
+ }
2366
+ /**
2367
+ * Check for missing languages (common in bots)
2368
+ */
2369
+ function detectMissingLanguages() {
2370
+ try {
2371
+ if (!navigator.languages || navigator.languages.length === 0) {
2372
+ return {
2373
+ detected: true,
2374
+ confidence: 70,
2375
+ method: 'no_languages'
2376
+ };
2377
+ }
2378
+ if (navigator.languages.length === 1 && navigator.languages[0] === 'en-US') {
2379
+ return {
2380
+ detected: true,
2381
+ confidence: 40,
2382
+ method: 'single_default_language'
2383
+ };
2384
+ }
2385
+ // Check for inconsistent language settings
2386
+ if (navigator.language !== navigator.languages[0]) {
2387
+ return {
2388
+ detected: true,
2389
+ confidence: 30,
2390
+ method: 'inconsistent_languages',
2391
+ details: {
2392
+ language: navigator.language,
2393
+ firstLanguage: navigator.languages[0]
2394
+ }
2395
+ };
2396
+ }
2397
+ return {
2398
+ detected: false,
2399
+ confidence: 0,
2400
+ method: 'languages_normal',
2401
+ details: { languageCount: navigator.languages.length }
2402
+ };
2403
+ }
2404
+ catch (error) {
2405
+ return {
2406
+ detected: true,
2407
+ confidence: 50,
2408
+ method: 'language_detection_error',
2409
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2410
+ };
2411
+ }
2412
+ }
2413
+ /**
2414
+ * Test for inconsistent eval behavior
2415
+ */
2416
+ function detectInconsistentEval() {
2417
+ try {
2418
+ // Test if basic math computation works normally
2419
+ const testFunction = new Function('return 2 + 2');
2420
+ const result = testFunction();
2421
+ if (result !== 4) {
2422
+ return {
2423
+ detected: true,
2424
+ confidence: 90,
2425
+ method: 'function_constructor_incorrect_result',
2426
+ details: { expected: 4, actual: result }
2427
+ };
2428
+ }
2429
+ // Test if Function constructor toString() is modified
2430
+ const functionString = Function.toString();
2431
+ if (!functionString.includes('[native code]') && !functionString.includes('function Function()')) {
2432
+ return {
2433
+ detected: true,
2434
+ confidence: 70,
2435
+ method: 'function_constructor_modified_toString',
2436
+ details: { functionString: functionString.substring(0, 100) }
2437
+ };
2438
+ }
2439
+ return {
2440
+ detected: false,
2441
+ confidence: 0,
2442
+ method: 'eval_normal'
2443
+ };
2444
+ }
2445
+ catch (error) {
2446
+ return {
2447
+ detected: true,
2448
+ confidence: 60,
2449
+ method: 'eval_error',
2450
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2451
+ };
2452
+ }
2453
+ }
2454
+ /**
2455
+ * Analyze user agent for bot patterns
2456
+ */
2457
+ function analyzeUserAgent$1() {
2458
+ const userAgent = navigator.userAgent;
2459
+ // Check for bot patterns
2460
+ const botPatterns = BOT_PATTERNS.filter(pattern => pattern.test(userAgent));
2461
+ const suspiciousPatterns = SUSPICIOUS_PATTERNS.filter(pattern => pattern.test(userAgent));
2462
+ let category = 'legitimate';
2463
+ if (botPatterns.some(p => p.source.includes('bot|crawler|spider'))) {
2464
+ category = 'crawler';
2465
+ }
2466
+ else if (botPatterns.some(p => p.source.includes('Selenium|WebDriver'))) {
2467
+ category = 'automation';
2468
+ }
2469
+ else if (botPatterns.some(p => p.source.includes('Headless|Phantom'))) {
2470
+ category = 'headless';
2471
+ }
2472
+ const allPatterns = [...botPatterns.map(p => p.source), ...suspiciousPatterns.map(p => p.source)];
2473
+ return {
2474
+ browser: {
2475
+ name: 'Unknown',
2476
+ version: 'Unknown',
2477
+ major: 'Unknown',
2478
+ engine: 'unknown'
2479
+ },
2480
+ os: {
2481
+ name: 'Unknown',
2482
+ version: 'Unknown',
2483
+ family: 'unknown'
2484
+ },
2485
+ device: {
2486
+ type: 'desktop'
2487
+ },
2488
+ suspicious: {
2489
+ isBot: botPatterns.length > 0 || suspiciousPatterns.length > 0,
2490
+ patterns: allPatterns,
2491
+ category: botPatterns.length > 0 ? category : 'legitimate'
2492
+ },
2493
+ isMobile: false,
2494
+ isTablet: false,
2495
+ isDesktop: true,
2496
+ isBot: botPatterns.length > 0 || suspiciousPatterns.length > 0,
2497
+ raw: userAgent
2498
+ };
2499
+ }
2500
+ /**
2501
+ * Main bot detection function
2502
+ */
2503
+ async function detectBot() {
2504
+ // Run all detection methods
2505
+ const detectors = {
2506
+ webDriver: detectWebDriver(),
2507
+ headlessBrowser: detectHeadlessBrowser(),
2508
+ automationTools: detectAutomationTools(),
2509
+ domBlockers: detectDOMBlockers(),
2510
+ missingLanguages: detectMissingLanguages(),
2511
+ inconsistentEval: detectInconsistentEval(),
2512
+ // Behavioral detection (placeholder - would need more time to implement)
2513
+ humanBehavior: { detected: false, confidence: 0, method: 'not_implemented' },
2514
+ mouseMovement: { detected: false, confidence: 0, method: 'not_implemented' },
2515
+ clickPatterns: { detected: false, confidence: 0, method: 'not_implemented' },
2516
+ // Canvas/WebGL consistency (placeholder - would integrate with fingerprinting)
2517
+ canvasInconsistency: { detected: false, confidence: 0, method: 'not_implemented' },
2518
+ webglAnomaly: { detected: false, confidence: 0, method: 'not_implemented' },
2519
+ // Plugin detection
2520
+ missingPlugins: { detected: navigator.plugins.length === 0, confidence: 30, method: 'plugin_count' },
2521
+ inconsistentProperties: { detected: false, confidence: 0, method: 'not_implemented' },
2522
+ // Modified built-ins detection
2523
+ modifiedBuiltins: { detected: false, confidence: 0, method: 'not_implemented' }
2524
+ };
2525
+ // Analyze user agent
2526
+ const userAgent = analyzeUserAgent$1();
2527
+ // Calculate overall bot detection
2528
+ const positiveDetections = Object.values(detectors).filter(d => d.detected);
2529
+ const totalConfidence = positiveDetections.reduce((sum, d) => sum + d.confidence, 0);
2530
+ const averageConfidence = positiveDetections.length > 0 ? totalConfidence / positiveDetections.length : 0;
2531
+ // Determine if bot is detected
2532
+ const isBot = positiveDetections.length >= 2 || // Multiple detections
2533
+ positiveDetections.some(d => d.confidence >= 90) || // High confidence detection
2534
+ (userAgent.suspicious?.isBot ?? false); // User agent indicates bot
2535
+ // Determine risk level
2536
+ let riskLevel = 'low';
2537
+ if (averageConfidence >= 70) {
2538
+ riskLevel = 'high';
2539
+ }
2540
+ else if (averageConfidence >= 40 || positiveDetections.length >= 2) {
2541
+ riskLevel = 'medium';
2542
+ }
2543
+ return {
2544
+ isBot,
2545
+ confidence: Math.round(Math.min(averageConfidence, 95)),
2546
+ riskLevel,
2547
+ detectionMethods: Object.keys(detectors).filter(key => detectors[key]?.detected),
2548
+ detectors,
2549
+ userAgent
2550
+ };
2551
+ }
2552
+ /**
2553
+ * Quick bot detection (faster, less comprehensive)
2554
+ */
2555
+ async function quickBotDetection() {
2556
+ // Check only the most reliable indicators
2557
+ const webDriver = detectWebDriver();
2558
+ const userAgent = analyzeUserAgent$1();
2559
+ return webDriver.detected || (userAgent.suspicious?.isBot ?? false);
2560
+ }
2561
+
2562
+ /**
2563
+ * Incognito Mode Detection (Analytics Only)
2564
+ *
2565
+ * Detects private/incognito browsing mode for analytics purposes only.
2566
+ * Does NOT use localStorage/sessionStorage for tracking or data persistence.
2567
+ * Used only to enrich analytics data with browser context information.
2568
+ */
2569
+ /**
2570
+ * Test localStorage availability and behavior (ANALYTICS ONLY)
2571
+ *
2572
+ * NOTE: This function uses localStorage purely for DETECTION purposes.
2573
+ * It does NOT store any tracking data or persistent information.
2574
+ * Used only to analyze browser capabilities for analytics context.
2575
+ */
2576
+ async function testLocalStorage() {
2577
+ try {
2578
+ const testKey = '__incognito_test_' + Date.now();
2579
+ const testValue = 'test';
2580
+ // Try to set and get localStorage item (for detection only, immediately cleaned up)
2581
+ localStorage.setItem(testKey, testValue);
2582
+ const retrieved = localStorage.getItem(testKey);
2583
+ localStorage.removeItem(testKey); // Immediately cleanup test data
2584
+ // In some incognito modes, localStorage works but has limited capacity
2585
+ if (retrieved !== testValue) {
2586
+ return {
2587
+ detected: true,
2588
+ confidence: 80,
2589
+ method: 'localStorage_inconsistency',
2590
+ details: { retrieved, expected: testValue }
2591
+ };
2592
+ }
2593
+ // Test storage capacity (incognito often has reduced limits) - DETECTION ONLY
2594
+ try {
2595
+ const largData = 'x'.repeat(1024 * 1024); // 1MB
2596
+ localStorage.setItem('__capacity_test', largData); // Test only, immediately removed
2597
+ localStorage.removeItem('__capacity_test'); // Immediate cleanup
2598
+ return {
2599
+ detected: false,
2600
+ confidence: 30,
2601
+ method: 'localStorage_capacity'
2602
+ };
2603
+ }
2604
+ catch (capacityError) {
2605
+ // Limited capacity might indicate incognito
2606
+ return {
2607
+ detected: true,
2608
+ confidence: 60,
2609
+ method: 'localStorage_capacity_limited',
2610
+ details: { error: capacityError instanceof Error ? capacityError.message : 'Unknown' }
2611
+ };
2612
+ }
2613
+ }
2614
+ catch (error) {
2615
+ // localStorage completely unavailable
2616
+ return {
2617
+ detected: true,
2618
+ confidence: 90,
2619
+ method: 'localStorage_unavailable',
2620
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2621
+ };
2622
+ }
2623
+ }
2624
+ /**
2625
+ * Test sessionStorage availability (ANALYTICS ONLY)
2626
+ *
2627
+ * NOTE: Uses sessionStorage purely for browser capability detection.
2628
+ * No tracking data is stored - all test data is immediately removed.
2629
+ */
2630
+ async function testSessionStorage() {
2631
+ try {
2632
+ const testKey = '__session_test_' + Date.now();
2633
+ const testValue = 'test';
2634
+ sessionStorage.setItem(testKey, testValue); // Test only, immediately cleaned
2635
+ const retrieved = sessionStorage.getItem(testKey);
2636
+ sessionStorage.removeItem(testKey); // Immediate cleanup
2637
+ if (retrieved !== testValue) {
2638
+ return {
2639
+ detected: true,
2640
+ confidence: 75,
2641
+ method: 'sessionStorage_inconsistency',
2642
+ details: { retrieved, expected: testValue }
2643
+ };
2644
+ }
2645
+ return {
2646
+ detected: false,
2647
+ confidence: 20,
2648
+ method: 'sessionStorage_working'
2649
+ };
2650
+ }
2651
+ catch (error) {
2652
+ return {
2653
+ detected: true,
2654
+ confidence: 85,
2655
+ method: 'sessionStorage_unavailable',
2656
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2657
+ };
2658
+ }
2659
+ }
2660
+ /**
2661
+ * Test IndexedDB availability and behavior
2662
+ */
2663
+ async function testIndexedDB() {
2664
+ try {
2665
+ if (!window.indexedDB) {
2666
+ return {
2667
+ detected: true,
2668
+ confidence: 70,
2669
+ method: 'indexedDB_unavailable'
2670
+ };
2671
+ }
2672
+ return new Promise((resolve) => {
2673
+ const dbName = '__incognito_test_' + Date.now();
2674
+ const request = indexedDB.open(dbName, 1);
2675
+ const timeout = setTimeout(() => {
2676
+ resolve({
2677
+ detected: true,
2678
+ confidence: 60,
2679
+ method: 'indexedDB_timeout'
2680
+ });
2681
+ }, 1000);
2682
+ request.onerror = () => {
2683
+ clearTimeout(timeout);
2684
+ resolve({
2685
+ detected: true,
2686
+ confidence: 80,
2687
+ method: 'indexedDB_error',
2688
+ details: { error: request.error?.message }
2689
+ });
2690
+ };
2691
+ request.onsuccess = () => {
2692
+ clearTimeout(timeout);
2693
+ const db = request.result;
2694
+ // Clean up
2695
+ db.close();
2696
+ indexedDB.deleteDatabase(dbName);
2697
+ resolve({
2698
+ detected: false,
2699
+ confidence: 25,
2700
+ method: 'indexedDB_working'
2701
+ });
2702
+ };
2703
+ request.onblocked = () => {
2704
+ clearTimeout(timeout);
2705
+ resolve({
2706
+ detected: true,
2707
+ confidence: 70,
2708
+ method: 'indexedDB_blocked'
2709
+ });
2710
+ };
2711
+ });
2712
+ }
2713
+ catch (error) {
2714
+ return {
2715
+ detected: true,
2716
+ confidence: 85,
2717
+ method: 'indexedDB_exception',
2718
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2719
+ };
2720
+ }
2721
+ }
2722
+ /**
2723
+ * Test WebSQL availability (legacy browsers)
2724
+ */
2725
+ async function testOpenDatabase() {
2726
+ try {
2727
+ const openDatabase = window.openDatabase;
2728
+ if (!openDatabase) {
2729
+ return {
2730
+ detected: false,
2731
+ confidence: 10,
2732
+ method: 'openDatabase_unsupported'
2733
+ };
2734
+ }
2735
+ const db = openDatabase('__test', '1.0', 'Test', 1024);
2736
+ if (!db) {
2737
+ return {
2738
+ detected: true,
2739
+ confidence: 60,
2740
+ method: 'openDatabase_failed'
2741
+ };
2742
+ }
2743
+ return {
2744
+ detected: false,
2745
+ confidence: 20,
2746
+ method: 'openDatabase_working'
2747
+ };
2748
+ }
2749
+ catch (error) {
2750
+ return {
2751
+ detected: true,
2752
+ confidence: 70,
2753
+ method: 'openDatabase_error',
2754
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2755
+ };
2756
+ }
2757
+ }
2758
+ /**
2759
+ * Test permissions API behavior
2760
+ */
2761
+ async function testPermissions() {
2762
+ try {
2763
+ if (!navigator.permissions) {
2764
+ return {
2765
+ detected: false,
2766
+ confidence: 10,
2767
+ method: 'permissions_unsupported'
2768
+ };
2769
+ }
2770
+ // Test notification permission (often different in incognito)
2771
+ const result = await navigator.permissions.query({ name: 'notifications' });
2772
+ // In some browsers, incognito mode always returns "denied" for notifications
2773
+ if (result.state === 'denied') {
2774
+ return {
2775
+ detected: true,
2776
+ confidence: 40,
2777
+ method: 'permissions_notifications_denied',
2778
+ details: { state: result.state }
2779
+ };
2780
+ }
2781
+ return {
2782
+ detected: false,
2783
+ confidence: 30,
2784
+ method: 'permissions_working',
2785
+ details: { state: result.state }
2786
+ };
2787
+ }
2788
+ catch (error) {
2789
+ return {
2790
+ detected: true,
2791
+ confidence: 50,
2792
+ method: 'permissions_error',
2793
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2794
+ };
2795
+ }
2796
+ }
2797
+ /**
2798
+ * Test quota management API
2799
+ */
2800
+ async function testQuotaManagement() {
2801
+ try {
2802
+ if (!navigator.storage || !navigator.storage.estimate) {
2803
+ return {
2804
+ detected: false,
2805
+ confidence: 10,
2806
+ method: 'quota_unsupported'
2807
+ };
2808
+ }
2809
+ const estimate = await navigator.storage.estimate();
2810
+ // Incognito mode often has very limited quota
2811
+ if (estimate.quota && estimate.quota < 10 * 1024 * 1024) { // Less than 10MB
2812
+ return {
2813
+ detected: true,
2814
+ confidence: 70,
2815
+ method: 'quota_limited',
2816
+ details: { quota: estimate.quota, usage: estimate.usage }
2817
+ };
2818
+ }
2819
+ return {
2820
+ detected: false,
2821
+ confidence: 40,
2822
+ method: 'quota_normal',
2823
+ details: { quota: estimate.quota, usage: estimate.usage }
2824
+ };
2825
+ }
2826
+ catch (error) {
2827
+ return {
2828
+ detected: true,
2829
+ confidence: 60,
2830
+ method: 'quota_error',
2831
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2832
+ };
2833
+ }
2834
+ }
2835
+ /**
2836
+ * Test filesystem API availability
2837
+ */
2838
+ async function testFilesystem() {
2839
+ try {
2840
+ const requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
2841
+ if (!requestFileSystem) {
2842
+ return {
2843
+ detected: false,
2844
+ confidence: 10,
2845
+ method: 'filesystem_unsupported'
2846
+ };
2847
+ }
2848
+ return new Promise((resolve) => {
2849
+ requestFileSystem(window.TEMPORARY || 0, 1024, () => {
2850
+ resolve({
2851
+ detected: false,
2852
+ confidence: 30,
2853
+ method: 'filesystem_working'
2854
+ });
2855
+ }, (error) => {
2856
+ resolve({
2857
+ detected: true,
2858
+ confidence: 60,
2859
+ method: 'filesystem_error',
2860
+ details: { error: error.name || 'Unknown' }
2861
+ });
2862
+ });
2863
+ // Timeout
2864
+ setTimeout(() => {
2865
+ resolve({
2866
+ detected: true,
2867
+ confidence: 50,
2868
+ method: 'filesystem_timeout'
2869
+ });
2870
+ }, 1000);
2871
+ });
2872
+ }
2873
+ catch (error) {
2874
+ return {
2875
+ detected: true,
2876
+ confidence: 70,
2877
+ method: 'filesystem_exception',
2878
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2879
+ };
2880
+ }
2881
+ }
2882
+ /**
2883
+ * Test cookies enabled state
2884
+ */
2885
+ async function testCookiesEnabled() {
2886
+ try {
2887
+ const cookiesEnabled = navigator.cookieEnabled;
2888
+ // Test by setting a cookie
2889
+ document.cookie = '__incognito_test=test; path=/';
2890
+ const cookieSet = document.cookie.includes('__incognito_test=test');
2891
+ // Clean up
2892
+ document.cookie = '__incognito_test=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
2893
+ if (!cookiesEnabled || !cookieSet) {
2894
+ return {
2895
+ detected: true,
2896
+ confidence: 50,
2897
+ method: 'cookies_disabled',
2898
+ details: { cookiesEnabled, cookieSet }
2899
+ };
2900
+ }
2901
+ return {
2902
+ detected: false,
2903
+ confidence: 20,
2904
+ method: 'cookies_working',
2905
+ details: { cookiesEnabled, cookieSet }
2906
+ };
2907
+ }
2908
+ catch (error) {
2909
+ return {
2910
+ detected: true,
2911
+ confidence: 60,
2912
+ method: 'cookies_error',
2913
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2914
+ };
2915
+ }
2916
+ }
2917
+ /**
2918
+ * Test cache detection through timing
2919
+ */
2920
+ async function testCacheDetection() {
2921
+ try {
2922
+ const image = new Image();
2923
+ const startTime = performance.now();
2924
+ return new Promise((resolve) => {
2925
+ const timeout = setTimeout(() => {
2926
+ resolve({
2927
+ detected: false,
2928
+ confidence: 10,
2929
+ method: 'cache_timeout'
2930
+ });
2931
+ }, 3000);
2932
+ image.onload = () => {
2933
+ clearTimeout(timeout);
2934
+ const loadTime = performance.now() - startTime;
2935
+ // Very fast load might indicate cache is disabled (incognito)
2936
+ if (loadTime < 5) {
2937
+ resolve({
2938
+ detected: true,
2939
+ confidence: 30,
2940
+ method: 'cache_too_fast',
2941
+ details: { loadTime }
2942
+ });
2943
+ }
2944
+ else {
2945
+ resolve({
2946
+ detected: false,
2947
+ confidence: 20,
2948
+ method: 'cache_normal',
2949
+ details: { loadTime }
2950
+ });
2951
+ }
2952
+ };
2953
+ image.onerror = () => {
2954
+ clearTimeout(timeout);
2955
+ resolve({
2956
+ detected: false,
2957
+ confidence: 10,
2958
+ method: 'cache_error'
2959
+ });
2960
+ };
2961
+ // Use a small, commonly cached image
2962
+ image.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
2963
+ });
2964
+ }
2965
+ catch (error) {
2966
+ return {
2967
+ detected: false,
2968
+ confidence: 10,
2969
+ method: 'cache_exception',
2970
+ details: { error: error instanceof Error ? error.message : 'Unknown' }
2971
+ };
2972
+ }
2973
+ }
2974
+ /**
2975
+ * Detect browser-specific incognito patterns
2976
+ */
2977
+ function detectBrowserSpecificPatterns() {
2978
+ const userAgent = navigator.userAgent.toLowerCase();
2979
+ if (userAgent.includes('chrome')) {
2980
+ return { browser: 'Chrome', supportsDetection: true };
2981
+ }
2982
+ else if (userAgent.includes('firefox')) {
2983
+ return { browser: 'Firefox', supportsDetection: true };
2984
+ }
2985
+ else if (userAgent.includes('safari')) {
2986
+ return { browser: 'Safari', supportsDetection: true };
2987
+ }
2988
+ else if (userAgent.includes('edge')) {
2989
+ return { browser: 'Edge', supportsDetection: true };
2990
+ }
2991
+ return { browser: 'Unknown', supportsDetection: false };
2992
+ }
2993
+ /**
2994
+ * Main incognito detection function
2995
+ */
2996
+ async function detectIncognitoMode() {
2997
+ detectBrowserSpecificPatterns();
2998
+ // Run all detection methods in parallel
2999
+ const [localStorage, sessionStorage, indexedDB, openDatabase, permissions, quotaManagement, filesystem, cookiesEnabled, cacheDetection] = await Promise.all([
3000
+ testLocalStorage(),
3001
+ testSessionStorage(),
3002
+ testIndexedDB(),
3003
+ testOpenDatabase(),
3004
+ testPermissions(),
3005
+ testQuotaManagement(),
3006
+ testFilesystem(),
3007
+ testCookiesEnabled(),
3008
+ testCacheDetection()
3009
+ ]);
3010
+ const detectionMethods = {
3011
+ localStorage,
3012
+ sessionStorage,
3013
+ indexedDB,
3014
+ openDatabase,
3015
+ permissions,
3016
+ quotaManagement,
3017
+ filesystem,
3018
+ cookiesEnabled,
3019
+ cacheDetection,
3020
+ canvasConsistency: { detected: false, confidence: 0, method: 'not_implemented' },
3021
+ webglConsistency: { detected: false, confidence: 0, method: 'not_implemented' },
3022
+ timingDifferences: { detected: false, confidence: 0, method: 'not_implemented' }
3023
+ };
3024
+ // Calculate overall confidence
3025
+ const detections = Object.values(detectionMethods).filter(d => d.detected);
3026
+ const totalConfidence = detections.reduce((sum, d) => sum + d.confidence, 0);
3027
+ const averageConfidence = detections.length > 0 ? totalConfidence / detections.length : 0;
3028
+ // Determine if incognito mode is detected
3029
+ const isIncognito = detections.length >= 2 || // Multiple methods detected
3030
+ detections.some(d => d.confidence >= 80); // High confidence detection
3031
+ const confidence = Math.min(averageConfidence, 95); // Cap at 95%
3032
+ return {
3033
+ isIncognito,
3034
+ confidence: Math.round(confidence),
3035
+ detectionMethods: Object.keys(detectionMethods).filter(key => detectionMethods[key]?.detected),
3036
+ details: {
3037
+ localStorage: localStorage?.detected ?? false,
3038
+ sessionStorage: sessionStorage?.detected ?? false,
3039
+ indexedDB: indexedDB?.detected ?? false,
3040
+ cookiesEnabled: cookiesEnabled?.detected ?? false,
3041
+ quota: quotaManagement?.detected ? 0 : null,
3042
+ permissions: permissions?.details || {}
3043
+ }
3044
+ };
3045
+ }
3046
+ /**
3047
+ * Quick incognito detection (less comprehensive but faster)
3048
+ */
3049
+ async function quickIncognitoDetection() {
3050
+ try {
3051
+ // Test the most reliable indicators quickly
3052
+ const [localStorage, indexedDB] = await Promise.all([
3053
+ testLocalStorage(),
3054
+ testIndexedDB()
3055
+ ]);
3056
+ return localStorage.detected || indexedDB.detected;
3057
+ }
3058
+ catch {
3059
+ return false;
3060
+ }
3061
+ }
3062
+
3063
+ /**
3064
+ * Enhanced User Agent Parser
3065
+ * Based on FingerprintJS browser detection with advanced engine and version parsing
3066
+ */
3067
+ /**
3068
+ * Browser engine detection (based on FingerprintJS)
3069
+ */
3070
+ function isChromium() {
3071
+ try {
3072
+ return typeof window.chrome === 'object' &&
3073
+ window.chrome !== null &&
3074
+ typeof window.chrome.runtime === 'object';
3075
+ }
3076
+ catch {
3077
+ return false;
3078
+ }
3079
+ }
3080
+ function isWebKit() {
3081
+ return navigator.userAgent.includes('WebKit') &&
3082
+ !navigator.userAgent.includes('Chrome');
3083
+ }
3084
+ function isGecko() {
3085
+ return navigator.userAgent.includes('Gecko') &&
3086
+ !navigator.userAgent.includes('Chrome');
3087
+ }
3088
+ function isSamsungInternet() {
3089
+ return navigator.userAgent.includes('SamsungBrowser') ||
3090
+ navigator.userAgent.includes('Samsung Internet');
3091
+ }
3092
+ function isIPad() {
3093
+ // iPad detection (iOS 13+ shows as desktop Safari)
3094
+ return navigator.userAgent.includes('iPad') ||
3095
+ (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
3096
+ }
3097
+ function isAndroid() {
3098
+ return navigator.userAgent.includes('Android');
3099
+ }
3100
+ /**
3101
+ * Extract browser version from user agent
3102
+ */
3103
+ function extractVersion(userAgent, pattern) {
3104
+ const match = userAgent.match(pattern);
3105
+ return match ? (match[1] || 'Unknown') : 'Unknown';
3106
+ }
3107
+ /**
3108
+ * Detect browser name and version
3109
+ */
3110
+ function detectBrowser(userAgent) {
3111
+ const ua = userAgent.toLowerCase();
3112
+ // Edge (Chromium-based)
3113
+ if (ua.includes('edg/')) {
3114
+ const version = extractVersion(userAgent, /edg\/([0-9.]+)/i);
3115
+ return {
3116
+ name: 'Edge',
3117
+ version,
3118
+ major: version.split('.')[0] || 'Unknown',
3119
+ engine: 'chromium'
3120
+ };
3121
+ }
3122
+ // Edge Legacy
3123
+ if (ua.includes('edge/')) {
3124
+ const version = extractVersion(userAgent, /edge\/([0-9.]+)/i);
3125
+ return {
3126
+ name: 'Edge Legacy',
3127
+ version,
3128
+ major: version.split('.')[0] || 'Unknown',
3129
+ engine: 'edgehtml'
3130
+ };
3131
+ }
3132
+ // Brave Browser (check before Chrome)
3133
+ if (ua.includes('brave/') || navigator.brave) {
3134
+ const version = extractVersion(userAgent, /chrome\/([0-9.]+)/i);
3135
+ return {
3136
+ name: 'Brave',
3137
+ version,
3138
+ major: version.split('.')[0] || 'Unknown',
3139
+ engine: 'chromium'
3140
+ };
3141
+ }
3142
+ // Samsung Internet
3143
+ if (isSamsungInternet()) {
3144
+ const version = extractVersion(userAgent, /samsungbrowser\/([0-9.]+)/i);
3145
+ return {
3146
+ name: 'Samsung Internet',
3147
+ version,
3148
+ major: version.split('.')[0] || 'Unknown',
3149
+ engine: 'chromium'
3150
+ };
3151
+ }
3152
+ // Chrome (check after other Chromium browsers)
3153
+ if (ua.includes('chrome/') && !ua.includes('edg')) {
3154
+ const version = extractVersion(userAgent, /chrome\/([0-9.]+)/i);
3155
+ return {
3156
+ name: 'Chrome',
3157
+ version,
3158
+ major: version.split('.')[0] || 'Unknown',
3159
+ engine: 'chromium'
3160
+ };
3161
+ }
3162
+ // Firefox
3163
+ if (ua.includes('firefox/')) {
3164
+ const version = extractVersion(userAgent, /firefox\/([0-9.]+)/i);
3165
+ return {
3166
+ name: 'Firefox',
3167
+ version,
3168
+ major: version.split('.')[0] || 'Unknown',
3169
+ engine: 'gecko'
3170
+ };
3171
+ }
3172
+ // Safari (check after Chrome to avoid false positives)
3173
+ if (ua.includes('safari/') && !ua.includes('chrome')) {
3174
+ const version = extractVersion(userAgent, /version\/([0-9.]+)/i);
3175
+ return {
3176
+ name: 'Safari',
3177
+ version,
3178
+ major: version.split('.')[0] || 'Unknown',
3179
+ engine: 'webkit'
3180
+ };
3181
+ }
3182
+ // Opera (modern versions)
3183
+ if (ua.includes('opr/') || ua.includes('opera/')) {
3184
+ const version = extractVersion(userAgent, /(?:opr|opera)\/([0-9.]+)/i);
3185
+ return {
3186
+ name: 'Opera',
3187
+ version,
3188
+ major: version.split('.')[0] || 'Unknown',
3189
+ engine: isChromium() ? 'chromium' : 'unknown'
3190
+ };
3191
+ }
3192
+ // Vivaldi
3193
+ if (ua.includes('vivaldi/')) {
3194
+ const version = extractVersion(userAgent, /vivaldi\/([0-9.]+)/i);
3195
+ return {
3196
+ name: 'Vivaldi',
3197
+ version,
3198
+ major: version.split('.')[0] || 'Unknown',
3199
+ engine: 'chromium'
3200
+ };
3201
+ }
3202
+ // Yandex Browser
3203
+ if (ua.includes('yabrowser/')) {
3204
+ const version = extractVersion(userAgent, /yabrowser\/([0-9.]+)/i);
3205
+ return {
3206
+ name: 'Yandex Browser',
3207
+ version,
3208
+ major: version.split('.')[0] || 'Unknown',
3209
+ engine: 'chromium'
3210
+ };
3211
+ }
3212
+ // Internet Explorer
3213
+ if (ua.includes('msie') || ua.includes('trident/')) {
3214
+ const version = extractVersion(userAgent, /(?:msie |rv:)([0-9.]+)/i);
3215
+ return {
3216
+ name: 'Internet Explorer',
3217
+ version,
3218
+ major: version.split('.')[0] || 'Unknown',
3219
+ engine: 'trident'
3220
+ };
3221
+ }
3222
+ // UC Browser
3223
+ if (ua.includes('ucbrowser/')) {
3224
+ const version = extractVersion(userAgent, /ucbrowser\/([0-9.]+)/i);
3225
+ return {
3226
+ name: 'UC Browser',
3227
+ version,
3228
+ major: version.split('.')[0] || 'Unknown',
3229
+ engine: 'chromium'
3230
+ };
3231
+ }
3232
+ // QQ Browser
3233
+ if (ua.includes('qqbrowser/')) {
3234
+ const version = extractVersion(userAgent, /qqbrowser\/([0-9.]+)/i);
3235
+ return {
3236
+ name: 'QQ Browser',
3237
+ version,
3238
+ major: version.split('.')[0] || 'Unknown',
3239
+ engine: 'chromium'
3240
+ };
3241
+ }
3242
+ // Unknown browser
3243
+ return {
3244
+ name: 'Unknown',
3245
+ version: 'Unknown',
3246
+ major: 'Unknown',
3247
+ engine: 'unknown'
3248
+ };
3249
+ }
3250
+ /**
3251
+ * Detect operating system
3252
+ */
3253
+ function detectOS(userAgent) {
3254
+ const ua = userAgent.toLowerCase();
3255
+ // Windows
3256
+ if (ua.includes('windows nt')) {
3257
+ const version = extractVersion(userAgent, /windows nt ([0-9.]+)/i);
3258
+ let osName = 'Windows';
3259
+ // Map NT versions to friendly names
3260
+ switch (version) {
3261
+ case '10.0':
3262
+ osName = 'Windows 10/11';
3263
+ break;
3264
+ case '6.3':
3265
+ osName = 'Windows 8.1';
3266
+ break;
3267
+ case '6.2':
3268
+ osName = 'Windows 8';
3269
+ break;
3270
+ case '6.1':
3271
+ osName = 'Windows 7';
3272
+ break;
3273
+ case '6.0':
3274
+ osName = 'Windows Vista';
3275
+ break;
3276
+ case '5.1':
3277
+ osName = 'Windows XP';
3278
+ break;
3279
+ default: osName = 'Windows';
3280
+ }
3281
+ return {
3282
+ name: osName,
3283
+ version,
3284
+ family: 'windows'
3285
+ };
3286
+ }
3287
+ // macOS / Mac OS X
3288
+ if (ua.includes('mac os x') || ua.includes('macos')) {
3289
+ const version = extractVersion(userAgent, /mac os x ([0-9._]+)/i);
3290
+ return {
3291
+ name: 'macOS',
3292
+ version: version.replace(/_/g, '.'),
3293
+ family: 'macos'
3294
+ };
3295
+ }
3296
+ // iOS
3297
+ if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod') || isIPad()) {
3298
+ const version = extractVersion(userAgent, /os ([0-9._]+)/i);
3299
+ return {
3300
+ name: 'iOS',
3301
+ version: version.replace(/_/g, '.'),
3302
+ family: 'ios'
3303
+ };
3304
+ }
3305
+ // Android
3306
+ if (isAndroid()) {
3307
+ const version = extractVersion(userAgent, /android ([0-9.]+)/i);
3308
+ return {
3309
+ name: 'Android',
3310
+ version,
3311
+ family: 'android'
3312
+ };
3313
+ }
3314
+ // Linux
3315
+ if (ua.includes('linux')) {
3316
+ return {
3317
+ name: 'Linux',
3318
+ version: 'Unknown',
3319
+ family: 'linux'
3320
+ };
3321
+ }
3322
+ // Chrome OS
3323
+ if (ua.includes('cros')) {
3324
+ const version = extractVersion(userAgent, /cros [a-z0-9_]+ ([0-9.]+)/i);
3325
+ return {
3326
+ name: 'Chrome OS',
3327
+ version,
3328
+ family: 'linux'
3329
+ };
3330
+ }
3331
+ // FreeBSD
3332
+ if (ua.includes('freebsd')) {
3333
+ return {
3334
+ name: 'FreeBSD',
3335
+ version: 'Unknown',
3336
+ family: 'unix'
3337
+ };
3338
+ }
3339
+ // Unknown OS
3340
+ return {
3341
+ name: 'Unknown',
3342
+ version: 'Unknown',
3343
+ family: 'unknown'
3344
+ };
3345
+ }
3346
+ /**
3347
+ * Detect device type
3348
+ */
3349
+ function detectDevice(userAgent) {
3350
+ const ua = userAgent.toLowerCase();
3351
+ // Tablet detection
3352
+ if (ua.includes('tablet') || isIPad()) {
3353
+ if (isIPad()) {
3354
+ return { type: 'tablet', vendor: 'Apple', model: 'iPad' };
3355
+ }
3356
+ if (ua.includes('kindle')) {
3357
+ return { type: 'tablet', vendor: 'Amazon', model: 'Kindle' };
3358
+ }
3359
+ return { type: 'tablet' };
3360
+ }
3361
+ // Mobile detection
3362
+ if (ua.includes('mobile') ||
3363
+ ua.includes('iphone') ||
3364
+ ua.includes('ipod') ||
3365
+ isAndroid()) {
3366
+ if (ua.includes('iphone')) {
3367
+ return { type: 'mobile', vendor: 'Apple', model: 'iPhone' };
3368
+ }
3369
+ if (ua.includes('ipod')) {
3370
+ return { type: 'mobile', vendor: 'Apple', model: 'iPod' };
3371
+ }
3372
+ if (isAndroid() && ua.includes('mobile')) {
3373
+ return { type: 'mobile', vendor: 'Android' };
3374
+ }
3375
+ return { type: 'mobile' };
3376
+ }
3377
+ // Desktop (default)
3378
+ return { type: 'desktop' };
3379
+ }
3380
+ /**
3381
+ * Check for suspicious patterns in user agent
3382
+ */
3383
+ function analyzeSuspiciousPatterns(userAgent) {
3384
+ const patterns = [];
3385
+ let category = 'legitimate';
3386
+ // Bot patterns
3387
+ const botKeywords = [
3388
+ 'bot', 'crawler', 'spider', 'scraper', 'automation',
3389
+ 'headless', 'phantom', 'selenium', 'webdriver',
3390
+ 'google', 'bing', 'yahoo', 'baidu', 'yandex',
3391
+ 'facebook', 'twitter', 'linkedin', 'whatsapp'
3392
+ ];
3393
+ for (const keyword of botKeywords) {
3394
+ if (userAgent.toLowerCase().includes(keyword)) {
3395
+ patterns.push(keyword);
3396
+ if (['bot', 'crawler', 'spider', 'scraper'].some(k => keyword.includes(k))) {
3397
+ category = 'crawler';
3398
+ }
3399
+ else if (['selenium', 'webdriver', 'automation'].some(k => keyword.includes(k))) {
3400
+ category = 'automation';
3401
+ }
3402
+ else if (['headless', 'phantom'].some(k => keyword.includes(k))) {
3403
+ category = 'headless';
3404
+ }
3405
+ }
3406
+ }
3407
+ // Suspicious patterns
3408
+ if (userAgent.length < 10) {
3409
+ patterns.push('too_short');
3410
+ }
3411
+ if (!userAgent || userAgent === 'Mozilla/5.0') {
3412
+ patterns.push('generic_mozilla');
3413
+ }
3414
+ const isBot = patterns.length > 0;
3415
+ return { isBot, patterns, category };
3416
+ }
3417
+ /**
3418
+ * Enhanced user agent analysis
3419
+ */
3420
+ function analyzeUserAgent(userAgent) {
3421
+ const ua = userAgent || navigator.userAgent || '';
3422
+ const browser = detectBrowser(ua);
3423
+ const os = detectOS(ua);
3424
+ const device = detectDevice(ua);
3425
+ const suspicious = analyzeSuspiciousPatterns(ua);
3426
+ return {
3427
+ browser: {
3428
+ name: browser.name,
3429
+ version: browser.version,
3430
+ major: browser.major,
3431
+ engine: browser.engine
3432
+ },
3433
+ os: {
3434
+ name: os.name,
3435
+ version: os.version,
3436
+ family: os.family
3437
+ },
3438
+ device: {
3439
+ type: device.type,
3440
+ vendor: device.vendor,
3441
+ model: device.model
3442
+ },
3443
+ suspicious: {
3444
+ isBot: suspicious.isBot,
3445
+ patterns: suspicious.patterns,
3446
+ category: suspicious.category
3447
+ },
3448
+ isMobile: device.type === 'mobile',
3449
+ isTablet: device.type === 'tablet',
3450
+ isDesktop: device.type === 'desktop',
3451
+ isBot: suspicious.isBot,
3452
+ raw: ua
3453
+ };
3454
+ }
3455
+ /**
3456
+ * Get browser engine type
3457
+ */
3458
+ function getBrowserEngine() {
3459
+ if (isChromium())
3460
+ return 'chromium';
3461
+ if (isWebKit())
3462
+ return 'webkit';
3463
+ if (isGecko())
3464
+ return 'gecko';
3465
+ const ua = navigator.userAgent.toLowerCase();
3466
+ if (ua.includes('trident'))
3467
+ return 'trident';
3468
+ return 'unknown';
3469
+ }
3470
+ /**
3471
+ * Check if running on mobile device
3472
+ */
3473
+ function isMobile() {
3474
+ return detectDevice(navigator.userAgent).type === 'mobile';
3475
+ }
3476
+ /**
3477
+ * Check if running on tablet
3478
+ */
3479
+ function isTablet() {
3480
+ return detectDevice(navigator.userAgent).type === 'tablet';
3481
+ }
3482
+ /**
3483
+ * Check if running on desktop
3484
+ */
3485
+ function isDesktop() {
3486
+ return detectDevice(navigator.userAgent).type === 'desktop';
3487
+ }
3488
+
3489
+ exports.RabbitTrackerSDK = RabbitTrackerSDK;
3490
+ exports.analyzeUserAgent = analyzeUserAgent;
3491
+ exports.clearFingerprintCache = clearFingerprintCache;
3492
+ exports.collectFingerprint = collectFingerprint;
3493
+ exports.default = RabbitTrackerSDK;
3494
+ exports.detectBot = detectBot;
3495
+ exports.detectIncognitoMode = detectIncognitoMode;
3496
+ exports.generateVisitorId = generateVisitorId;
3497
+ exports.generateVisitorIdFromFingerprint = generateVisitorIdFromFingerprint;
3498
+ exports.getAvailableComponents = getAvailableComponents;
3499
+ exports.getBrowserEngine = getBrowserEngine;
3500
+ exports.getCompleteFingerprint = getCompleteFingerprint;
3501
+ exports.getLightweightFingerprint = getLightweightFingerprint;
3502
+ exports.hash32 = hash32;
3503
+ exports.hashFingerprint = hashFingerprint;
3504
+ exports.isAndroid = isAndroid;
3505
+ exports.isChromium = isChromium;
3506
+ exports.isDesktop = isDesktop;
3507
+ exports.isFingerprintingAvailable = isFingerprintingAvailable;
3508
+ exports.isGecko = isGecko;
3509
+ exports.isIPad = isIPad;
3510
+ exports.isMobile = isMobile;
3511
+ exports.isSamsungInternet = isSamsungInternet;
3512
+ exports.isTablet = isTablet;
3513
+ exports.isWebKit = isWebKit;
3514
+ exports.quickBotDetection = quickBotDetection;
3515
+ exports.quickIncognitoDetection = quickIncognitoDetection;
3516
+ exports.x64hash128 = x64hash128;
3517
+ //# sourceMappingURL=index.js.map