@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/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({
@@ -7406,7 +7458,11 @@
7406
7458
  enable: () => {
7407
7459
  this.config.heatmap = true;
7408
7460
  if (!this.heatmapEngine && this.isInitialized) {
7409
- 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;
7410
7466
  this.heatmapEngine = new HeatmapEngine(sessionId, {
7411
7467
  trackClicks: true,
7412
7468
  trackScrollDepth: true,
@@ -7453,7 +7509,11 @@
7453
7509
  enable: () => {
7454
7510
  this.config.replay = true;
7455
7511
  if (!this.replayEngine && this.isInitialized) {
7456
- 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;
7457
7517
  this.replayEngine = new SessionReplayEngine(sessionId, {
7458
7518
  sampleRate: this.config.replaySampling,
7459
7519
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7477,7 +7537,11 @@
7477
7537
  },
7478
7538
  start: () => {
7479
7539
  if (!this.replayEngine && this.isInitialized) {
7480
- 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;
7481
7545
  this.replayEngine = new SessionReplayEngine(sessionId, {
7482
7546
  sampleRate: 1.0, // Force recording when manually started
7483
7547
  maskSensitiveFields: this.config.replayMaskInputs,
@@ -7567,9 +7631,12 @@
7567
7631
  */
7568
7632
  initializeTrackingEngines() {
7569
7633
  try {
7570
- // Generate session ID if not exists
7634
+ // Generate session ID once and only once during initialization
7571
7635
  if (!this.sessionId) {
7572
7636
  this.sessionId = this.generateSessionId();
7637
+ if (this.config.debug) {
7638
+ console.log("[Zaplier] Generated session ID:", this.sessionId);
7639
+ }
7573
7640
  }
7574
7641
  // Initialize Session Replay Engine
7575
7642
  if (this.config.replay) {
@@ -7605,7 +7672,33 @@
7605
7672
  * Generate session ID
7606
7673
  */
7607
7674
  generateSessionId() {
7608
- 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);
7609
7702
  }
7610
7703
  /**
7611
7704
  * Initialize Anti-Adblock Manager
@@ -7673,7 +7766,7 @@
7673
7766
  }
7674
7767
  // Fallback: collect entropy for diagnostics only; do NOT set local visitorId
7675
7768
  try {
7676
- // Collect all available entropy sources
7769
+ // Collect stable entropy sources (no random values for consistency)
7677
7770
  const entropyComponents = [
7678
7771
  navigator.userAgent || "",
7679
7772
  navigator.language || "",
@@ -7681,44 +7774,40 @@
7681
7774
  screen.width || 0,
7682
7775
  screen.height || 0,
7683
7776
  screen.colorDepth || 0,
7684
- new Date().getTimezoneOffset(),
7685
- // Hardware-specific
7777
+ new Date(2024, 0, 1).getTimezoneOffset(),
7778
+ // Hardware-specific (stable values)
7686
7779
  navigator.hardwareConcurrency || 0,
7687
7780
  navigator.deviceMemory || 0,
7688
7781
  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,
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),
7700
7790
  // Connection info (if available)
7701
7791
  navigator.connection?.effectiveType || "",
7702
- navigator.connection?.downlink || Math.random() * 100,
7792
+ Math.floor(navigator.connection?.downlink || 0),
7703
7793
  // Additional browser-specific entropy
7704
7794
  window.outerHeight || 0,
7705
7795
  window.outerWidth || 0,
7706
- window.devicePixelRatio || 1,
7707
- // Session-specific entropy
7708
- 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,
7709
7799
  ];
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("");
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;
7718
7806
  }
7807
+ const deterministic = Math.abs(checksum).toString(36);
7719
7808
  // Advanced hash function with better distribution
7720
7809
  let hash = 0;
7721
- const finalString = entropyString + cryptoRandom;
7810
+ const finalString = entropyString + deterministic;
7722
7811
  for (let i = 0; i < finalString.length; i++) {
7723
7812
  const char = finalString.charCodeAt(i);
7724
7813
  hash = ((hash << 5) - hash + char) & 0xffffffff;
@@ -10634,8 +10723,8 @@
10634
10723
  return timezone;
10635
10724
  }
10636
10725
  }
10637
- // Fallback: timezone offset
10638
- 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();
10639
10728
  return `UTC${offset > 0 ? '-' : '+'}${Math.abs(offset / 60)}`;
10640
10729
  }
10641
10730
  catch {
@@ -12463,16 +12552,84 @@
12463
12552
  }
12464
12553
  /**
12465
12554
  * Get CPU architecture signature (ARM vs x86)
12466
- * Uses floating point NaN behavior to detect architecture
12555
+ * Uses multiple detection methods for maximum reliability
12467
12556
  */
12468
12557
  function getArchitecture() {
12469
12558
  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
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
+ }
12476
12633
  }
12477
12634
  catch {
12478
12635
  return undefined;