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