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