@zaplier/sdk 1.0.9 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -747,6 +747,11 @@ declare class ZaplierSDK implements ZaplierSDK$1 {
747
747
  * Generate session ID
748
748
  */
749
749
  private generateSessionId;
750
+ /**
751
+ * Get stable browser characteristics for session ID generation
752
+ * Uses only stable, privacy-safe values that don't change during session
753
+ */
754
+ private getBrowserFingerprint;
750
755
  /**
751
756
  * Initialize Anti-Adblock Manager
752
757
  */
package/dist/index.esm.js CHANGED
@@ -256,8 +256,22 @@ function canonicalizeStable(input) {
256
256
  return out;
257
257
  }
258
258
  if (typeof input === "number") {
259
- // Normalize floating point to 2 decimals to reduce micro-variance
260
- return Number.isFinite(input) ? Number(input.toFixed(2)) : 0;
259
+ // Enhanced floating point normalization for maximum stability
260
+ if (!Number.isFinite(input))
261
+ return 0;
262
+ // Handle very small numbers (round to 0)
263
+ if (Math.abs(input) < 0.0001)
264
+ return 0;
265
+ // Handle very large numbers (clamp to reasonable range)
266
+ if (Math.abs(input) > 1000000) {
267
+ return input > 0 ? 1000000 : -1e6;
268
+ }
269
+ // For screen ratios and similar values, use more precision
270
+ if (input >= 0.1 && input <= 10) {
271
+ return Number(input.toFixed(3));
272
+ }
273
+ // For other numbers, use 2 decimal places
274
+ return Number(input.toFixed(2));
261
275
  }
262
276
  if (typeof input === "string") {
263
277
  return input.trim().toLowerCase();
@@ -1232,15 +1246,18 @@ function getTimezoneInfo() {
1232
1246
  // Fallback
1233
1247
  }
1234
1248
  }
1235
- // Fallback: use timezone offset
1249
+ // Fallback: use timezone offset (STABLE - use standard time offset to avoid DST changes)
1236
1250
  if (!timezone) {
1237
- const offset = new Date().getTimezoneOffset();
1251
+ // Use January date to get standard time offset (avoiding DST variations)
1252
+ const januaryDate = new Date(2024, 0, 1); // January 1st
1253
+ const offset = januaryDate.getTimezoneOffset();
1238
1254
  const sign = offset > 0 ? '-' : '+';
1239
1255
  const hours = Math.floor(Math.abs(offset) / 60);
1240
1256
  const minutes = Math.abs(offset) % 60;
1241
1257
  timezone = `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
1242
1258
  }
1243
- const timezoneOffset = new Date().getTimezoneOffset();
1259
+ // Use January date for stable timezone offset (avoiding DST fluctuations)
1260
+ const timezoneOffset = new Date(2024, 0, 1).getTimezoneOffset();
1244
1261
  return { timezone, timezoneOffset };
1245
1262
  }
1246
1263
  catch {
@@ -1588,25 +1605,40 @@ function analyzeSubPixels(ctx, canvas) {
1588
1605
  // Get pixel data for analysis
1589
1606
  const imageData = ctx.getImageData(10, 10, 30, 40);
1590
1607
  const pixels = imageData.data;
1591
- // Calculate sub-pixel characteristics
1608
+ // Calculate sub-pixel characteristics with enhanced stability
1592
1609
  let redSum = 0, greenSum = 0, blueSum = 0;
1593
1610
  let edgeVariance = 0;
1611
+ let validPixels = 0;
1594
1612
  for (let i = 0; i < pixels.length; i += 4) {
1595
- redSum += pixels[i] || 0;
1596
- greenSum += pixels[i + 1] || 0;
1597
- blueSum += pixels[i + 2] || 0;
1598
- // Calculate edge variance for anti-aliasing detection
1599
- if (i > 0) {
1600
- const prevR = pixels[i - 4] || 0;
1601
- const currR = pixels[i] || 0;
1602
- edgeVariance += Math.abs(currR - prevR);
1603
- }
1604
- }
1605
- const pixelCount = pixels.length / 4;
1606
- const avgRed = Math.round(redSum / pixelCount);
1607
- const avgGreen = Math.round(greenSum / pixelCount);
1608
- const avgBlue = Math.round(blueSum / pixelCount);
1609
- const avgVariance = Math.round(edgeVariance / pixelCount);
1613
+ const r = pixels[i] || 0;
1614
+ const g = pixels[i + 1] || 0;
1615
+ const b = pixels[i + 2] || 0;
1616
+ const a = pixels[i + 3] || 0;
1617
+ // Only process non-transparent pixels for stability
1618
+ if (a > 25) { // Higher threshold than before
1619
+ redSum += r;
1620
+ greenSum += g;
1621
+ blueSum += b;
1622
+ validPixels++;
1623
+ // Calculate edge variance with larger step for stability
1624
+ if (i >= 16) { // Skip more pixels for stability
1625
+ const prevR = pixels[i - 16] || 0;
1626
+ const diff = Math.abs(r - prevR);
1627
+ // Only count significant differences to reduce noise
1628
+ if (diff >= 8) { // Higher threshold
1629
+ edgeVariance += diff;
1630
+ }
1631
+ }
1632
+ }
1633
+ }
1634
+ if (validPixels === 0) {
1635
+ return "no-valid-pixels";
1636
+ }
1637
+ // Round to nearest 10 for maximum stability
1638
+ const avgRed = Math.round((redSum / validPixels) / 10) * 10;
1639
+ const avgGreen = Math.round((greenSum / validPixels) / 10) * 10;
1640
+ const avgBlue = Math.round((blueSum / validPixels) / 10) * 10;
1641
+ const avgVariance = Math.round((edgeVariance / validPixels) / 10) * 10;
1610
1642
  return `${avgRed}-${avgGreen}-${avgBlue}-${avgVariance}`;
1611
1643
  }
1612
1644
  catch (error) {
@@ -2208,7 +2240,8 @@ const MAX_CONTEXTS = 8;
2208
2240
  */
2209
2241
  function getCachedWebGLContext() {
2210
2242
  try {
2211
- const now = Date.now();
2243
+ // Use performance.now() for more stable timing measurements
2244
+ const now = performance.now();
2212
2245
  // Check if cache is valid and not expired
2213
2246
  if (webglCache && webglCache.isValid) {
2214
2247
  if (now - webglCache.timestamp < CACHE_TIMEOUT) {
@@ -2669,12 +2702,14 @@ function render3DScene(gl) {
2669
2702
  const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
2670
2703
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
2671
2704
  if (!vertexShader || !fragmentShader) {
2672
- return '';
2705
+ // Fallback to basic parameters hash if shader compilation fails
2706
+ return generateFallbackWebGLHash(gl);
2673
2707
  }
2674
2708
  // Create program
2675
2709
  const program = createProgram(gl, vertexShader, fragmentShader);
2676
2710
  if (!program) {
2677
- return '';
2711
+ // Fallback to basic parameters hash if program creation fails
2712
+ return generateFallbackWebGLHash(gl);
2678
2713
  }
2679
2714
  gl.useProgram(program);
2680
2715
  // Create complex geometry that differentiates GPU rendering
@@ -2731,7 +2766,35 @@ function render3DScene(gl) {
2731
2766
  return hash.toString(16);
2732
2767
  }
2733
2768
  catch (error) {
2734
- return '';
2769
+ // Fallback to basic parameters hash if rendering fails
2770
+ return generateFallbackWebGLHash(gl);
2771
+ }
2772
+ }
2773
+ /**
2774
+ * Generate fallback WebGL hash when rendering fails
2775
+ * Uses basic WebGL parameters to ensure consistent fingerprinting
2776
+ */
2777
+ function generateFallbackWebGLHash(gl) {
2778
+ try {
2779
+ // Collect basic but stable WebGL parameters
2780
+ const vendor = gl.getParameter(gl.VENDOR) || 'unknown';
2781
+ const renderer = gl.getParameter(gl.RENDERER) || 'unknown';
2782
+ const version = gl.getParameter(gl.VERSION) || 'unknown';
2783
+ const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) || 0;
2784
+ const maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS) || 0;
2785
+ const maxViewportDims = gl.getParameter(gl.MAX_VIEWPORT_DIMS) || [0, 0];
2786
+ // Combine parameters into string
2787
+ const combined = `${vendor}|${renderer}|${version}|${maxTextureSize}|${maxVertexAttribs}|${maxViewportDims}`;
2788
+ // Generate simple hash
2789
+ let hash = 0;
2790
+ for (let i = 0; i < combined.length; i++) {
2791
+ const char = combined.charCodeAt(i);
2792
+ hash = ((hash << 5) - hash + char) & 0xffffffff;
2793
+ }
2794
+ return `fallback_${Math.abs(hash).toString(16)}`;
2795
+ }
2796
+ catch {
2797
+ return 'webgl_unavailable';
2735
2798
  }
2736
2799
  }
2737
2800
  /**
@@ -5065,91 +5128,67 @@ async function collectFingerprint(options = {}) {
5065
5128
  gdprMode: opts.gdprMode,
5066
5129
  components: [],
5067
5130
  };
5068
- // Collect Canvas fingerprint
5131
+ // OPTIMIZATION: Collect independent fingerprint components in parallel
5132
+ // This reduces total collection time from 3-5s to 500ms-1s
5133
+ const parallelTasks = [];
5134
+ // Group 1: Independent rendering components (can run in parallel)
5069
5135
  if (shouldIncludeComponent("canvas", opts) && isCanvasAvailable$1()) {
5070
- const result = await collectComponent("canvas", getCanvasFingerprint, opts);
5071
- if (result) {
5072
- fingerprintData.canvas = result;
5073
- collectedComponents.push("canvas");
5074
- }
5075
- else {
5076
- failedComponents.push({
5077
- component: "canvas",
5078
- error: "Collection failed or timeout",
5079
- });
5080
- }
5136
+ parallelTasks.push(collectComponent("canvas", getCanvasFingerprint, opts).then(result => ({
5137
+ component: "canvas",
5138
+ result
5139
+ })));
5081
5140
  }
5082
- // Collect WebGL fingerprint
5083
5141
  if (shouldIncludeComponent("webgl", opts) && isWebGLAvailable()) {
5084
- const result = await collectComponent("webgl", getWebGLFingerprint, opts);
5085
- if (result) {
5086
- fingerprintData.webgl = result;
5087
- collectedComponents.push("webgl");
5088
- }
5089
- else {
5090
- failedComponents.push({
5091
- component: "webgl",
5092
- error: "Collection failed or timeout",
5093
- });
5094
- }
5142
+ parallelTasks.push(collectComponent("webgl", getWebGLFingerprint, opts).then(result => ({
5143
+ component: "webgl",
5144
+ result
5145
+ })));
5095
5146
  }
5096
- // Collect Audio fingerprint
5097
5147
  if (shouldIncludeComponent("audio", opts) && isAudioAvailable()) {
5098
- const result = await collectComponent("audio", getAudioFingerprint, opts);
5099
- if (result) {
5100
- fingerprintData.audio = result;
5101
- collectedComponents.push("audio");
5102
- }
5103
- else {
5104
- failedComponents.push({
5105
- component: "audio",
5106
- error: "Collection failed or timeout",
5107
- });
5108
- }
5148
+ parallelTasks.push(collectComponent("audio", getAudioFingerprint, opts).then(result => ({
5149
+ component: "audio",
5150
+ result
5151
+ })));
5109
5152
  }
5110
- // Collect Font fingerprint
5153
+ // Group 2: Fast synchronous components (can run in parallel)
5111
5154
  if (shouldIncludeComponent("fonts", opts) && isFontDetectionAvailable()) {
5112
- const result = await collectComponent("fonts", () => getFontFingerprint(!opts.gdprMode), opts);
5113
- if (result) {
5114
- fingerprintData.fonts = result;
5115
- collectedComponents.push("fonts");
5116
- }
5117
- else {
5118
- failedComponents.push({
5119
- component: "fonts",
5120
- error: "Collection failed or timeout",
5121
- });
5122
- }
5155
+ parallelTasks.push(collectComponent("fonts", () => getFontFingerprint(!opts.gdprMode), opts).then(result => ({
5156
+ component: "fonts",
5157
+ result
5158
+ })));
5123
5159
  }
5124
- // Collect Screen fingerprint (always available)
5125
5160
  if (shouldIncludeComponent("screen", opts) && isScreenAvailable()) {
5126
- const result = await collectComponent("screen", getScreenFingerprint, opts);
5127
- if (result) {
5128
- fingerprintData.screen = result;
5129
- collectedComponents.push("screen");
5161
+ parallelTasks.push(collectComponent("screen", getScreenFingerprint, opts).then(result => ({
5162
+ component: "screen",
5163
+ result
5164
+ })));
5165
+ }
5166
+ if (shouldIncludeComponent("browser", opts) && isBrowserFingerprintingAvailable()) {
5167
+ parallelTasks.push(collectComponent("browser", getBrowserFingerprint, opts).then(result => ({
5168
+ component: "browser",
5169
+ result
5170
+ })));
5171
+ }
5172
+ // Execute all parallel tasks and handle results
5173
+ const parallelResults = await Promise.allSettled(parallelTasks);
5174
+ parallelResults.forEach((promiseResult, index) => {
5175
+ if (promiseResult.status === 'fulfilled' && promiseResult.value.result) {
5176
+ const { component, result } = promiseResult.value;
5177
+ fingerprintData[component] = result;
5178
+ collectedComponents.push(component);
5130
5179
  }
5131
5180
  else {
5181
+ // Extract component name from the corresponding task
5182
+ const taskComponent = parallelTasks[index];
5183
+ const componentName = taskComponent?.toString().match(/component: "(\w+)"/)?.[1] || 'unknown';
5132
5184
  failedComponents.push({
5133
- component: "screen",
5134
- error: "Collection failed or timeout",
5185
+ component: componentName,
5186
+ error: promiseResult.status === 'rejected'
5187
+ ? promiseResult.reason?.message || "Promise rejected"
5188
+ : "Collection failed or timeout",
5135
5189
  });
5136
5190
  }
5137
- }
5138
- // Collect Browser fingerprint (always available)
5139
- if (shouldIncludeComponent("browser", opts) &&
5140
- isBrowserFingerprintingAvailable()) {
5141
- const result = await collectComponent("browser", getBrowserFingerprint, opts);
5142
- if (result) {
5143
- fingerprintData.browser = result;
5144
- collectedComponents.push("browser");
5145
- }
5146
- else {
5147
- failedComponents.push({
5148
- component: "browser",
5149
- error: "Collection failed or timeout",
5150
- });
5151
- }
5152
- }
5191
+ });
5153
5192
  // ADDED: Collect Incognito Detection (IMPROVED - 2024)
5154
5193
  // Only in enhanced mode for better accuracy
5155
5194
  if (!opts.gdprMode) {
@@ -5176,9 +5215,9 @@ async function collectFingerprint(options = {}) {
5176
5215
  // Collect Hardware fingerprint (NEW - inspired by FingerprintJS)
5177
5216
  if (shouldIncludeComponent("hardware", opts)) {
5178
5217
  try {
5179
- const { getHardwareFingerprint, isHardwareDetectionAvailable } = await Promise.resolve().then(function () { return hardware; });
5180
- if (isHardwareDetectionAvailable()) {
5181
- const result = getHardwareFingerprint();
5218
+ const hardwareModule = await Promise.resolve().then(function () { return hardware; }).catch(() => null);
5219
+ if (hardwareModule?.isHardwareDetectionAvailable?.()) {
5220
+ const result = hardwareModule.getHardwareFingerprint?.();
5182
5221
  if (result) {
5183
5222
  fingerprintData.hardware = {
5184
5223
  value: result,
@@ -5187,6 +5226,19 @@ async function collectFingerprint(options = {}) {
5187
5226
  collectedComponents.push("hardware");
5188
5227
  }
5189
5228
  }
5229
+ else if (!hardwareModule) {
5230
+ // Fallback for when module fails to load
5231
+ const fallbackHardware = {
5232
+ hardwareConcurrency: navigator.hardwareConcurrency || 0,
5233
+ deviceMemory: navigator.deviceMemory || 0,
5234
+ platform: navigator.platform || 'unknown'
5235
+ };
5236
+ fingerprintData.hardware = {
5237
+ value: fallbackHardware,
5238
+ duration: 0,
5239
+ };
5240
+ collectedComponents.push("hardware");
5241
+ }
5190
5242
  }
5191
5243
  catch (error) {
5192
5244
  failedComponents.push({
@@ -6006,6 +6058,17 @@ async function collectFingerprint(options = {}) {
6006
6058
  // - Screen frame (can vary slightly)
6007
6059
  // - Vendor flavors (can change)
6008
6060
  // - Exact hardware values (use buckets)
6061
+ // DEBUG: Log coreVector components to identify DevTools instability
6062
+ if (opts.debug || (typeof window !== 'undefined' && window._rabbitTrackerDebug)) {
6063
+ console.log('[RabbitTracker DEBUG] coreVector components:', {
6064
+ ua: coreVector.ua,
6065
+ platform: coreVector.platform,
6066
+ deviceType: coreVector.deviceType,
6067
+ screenBucket: coreVector.screen?.bucket,
6068
+ timezone: coreVector.timezone,
6069
+ _entropy: coreVector._entropy
6070
+ });
6071
+ }
6009
6072
  // Generate ultra-stable hash with additional collision resistance
6010
6073
  const stableCoreHash = hashStableCore(coreVector, opts.debug);
6011
6074
  // CRITICAL: Generate enhanced fingerprint hash combining STABLE components only
@@ -7400,7 +7463,11 @@ class ZaplierSDK {
7400
7463
  enable: () => {
7401
7464
  this.config.heatmap = true;
7402
7465
  if (!this.heatmapEngine && this.isInitialized) {
7403
- const sessionId = this.sessionId || this.generateSessionId();
7466
+ // Ensure sessionId is available (should be set during initialization)
7467
+ if (!this.sessionId) {
7468
+ this.sessionId = this.generateSessionId();
7469
+ }
7470
+ const sessionId = this.sessionId;
7404
7471
  this.heatmapEngine = new HeatmapEngine(sessionId, {
7405
7472
  trackClicks: true,
7406
7473
  trackScrollDepth: true,
@@ -7447,7 +7514,11 @@ class ZaplierSDK {
7447
7514
  enable: () => {
7448
7515
  this.config.replay = true;
7449
7516
  if (!this.replayEngine && this.isInitialized) {
7450
- const sessionId = this.sessionId || this.generateSessionId();
7517
+ // Ensure sessionId is available (should be set during initialization)
7518
+ if (!this.sessionId) {
7519
+ this.sessionId = this.generateSessionId();
7520
+ }
7521
+ const sessionId = this.sessionId;
7451
7522
  this.replayEngine = new SessionReplayEngine(sessionId, {
7452
7523
  sampleRate: this.config.replaySampling,
7453
7524
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7471,7 +7542,11 @@ class ZaplierSDK {
7471
7542
  },
7472
7543
  start: () => {
7473
7544
  if (!this.replayEngine && this.isInitialized) {
7474
- const sessionId = this.sessionId || this.generateSessionId();
7545
+ // Ensure sessionId is available (should be set during initialization)
7546
+ if (!this.sessionId) {
7547
+ this.sessionId = this.generateSessionId();
7548
+ }
7549
+ const sessionId = this.sessionId;
7475
7550
  this.replayEngine = new SessionReplayEngine(sessionId, {
7476
7551
  sampleRate: 1.0, // Force recording when manually started
7477
7552
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7561,9 +7636,12 @@ class ZaplierSDK {
7561
7636
  */
7562
7637
  initializeTrackingEngines() {
7563
7638
  try {
7564
- // Generate session ID if not exists
7639
+ // Generate session ID once and only once during initialization
7565
7640
  if (!this.sessionId) {
7566
7641
  this.sessionId = this.generateSessionId();
7642
+ if (this.config.debug) {
7643
+ console.log("[Zaplier] Generated session ID:", this.sessionId);
7644
+ }
7567
7645
  }
7568
7646
  // Initialize Session Replay Engine
7569
7647
  if (this.config.replay) {
@@ -7599,7 +7677,33 @@ class ZaplierSDK {
7599
7677
  * Generate session ID
7600
7678
  */
7601
7679
  generateSessionId() {
7602
- return Date.now().toString(36) + Math.random().toString(36).substr(2);
7680
+ // Create deterministic session ID based on page load time and stable browser characteristics
7681
+ const pageLoadTime = performance.timeOrigin || Date.now();
7682
+ const browserFingerprint = this.getBrowserFingerprint();
7683
+ // Use page load time + browser fingerprint for session uniqueness within same page load
7684
+ return pageLoadTime.toString(36) + browserFingerprint.substring(0, 8);
7685
+ }
7686
+ /**
7687
+ * Get stable browser characteristics for session ID generation
7688
+ * Uses only stable, privacy-safe values that don't change during session
7689
+ */
7690
+ getBrowserFingerprint() {
7691
+ const components = [
7692
+ navigator.userAgent || '',
7693
+ navigator.platform || '',
7694
+ screen.width || 0,
7695
+ screen.height || 0,
7696
+ new Date(2024, 0, 1).getTimezoneOffset(),
7697
+ navigator.language || '',
7698
+ navigator.hardwareConcurrency || 0
7699
+ ];
7700
+ const combined = components.join('|');
7701
+ let hash = 0;
7702
+ for (let i = 0; i < combined.length; i++) {
7703
+ const char = combined.charCodeAt(i);
7704
+ hash = ((hash << 5) - hash + char) & 0xffffffff;
7705
+ }
7706
+ return Math.abs(hash).toString(36);
7603
7707
  }
7604
7708
  /**
7605
7709
  * Initialize Anti-Adblock Manager
@@ -7667,7 +7771,7 @@ class ZaplierSDK {
7667
7771
  }
7668
7772
  // Fallback: collect entropy for diagnostics only; do NOT set local visitorId
7669
7773
  try {
7670
- // Collect all available entropy sources
7774
+ // Collect stable entropy sources (no random values for consistency)
7671
7775
  const entropyComponents = [
7672
7776
  navigator.userAgent || "",
7673
7777
  navigator.language || "",
@@ -7675,44 +7779,40 @@ class ZaplierSDK {
7675
7779
  screen.width || 0,
7676
7780
  screen.height || 0,
7677
7781
  screen.colorDepth || 0,
7678
- new Date().getTimezoneOffset(),
7679
- // Hardware-specific
7782
+ new Date(2024, 0, 1).getTimezoneOffset(),
7783
+ // Hardware-specific (stable values)
7680
7784
  navigator.hardwareConcurrency || 0,
7681
7785
  navigator.deviceMemory || 0,
7682
7786
  navigator.maxTouchPoints || 0,
7683
- // High-resolution timestamps for uniqueness
7684
- performance.now(),
7685
- Date.now(),
7686
- // Random component to ensure uniqueness
7687
- Math.random() * 1000000,
7688
- Math.random() * 1000000, // Double random for extra entropy
7689
- // Memory usage pattern (if available)
7690
- performance.memory?.usedJSHeapSize ||
7691
- Math.random() * 1000000,
7692
- performance.memory?.totalJSHeapSize ||
7693
- Math.random() * 1000000,
7787
+ // Use page load time (stable during session) instead of current time
7788
+ performance.timeOrigin || 0,
7789
+ // Deterministic entropy from browser characteristics
7790
+ (navigator.plugins ? navigator.plugins.length : 0),
7791
+ (navigator.mimeTypes ? navigator.mimeTypes.length : 0),
7792
+ // Memory usage pattern (if available, use rounded values for stability)
7793
+ Math.floor((performance.memory?.usedJSHeapSize || 0) / 1000000),
7794
+ Math.floor((performance.memory?.totalJSHeapSize || 0) / 1000000),
7694
7795
  // Connection info (if available)
7695
7796
  navigator.connection?.effectiveType || "",
7696
- navigator.connection?.downlink || Math.random() * 100,
7797
+ Math.floor(navigator.connection?.downlink || 0),
7697
7798
  // Additional browser-specific entropy
7698
7799
  window.outerHeight || 0,
7699
7800
  window.outerWidth || 0,
7700
- window.devicePixelRatio || 1,
7701
- // Session-specific entropy
7702
- sessionStorage.length || Math.random() * 100,
7801
+ Math.floor((window.devicePixelRatio || 1) * 100) / 100, // Round to 2 decimals
7802
+ // Canvas support (deterministic)
7803
+ typeof document.createElement('canvas').getContext === 'function' ? 1 : 0,
7703
7804
  ];
7704
- // Create high-entropy string
7705
- const entropyString = entropyComponents.join("|") + "|" + Math.random().toString(36);
7706
- // Use crypto.getRandomValues if available for extra randomness
7707
- let cryptoRandom = "";
7708
- if (window.crypto && window.crypto.getRandomValues) {
7709
- const array = new Uint32Array(4);
7710
- window.crypto.getRandomValues(array);
7711
- cryptoRandom = Array.from(array).join("");
7805
+ // Create deterministic entropy string
7806
+ const entropyString = entropyComponents.join("|");
7807
+ // Add deterministic checksum instead of random values
7808
+ let checksum = 0;
7809
+ for (let i = 0; i < entropyString.length; i++) {
7810
+ checksum = ((checksum << 5) - checksum + entropyString.charCodeAt(i)) & 0xffffffff;
7712
7811
  }
7812
+ const deterministic = Math.abs(checksum).toString(36);
7713
7813
  // Advanced hash function with better distribution
7714
7814
  let hash = 0;
7715
- const finalString = entropyString + cryptoRandom;
7815
+ const finalString = entropyString + deterministic;
7716
7816
  for (let i = 0; i < finalString.length; i++) {
7717
7817
  const char = finalString.charCodeAt(i);
7718
7818
  hash = ((hash << 5) - hash + char) & 0xffffffff;
@@ -10628,8 +10728,8 @@ function getTimezone() {
10628
10728
  return timezone;
10629
10729
  }
10630
10730
  }
10631
- // Fallback: timezone offset
10632
- const offset = new Date().getTimezoneOffset();
10731
+ // Fallback: timezone offset (STABLE - use standard time to avoid DST changes)
10732
+ const offset = new Date(2024, 0, 1).getTimezoneOffset();
10633
10733
  return `UTC${offset > 0 ? '-' : '+'}${Math.abs(offset / 60)}`;
10634
10734
  }
10635
10735
  catch {
@@ -12457,16 +12557,84 @@ function getScreenFrame() {
12457
12557
  }
12458
12558
  /**
12459
12559
  * Get CPU architecture signature (ARM vs x86)
12460
- * Uses floating point NaN behavior to detect architecture
12560
+ * Uses multiple detection methods for maximum reliability
12461
12561
  */
12462
12562
  function getArchitecture() {
12463
12563
  try {
12464
- // x86/x86-64 sets sign bit on NaN from infinity subtraction
12465
- const f = new Float32Array(1);
12466
- const u8 = new Uint8Array(f.buffer);
12467
- f[0] = Infinity;
12468
- f[0] = f[0] - f[0]; // Should produce NaN
12469
- return u8[3]; // Sign bit in byte 3
12564
+ // Method 1: Enhanced platform string detection (most reliable)
12565
+ const platform = navigator.platform?.toLowerCase() || '';
12566
+ const userAgent = navigator.userAgent?.toLowerCase() || '';
12567
+ // CRITICAL FIX: Prioritize desktop x86_64 detection first
12568
+ // Windows x64/x86 detection (highest priority for x86_64)
12569
+ if (platform.includes('win') && (userAgent.includes('wow64') || userAgent.includes('x64') || userAgent.includes('x86_64'))) {
12570
+ return 0; // x86_64 - Windows
12571
+ }
12572
+ // Linux x86_64 detection
12573
+ if (platform.includes('linux') && (platform.includes('x86_64') || userAgent.includes('x86_64'))) {
12574
+ return 0; // x86_64 - Linux
12575
+ }
12576
+ // MacOS Intel detection (before ARM check)
12577
+ if (platform.includes('mac') && (platform.includes('intel') || userAgent.includes('intel mac'))) {
12578
+ return 0; // x86_64 - Intel Mac
12579
+ }
12580
+ // Generic x86/x64 detection
12581
+ if (platform.includes('x86') || platform.includes('x64') ||
12582
+ platform.includes('intel') || platform.includes('amd64') ||
12583
+ userAgent.includes('x86') || userAgent.includes('amd64')) {
12584
+ return 0; // x86_64
12585
+ }
12586
+ // ARM detection (after x86_64 checks)
12587
+ if (platform.includes('arm') || userAgent.includes('arm') ||
12588
+ userAgent.includes('aarch64') || platform.includes('aarch64')) {
12589
+ return 255; // ARM
12590
+ }
12591
+ // Method 2: WebGL vendor detection (secondary check)
12592
+ try {
12593
+ const canvas = document.createElement('canvas');
12594
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
12595
+ if (gl && 'getExtension' in gl && 'getParameter' in gl) {
12596
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
12597
+ if (debugInfo) {
12598
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)?.toLowerCase() || '';
12599
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)?.toLowerCase() || '';
12600
+ // Enhanced x86_64 GPU detection (desktop GPUs)
12601
+ if (vendor.includes('intel') || vendor.includes('nvidia') || vendor.includes('amd') ||
12602
+ renderer.includes('intel') || renderer.includes('nvidia') || renderer.includes('amd') ||
12603
+ renderer.includes('radeon') || renderer.includes('geforce') || renderer.includes('quadro') ||
12604
+ renderer.includes('iris') || renderer.includes('uhd') || renderer.includes('hd graphics')) {
12605
+ return 0; // x86_64 - Desktop GPU
12606
+ }
12607
+ // ARM GPU detection (mobile and ARM devices)
12608
+ if (renderer.includes('mali') || renderer.includes('adreno') ||
12609
+ renderer.includes('powervr') || renderer.includes('tegra') ||
12610
+ renderer.includes('apple gpu') || vendor.includes('arm') ||
12611
+ renderer.includes('videocore')) {
12612
+ return 255; // ARM - Mobile/ARM GPU
12613
+ }
12614
+ }
12615
+ }
12616
+ }
12617
+ catch {
12618
+ // WebGL detection failed, continue
12619
+ }
12620
+ // Method 3: Fallback floating point NaN behavior (least reliable)
12621
+ try {
12622
+ const f = new Float32Array(1);
12623
+ const u8 = new Uint8Array(f.buffer);
12624
+ f[0] = Infinity;
12625
+ f[0] = f[0] - f[0]; // Should produce NaN
12626
+ const nanBehavior = u8[3];
12627
+ // Only trust this if platform detection was inconclusive
12628
+ // AND we have strong indicators from other methods
12629
+ if (platform.includes('linux') && nanBehavior === 255) {
12630
+ // Linux on ARM is less common, but validate with other signals
12631
+ return nanBehavior;
12632
+ }
12633
+ return nanBehavior;
12634
+ }
12635
+ catch {
12636
+ return undefined;
12637
+ }
12470
12638
  }
12471
12639
  catch {
12472
12640
  return undefined;