@zaplier/sdk 1.0.9 → 1.1.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.
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");
5130
- }
5131
- else {
5132
- failedComponents.push({
5133
- component: "screen",
5134
- error: "Collection failed or timeout",
5135
- });
5136
- }
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");
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);
5145
5179
  }
5146
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';
5147
5184
  failedComponents.push({
5148
- component: "browser",
5149
- error: "Collection failed or timeout",
5185
+ component: componentName,
5186
+ error: promiseResult.status === 'rejected'
5187
+ ? promiseResult.reason?.message || "Promise rejected"
5188
+ : "Collection failed or timeout",
5150
5189
  });
5151
5190
  }
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({
@@ -7400,7 +7452,11 @@ class ZaplierSDK {
7400
7452
  enable: () => {
7401
7453
  this.config.heatmap = true;
7402
7454
  if (!this.heatmapEngine && this.isInitialized) {
7403
- const sessionId = this.sessionId || this.generateSessionId();
7455
+ // Ensure sessionId is available (should be set during initialization)
7456
+ if (!this.sessionId) {
7457
+ this.sessionId = this.generateSessionId();
7458
+ }
7459
+ const sessionId = this.sessionId;
7404
7460
  this.heatmapEngine = new HeatmapEngine(sessionId, {
7405
7461
  trackClicks: true,
7406
7462
  trackScrollDepth: true,
@@ -7447,7 +7503,11 @@ class ZaplierSDK {
7447
7503
  enable: () => {
7448
7504
  this.config.replay = true;
7449
7505
  if (!this.replayEngine && this.isInitialized) {
7450
- const sessionId = this.sessionId || this.generateSessionId();
7506
+ // Ensure sessionId is available (should be set during initialization)
7507
+ if (!this.sessionId) {
7508
+ this.sessionId = this.generateSessionId();
7509
+ }
7510
+ const sessionId = this.sessionId;
7451
7511
  this.replayEngine = new SessionReplayEngine(sessionId, {
7452
7512
  sampleRate: this.config.replaySampling,
7453
7513
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7471,7 +7531,11 @@ class ZaplierSDK {
7471
7531
  },
7472
7532
  start: () => {
7473
7533
  if (!this.replayEngine && this.isInitialized) {
7474
- const sessionId = this.sessionId || this.generateSessionId();
7534
+ // Ensure sessionId is available (should be set during initialization)
7535
+ if (!this.sessionId) {
7536
+ this.sessionId = this.generateSessionId();
7537
+ }
7538
+ const sessionId = this.sessionId;
7475
7539
  this.replayEngine = new SessionReplayEngine(sessionId, {
7476
7540
  sampleRate: 1.0, // Force recording when manually started
7477
7541
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7561,9 +7625,12 @@ class ZaplierSDK {
7561
7625
  */
7562
7626
  initializeTrackingEngines() {
7563
7627
  try {
7564
- // Generate session ID if not exists
7628
+ // Generate session ID once and only once during initialization
7565
7629
  if (!this.sessionId) {
7566
7630
  this.sessionId = this.generateSessionId();
7631
+ if (this.config.debug) {
7632
+ console.log("[Zaplier] Generated session ID:", this.sessionId);
7633
+ }
7567
7634
  }
7568
7635
  // Initialize Session Replay Engine
7569
7636
  if (this.config.replay) {
@@ -7599,7 +7666,33 @@ class ZaplierSDK {
7599
7666
  * Generate session ID
7600
7667
  */
7601
7668
  generateSessionId() {
7602
- return Date.now().toString(36) + Math.random().toString(36).substr(2);
7669
+ // Create deterministic session ID based on page load time and stable browser characteristics
7670
+ const pageLoadTime = performance.timeOrigin || Date.now();
7671
+ const browserFingerprint = this.getBrowserFingerprint();
7672
+ // Use page load time + browser fingerprint for session uniqueness within same page load
7673
+ return pageLoadTime.toString(36) + browserFingerprint.substring(0, 8);
7674
+ }
7675
+ /**
7676
+ * Get stable browser characteristics for session ID generation
7677
+ * Uses only stable, privacy-safe values that don't change during session
7678
+ */
7679
+ getBrowserFingerprint() {
7680
+ const components = [
7681
+ navigator.userAgent || '',
7682
+ navigator.platform || '',
7683
+ screen.width || 0,
7684
+ screen.height || 0,
7685
+ new Date(2024, 0, 1).getTimezoneOffset(),
7686
+ navigator.language || '',
7687
+ navigator.hardwareConcurrency || 0
7688
+ ];
7689
+ const combined = components.join('|');
7690
+ let hash = 0;
7691
+ for (let i = 0; i < combined.length; i++) {
7692
+ const char = combined.charCodeAt(i);
7693
+ hash = ((hash << 5) - hash + char) & 0xffffffff;
7694
+ }
7695
+ return Math.abs(hash).toString(36);
7603
7696
  }
7604
7697
  /**
7605
7698
  * Initialize Anti-Adblock Manager
@@ -7667,7 +7760,7 @@ class ZaplierSDK {
7667
7760
  }
7668
7761
  // Fallback: collect entropy for diagnostics only; do NOT set local visitorId
7669
7762
  try {
7670
- // Collect all available entropy sources
7763
+ // Collect stable entropy sources (no random values for consistency)
7671
7764
  const entropyComponents = [
7672
7765
  navigator.userAgent || "",
7673
7766
  navigator.language || "",
@@ -7675,44 +7768,40 @@ class ZaplierSDK {
7675
7768
  screen.width || 0,
7676
7769
  screen.height || 0,
7677
7770
  screen.colorDepth || 0,
7678
- new Date().getTimezoneOffset(),
7679
- // Hardware-specific
7771
+ new Date(2024, 0, 1).getTimezoneOffset(),
7772
+ // Hardware-specific (stable values)
7680
7773
  navigator.hardwareConcurrency || 0,
7681
7774
  navigator.deviceMemory || 0,
7682
7775
  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,
7776
+ // Use page load time (stable during session) instead of current time
7777
+ performance.timeOrigin || 0,
7778
+ // Deterministic entropy from browser characteristics
7779
+ (navigator.plugins ? navigator.plugins.length : 0),
7780
+ (navigator.mimeTypes ? navigator.mimeTypes.length : 0),
7781
+ // Memory usage pattern (if available, use rounded values for stability)
7782
+ Math.floor((performance.memory?.usedJSHeapSize || 0) / 1000000),
7783
+ Math.floor((performance.memory?.totalJSHeapSize || 0) / 1000000),
7694
7784
  // Connection info (if available)
7695
7785
  navigator.connection?.effectiveType || "",
7696
- navigator.connection?.downlink || Math.random() * 100,
7786
+ Math.floor(navigator.connection?.downlink || 0),
7697
7787
  // Additional browser-specific entropy
7698
7788
  window.outerHeight || 0,
7699
7789
  window.outerWidth || 0,
7700
- window.devicePixelRatio || 1,
7701
- // Session-specific entropy
7702
- sessionStorage.length || Math.random() * 100,
7790
+ Math.floor((window.devicePixelRatio || 1) * 100) / 100, // Round to 2 decimals
7791
+ // Canvas support (deterministic)
7792
+ typeof document.createElement('canvas').getContext === 'function' ? 1 : 0,
7703
7793
  ];
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("");
7794
+ // Create deterministic entropy string
7795
+ const entropyString = entropyComponents.join("|");
7796
+ // Add deterministic checksum instead of random values
7797
+ let checksum = 0;
7798
+ for (let i = 0; i < entropyString.length; i++) {
7799
+ checksum = ((checksum << 5) - checksum + entropyString.charCodeAt(i)) & 0xffffffff;
7712
7800
  }
7801
+ const deterministic = Math.abs(checksum).toString(36);
7713
7802
  // Advanced hash function with better distribution
7714
7803
  let hash = 0;
7715
- const finalString = entropyString + cryptoRandom;
7804
+ const finalString = entropyString + deterministic;
7716
7805
  for (let i = 0; i < finalString.length; i++) {
7717
7806
  const char = finalString.charCodeAt(i);
7718
7807
  hash = ((hash << 5) - hash + char) & 0xffffffff;
@@ -10628,8 +10717,8 @@ function getTimezone() {
10628
10717
  return timezone;
10629
10718
  }
10630
10719
  }
10631
- // Fallback: timezone offset
10632
- const offset = new Date().getTimezoneOffset();
10720
+ // Fallback: timezone offset (STABLE - use standard time to avoid DST changes)
10721
+ const offset = new Date(2024, 0, 1).getTimezoneOffset();
10633
10722
  return `UTC${offset > 0 ? '-' : '+'}${Math.abs(offset / 60)}`;
10634
10723
  }
10635
10724
  catch {
@@ -12457,16 +12546,84 @@ function getScreenFrame() {
12457
12546
  }
12458
12547
  /**
12459
12548
  * Get CPU architecture signature (ARM vs x86)
12460
- * Uses floating point NaN behavior to detect architecture
12549
+ * Uses multiple detection methods for maximum reliability
12461
12550
  */
12462
12551
  function getArchitecture() {
12463
12552
  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
12553
+ // Method 1: Enhanced platform string detection (most reliable)
12554
+ const platform = navigator.platform?.toLowerCase() || '';
12555
+ const userAgent = navigator.userAgent?.toLowerCase() || '';
12556
+ // CRITICAL FIX: Prioritize desktop x86_64 detection first
12557
+ // Windows x64/x86 detection (highest priority for x86_64)
12558
+ if (platform.includes('win') && (userAgent.includes('wow64') || userAgent.includes('x64') || userAgent.includes('x86_64'))) {
12559
+ return 0; // x86_64 - Windows
12560
+ }
12561
+ // Linux x86_64 detection
12562
+ if (platform.includes('linux') && (platform.includes('x86_64') || userAgent.includes('x86_64'))) {
12563
+ return 0; // x86_64 - Linux
12564
+ }
12565
+ // MacOS Intel detection (before ARM check)
12566
+ if (platform.includes('mac') && (platform.includes('intel') || userAgent.includes('intel mac'))) {
12567
+ return 0; // x86_64 - Intel Mac
12568
+ }
12569
+ // Generic x86/x64 detection
12570
+ if (platform.includes('x86') || platform.includes('x64') ||
12571
+ platform.includes('intel') || platform.includes('amd64') ||
12572
+ userAgent.includes('x86') || userAgent.includes('amd64')) {
12573
+ return 0; // x86_64
12574
+ }
12575
+ // ARM detection (after x86_64 checks)
12576
+ if (platform.includes('arm') || userAgent.includes('arm') ||
12577
+ userAgent.includes('aarch64') || platform.includes('aarch64')) {
12578
+ return 255; // ARM
12579
+ }
12580
+ // Method 2: WebGL vendor detection (secondary check)
12581
+ try {
12582
+ const canvas = document.createElement('canvas');
12583
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
12584
+ if (gl && 'getExtension' in gl && 'getParameter' in gl) {
12585
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
12586
+ if (debugInfo) {
12587
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)?.toLowerCase() || '';
12588
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)?.toLowerCase() || '';
12589
+ // Enhanced x86_64 GPU detection (desktop GPUs)
12590
+ if (vendor.includes('intel') || vendor.includes('nvidia') || vendor.includes('amd') ||
12591
+ renderer.includes('intel') || renderer.includes('nvidia') || renderer.includes('amd') ||
12592
+ renderer.includes('radeon') || renderer.includes('geforce') || renderer.includes('quadro') ||
12593
+ renderer.includes('iris') || renderer.includes('uhd') || renderer.includes('hd graphics')) {
12594
+ return 0; // x86_64 - Desktop GPU
12595
+ }
12596
+ // ARM GPU detection (mobile and ARM devices)
12597
+ if (renderer.includes('mali') || renderer.includes('adreno') ||
12598
+ renderer.includes('powervr') || renderer.includes('tegra') ||
12599
+ renderer.includes('apple gpu') || vendor.includes('arm') ||
12600
+ renderer.includes('videocore')) {
12601
+ return 255; // ARM - Mobile/ARM GPU
12602
+ }
12603
+ }
12604
+ }
12605
+ }
12606
+ catch {
12607
+ // WebGL detection failed, continue
12608
+ }
12609
+ // Method 3: Fallback floating point NaN behavior (least reliable)
12610
+ try {
12611
+ const f = new Float32Array(1);
12612
+ const u8 = new Uint8Array(f.buffer);
12613
+ f[0] = Infinity;
12614
+ f[0] = f[0] - f[0]; // Should produce NaN
12615
+ const nanBehavior = u8[3];
12616
+ // Only trust this if platform detection was inconclusive
12617
+ // AND we have strong indicators from other methods
12618
+ if (platform.includes('linux') && nanBehavior === 255) {
12619
+ // Linux on ARM is less common, but validate with other signals
12620
+ return nanBehavior;
12621
+ }
12622
+ return nanBehavior;
12623
+ }
12624
+ catch {
12625
+ return undefined;
12626
+ }
12470
12627
  }
12471
12628
  catch {
12472
12629
  return undefined;