@zaplier/sdk 1.0.8 → 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/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");
5136
- }
5137
- else {
5138
- failedComponents.push({
5139
- component: "screen",
5140
- error: "Collection failed or timeout",
5141
- });
5142
- }
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");
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);
5151
5185
  }
5152
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';
5153
5190
  failedComponents.push({
5154
- component: "browser",
5155
- error: "Collection failed or timeout",
5191
+ component: componentName,
5192
+ error: promiseResult.status === 'rejected'
5193
+ ? promiseResult.reason?.message || "Promise rejected"
5194
+ : "Collection failed or timeout",
5156
5195
  });
5157
5196
  }
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({
@@ -5652,7 +5704,9 @@
5652
5704
  platform: (browser?.platform || "unknown").toLowerCase(),
5653
5705
  // PRIORITY 5: Device type (critical for differentiation, ultra-stable)
5654
5706
  deviceType,
5655
- // PRIORITY 6: Enhanced screen resolution buckets with 2024 display trends
5707
+ // PRIORITY 6: Enhanced screen resolution with exact dimensions for mobile differentiation
5708
+ // For mobile devices, we NEED exact dimensions to differentiate between similar models (S22 vs A54)
5709
+ // For desktop, bucketing is sufficient as hardware varies more
5656
5710
  screen: screen
5657
5711
  ? (() => {
5658
5712
  // Use enhanced screen buckets for modern display landscape
@@ -5747,6 +5801,19 @@
5747
5801
  return "enhanced"; // Slightly enhanced DPI
5748
5802
  return "standard"; // Standard 1x displays
5749
5803
  };
5804
+ // CRITICAL: Do NOT include exact dimensions in stableCoreVector for mobile!
5805
+ // Reason: Screen dimensions change when:
5806
+ // - Chrome address bar appears/disappears (scroll behavior)
5807
+ // - Virtual keyboard opens/closes
5808
+ // - Screen rotates (portrait ↔ landscape)
5809
+ // - Browser zoom changes
5810
+ // These changes would cause stableCoreHash to change, breaking visitor identification
5811
+ //
5812
+ // INSTEAD: Use ONLY stable screen characteristics (bucket, aspect, density)
5813
+ // For device differentiation (S22 vs A54), use:
5814
+ // - screenFrame (bezel measurements)
5815
+ // - Combination of bucket + hardware + display
5816
+ // - Vector similarity in IdentityResolver
5750
5817
  return {
5751
5818
  bucket: getScreenBucket(width),
5752
5819
  aspectClass: getAspectRatioClass(width, height),
@@ -5763,6 +5830,11 @@
5763
5830
  : width >= 768
5764
5831
  ? "tablet"
5765
5832
  : "mobile",
5833
+ // ❌ REMOVED: exact dimensions (causes instability on mobile)
5834
+ // Device differentiation is now handled by:
5835
+ // 1. screenFrame (bezel measurements - stable)
5836
+ // 2. hardware + display combination
5837
+ // 3. Vector similarity in IdentityResolver
5766
5838
  };
5767
5839
  })()
5768
5840
  : {
@@ -5891,7 +5963,18 @@
5891
5963
  : "none",
5892
5964
  };
5893
5965
  })(),
5894
- // PRIORITY 8: Enhanced color and display capabilities bucketing
5966
+ // PRIORITY 8: Screen frame (bezel measurements) for mobile device differentiation
5967
+ // CRITICAL: This is STABLE across page loads and provides device-specific fingerprint
5968
+ // Samsung S22 vs A54 have different screen frames despite similar resolutions
5969
+ screenFrame: deviceSignals$1?.screenFrame
5970
+ ? {
5971
+ top: deviceSignals$1.screenFrame.top || 0,
5972
+ right: deviceSignals$1.screenFrame.right || 0,
5973
+ bottom: deviceSignals$1.screenFrame.bottom || 0,
5974
+ left: deviceSignals$1.screenFrame.left || 0,
5975
+ }
5976
+ : undefined,
5977
+ // PRIORITY 9: Enhanced color and display capabilities bucketing
5895
5978
  display: (() => {
5896
5979
  const depth = deviceSignals$1.colorDepth;
5897
5980
  const gamut = deviceSignals$1.colorGamut;
@@ -7375,7 +7458,11 @@
7375
7458
  enable: () => {
7376
7459
  this.config.heatmap = true;
7377
7460
  if (!this.heatmapEngine && this.isInitialized) {
7378
- const sessionId = this.sessionId || this.generateSessionId();
7461
+ // Ensure sessionId is available (should be set during initialization)
7462
+ if (!this.sessionId) {
7463
+ this.sessionId = this.generateSessionId();
7464
+ }
7465
+ const sessionId = this.sessionId;
7379
7466
  this.heatmapEngine = new HeatmapEngine(sessionId, {
7380
7467
  trackClicks: true,
7381
7468
  trackScrollDepth: true,
@@ -7422,7 +7509,11 @@
7422
7509
  enable: () => {
7423
7510
  this.config.replay = true;
7424
7511
  if (!this.replayEngine && this.isInitialized) {
7425
- const sessionId = this.sessionId || this.generateSessionId();
7512
+ // Ensure sessionId is available (should be set during initialization)
7513
+ if (!this.sessionId) {
7514
+ this.sessionId = this.generateSessionId();
7515
+ }
7516
+ const sessionId = this.sessionId;
7426
7517
  this.replayEngine = new SessionReplayEngine(sessionId, {
7427
7518
  sampleRate: this.config.replaySampling,
7428
7519
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7446,7 +7537,11 @@
7446
7537
  },
7447
7538
  start: () => {
7448
7539
  if (!this.replayEngine && this.isInitialized) {
7449
- const sessionId = this.sessionId || this.generateSessionId();
7540
+ // Ensure sessionId is available (should be set during initialization)
7541
+ if (!this.sessionId) {
7542
+ this.sessionId = this.generateSessionId();
7543
+ }
7544
+ const sessionId = this.sessionId;
7450
7545
  this.replayEngine = new SessionReplayEngine(sessionId, {
7451
7546
  sampleRate: 1.0, // Force recording when manually started
7452
7547
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7536,9 +7631,12 @@
7536
7631
  */
7537
7632
  initializeTrackingEngines() {
7538
7633
  try {
7539
- // Generate session ID if not exists
7634
+ // Generate session ID once and only once during initialization
7540
7635
  if (!this.sessionId) {
7541
7636
  this.sessionId = this.generateSessionId();
7637
+ if (this.config.debug) {
7638
+ console.log("[Zaplier] Generated session ID:", this.sessionId);
7639
+ }
7542
7640
  }
7543
7641
  // Initialize Session Replay Engine
7544
7642
  if (this.config.replay) {
@@ -7574,7 +7672,33 @@
7574
7672
  * Generate session ID
7575
7673
  */
7576
7674
  generateSessionId() {
7577
- return Date.now().toString(36) + Math.random().toString(36).substr(2);
7675
+ // Create deterministic session ID based on page load time and stable browser characteristics
7676
+ const pageLoadTime = performance.timeOrigin || Date.now();
7677
+ const browserFingerprint = this.getBrowserFingerprint();
7678
+ // Use page load time + browser fingerprint for session uniqueness within same page load
7679
+ return pageLoadTime.toString(36) + browserFingerprint.substring(0, 8);
7680
+ }
7681
+ /**
7682
+ * Get stable browser characteristics for session ID generation
7683
+ * Uses only stable, privacy-safe values that don't change during session
7684
+ */
7685
+ getBrowserFingerprint() {
7686
+ const components = [
7687
+ navigator.userAgent || '',
7688
+ navigator.platform || '',
7689
+ screen.width || 0,
7690
+ screen.height || 0,
7691
+ new Date(2024, 0, 1).getTimezoneOffset(),
7692
+ navigator.language || '',
7693
+ navigator.hardwareConcurrency || 0
7694
+ ];
7695
+ const combined = components.join('|');
7696
+ let hash = 0;
7697
+ for (let i = 0; i < combined.length; i++) {
7698
+ const char = combined.charCodeAt(i);
7699
+ hash = ((hash << 5) - hash + char) & 0xffffffff;
7700
+ }
7701
+ return Math.abs(hash).toString(36);
7578
7702
  }
7579
7703
  /**
7580
7704
  * Initialize Anti-Adblock Manager
@@ -7642,7 +7766,7 @@
7642
7766
  }
7643
7767
  // Fallback: collect entropy for diagnostics only; do NOT set local visitorId
7644
7768
  try {
7645
- // Collect all available entropy sources
7769
+ // Collect stable entropy sources (no random values for consistency)
7646
7770
  const entropyComponents = [
7647
7771
  navigator.userAgent || "",
7648
7772
  navigator.language || "",
@@ -7650,44 +7774,40 @@
7650
7774
  screen.width || 0,
7651
7775
  screen.height || 0,
7652
7776
  screen.colorDepth || 0,
7653
- new Date().getTimezoneOffset(),
7654
- // Hardware-specific
7777
+ new Date(2024, 0, 1).getTimezoneOffset(),
7778
+ // Hardware-specific (stable values)
7655
7779
  navigator.hardwareConcurrency || 0,
7656
7780
  navigator.deviceMemory || 0,
7657
7781
  navigator.maxTouchPoints || 0,
7658
- // High-resolution timestamps for uniqueness
7659
- performance.now(),
7660
- Date.now(),
7661
- // Random component to ensure uniqueness
7662
- Math.random() * 1000000,
7663
- Math.random() * 1000000, // Double random for extra entropy
7664
- // Memory usage pattern (if available)
7665
- performance.memory?.usedJSHeapSize ||
7666
- Math.random() * 1000000,
7667
- performance.memory?.totalJSHeapSize ||
7668
- Math.random() * 1000000,
7782
+ // Use page load time (stable during session) instead of current time
7783
+ performance.timeOrigin || 0,
7784
+ // Deterministic entropy from browser characteristics
7785
+ (navigator.plugins ? navigator.plugins.length : 0),
7786
+ (navigator.mimeTypes ? navigator.mimeTypes.length : 0),
7787
+ // Memory usage pattern (if available, use rounded values for stability)
7788
+ Math.floor((performance.memory?.usedJSHeapSize || 0) / 1000000),
7789
+ Math.floor((performance.memory?.totalJSHeapSize || 0) / 1000000),
7669
7790
  // Connection info (if available)
7670
7791
  navigator.connection?.effectiveType || "",
7671
- navigator.connection?.downlink || Math.random() * 100,
7792
+ Math.floor(navigator.connection?.downlink || 0),
7672
7793
  // Additional browser-specific entropy
7673
7794
  window.outerHeight || 0,
7674
7795
  window.outerWidth || 0,
7675
- window.devicePixelRatio || 1,
7676
- // Session-specific entropy
7677
- sessionStorage.length || Math.random() * 100,
7796
+ Math.floor((window.devicePixelRatio || 1) * 100) / 100, // Round to 2 decimals
7797
+ // Canvas support (deterministic)
7798
+ typeof document.createElement('canvas').getContext === 'function' ? 1 : 0,
7678
7799
  ];
7679
- // Create high-entropy string
7680
- const entropyString = entropyComponents.join("|") + "|" + Math.random().toString(36);
7681
- // Use crypto.getRandomValues if available for extra randomness
7682
- let cryptoRandom = "";
7683
- if (window.crypto && window.crypto.getRandomValues) {
7684
- const array = new Uint32Array(4);
7685
- window.crypto.getRandomValues(array);
7686
- cryptoRandom = Array.from(array).join("");
7800
+ // Create deterministic entropy string
7801
+ const entropyString = entropyComponents.join("|");
7802
+ // Add deterministic checksum instead of random values
7803
+ let checksum = 0;
7804
+ for (let i = 0; i < entropyString.length; i++) {
7805
+ checksum = ((checksum << 5) - checksum + entropyString.charCodeAt(i)) & 0xffffffff;
7687
7806
  }
7807
+ const deterministic = Math.abs(checksum).toString(36);
7688
7808
  // Advanced hash function with better distribution
7689
7809
  let hash = 0;
7690
- const finalString = entropyString + cryptoRandom;
7810
+ const finalString = entropyString + deterministic;
7691
7811
  for (let i = 0; i < finalString.length; i++) {
7692
7812
  const char = finalString.charCodeAt(i);
7693
7813
  hash = ((hash << 5) - hash + char) & 0xffffffff;
@@ -10603,8 +10723,8 @@
10603
10723
  return timezone;
10604
10724
  }
10605
10725
  }
10606
- // Fallback: timezone offset
10607
- const offset = new Date().getTimezoneOffset();
10726
+ // Fallback: timezone offset (STABLE - use standard time to avoid DST changes)
10727
+ const offset = new Date(2024, 0, 1).getTimezoneOffset();
10608
10728
  return `UTC${offset > 0 ? '-' : '+'}${Math.abs(offset / 60)}`;
10609
10729
  }
10610
10730
  catch {
@@ -12432,16 +12552,84 @@
12432
12552
  }
12433
12553
  /**
12434
12554
  * Get CPU architecture signature (ARM vs x86)
12435
- * Uses floating point NaN behavior to detect architecture
12555
+ * Uses multiple detection methods for maximum reliability
12436
12556
  */
12437
12557
  function getArchitecture() {
12438
12558
  try {
12439
- // x86/x86-64 sets sign bit on NaN from infinity subtraction
12440
- const f = new Float32Array(1);
12441
- const u8 = new Uint8Array(f.buffer);
12442
- f[0] = Infinity;
12443
- f[0] = f[0] - f[0]; // Should produce NaN
12444
- return u8[3]; // Sign bit in byte 3
12559
+ // Method 1: Enhanced platform string detection (most reliable)
12560
+ const platform = navigator.platform?.toLowerCase() || '';
12561
+ const userAgent = navigator.userAgent?.toLowerCase() || '';
12562
+ // CRITICAL FIX: Prioritize desktop x86_64 detection first
12563
+ // Windows x64/x86 detection (highest priority for x86_64)
12564
+ if (platform.includes('win') && (userAgent.includes('wow64') || userAgent.includes('x64') || userAgent.includes('x86_64'))) {
12565
+ return 0; // x86_64 - Windows
12566
+ }
12567
+ // Linux x86_64 detection
12568
+ if (platform.includes('linux') && (platform.includes('x86_64') || userAgent.includes('x86_64'))) {
12569
+ return 0; // x86_64 - Linux
12570
+ }
12571
+ // MacOS Intel detection (before ARM check)
12572
+ if (platform.includes('mac') && (platform.includes('intel') || userAgent.includes('intel mac'))) {
12573
+ return 0; // x86_64 - Intel Mac
12574
+ }
12575
+ // Generic x86/x64 detection
12576
+ if (platform.includes('x86') || platform.includes('x64') ||
12577
+ platform.includes('intel') || platform.includes('amd64') ||
12578
+ userAgent.includes('x86') || userAgent.includes('amd64')) {
12579
+ return 0; // x86_64
12580
+ }
12581
+ // ARM detection (after x86_64 checks)
12582
+ if (platform.includes('arm') || userAgent.includes('arm') ||
12583
+ userAgent.includes('aarch64') || platform.includes('aarch64')) {
12584
+ return 255; // ARM
12585
+ }
12586
+ // Method 2: WebGL vendor detection (secondary check)
12587
+ try {
12588
+ const canvas = document.createElement('canvas');
12589
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
12590
+ if (gl && 'getExtension' in gl && 'getParameter' in gl) {
12591
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
12592
+ if (debugInfo) {
12593
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)?.toLowerCase() || '';
12594
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)?.toLowerCase() || '';
12595
+ // Enhanced x86_64 GPU detection (desktop GPUs)
12596
+ if (vendor.includes('intel') || vendor.includes('nvidia') || vendor.includes('amd') ||
12597
+ renderer.includes('intel') || renderer.includes('nvidia') || renderer.includes('amd') ||
12598
+ renderer.includes('radeon') || renderer.includes('geforce') || renderer.includes('quadro') ||
12599
+ renderer.includes('iris') || renderer.includes('uhd') || renderer.includes('hd graphics')) {
12600
+ return 0; // x86_64 - Desktop GPU
12601
+ }
12602
+ // ARM GPU detection (mobile and ARM devices)
12603
+ if (renderer.includes('mali') || renderer.includes('adreno') ||
12604
+ renderer.includes('powervr') || renderer.includes('tegra') ||
12605
+ renderer.includes('apple gpu') || vendor.includes('arm') ||
12606
+ renderer.includes('videocore')) {
12607
+ return 255; // ARM - Mobile/ARM GPU
12608
+ }
12609
+ }
12610
+ }
12611
+ }
12612
+ catch {
12613
+ // WebGL detection failed, continue
12614
+ }
12615
+ // Method 3: Fallback floating point NaN behavior (least reliable)
12616
+ try {
12617
+ const f = new Float32Array(1);
12618
+ const u8 = new Uint8Array(f.buffer);
12619
+ f[0] = Infinity;
12620
+ f[0] = f[0] - f[0]; // Should produce NaN
12621
+ const nanBehavior = u8[3];
12622
+ // Only trust this if platform detection was inconclusive
12623
+ // AND we have strong indicators from other methods
12624
+ if (platform.includes('linux') && nanBehavior === 255) {
12625
+ // Linux on ARM is less common, but validate with other signals
12626
+ return nanBehavior;
12627
+ }
12628
+ return nanBehavior;
12629
+ }
12630
+ catch {
12631
+ return undefined;
12632
+ }
12445
12633
  }
12446
12634
  catch {
12447
12635
  return undefined;