@zaplier/sdk 1.0.5 → 1.0.7

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
@@ -157,8 +157,12 @@
157
157
  h1 = x64Add(h1, h2);
158
158
  h2 = x64Add(h2, h1);
159
159
  // Convert to hex string
160
- const hex1 = h1[0].toString(16).padStart(8, "0") + h1[1].toString(16).padStart(8, "0");
161
- const hex2 = h2[0].toString(16).padStart(8, "0") + h2[1].toString(16).padStart(8, "0");
160
+ // CRITICAL: Use >>> 0 to convert to unsigned 32-bit integer before converting to hex
161
+ // This prevents negative numbers from generating hashes with minus signs (e.g., "-6b43973f")
162
+ const hex1 = (h1[0] >>> 0).toString(16).padStart(8, "0") +
163
+ (h1[1] >>> 0).toString(16).padStart(8, "0");
164
+ const hex2 = (h2[0] >>> 0).toString(16).padStart(8, "0") +
165
+ (h2[1] >>> 0).toString(16).padStart(8, "0");
162
166
  return hex1 + hex2;
163
167
  }
164
168
  /**
@@ -178,13 +182,26 @@
178
182
  /**
179
183
  * Hash fingerprint components into a stable identifier
180
184
  */
181
- function hashFingerprint(components) {
185
+ function hashFingerprint(components, debug = false) {
182
186
  // Convert components to canonical string representation using deep sorting
183
187
  // CRITICAL: Do NOT use JSON.stringify(obj, keys) as it filters out nested properties!
184
188
  // Use canonicalizeStable instead which handles deep sorting and normalization.
185
- const canonical = JSON.stringify(canonicalizeStable(components));
189
+ const canonicalized = canonicalizeStable(components);
190
+ const canonical = JSON.stringify(canonicalized);
191
+ if (debug) {
192
+ console.log("[RabbitTracker] hashFingerprint debug:", {
193
+ originalKeys: Object.keys(components),
194
+ canonicalizedKeys: Object.keys(canonicalized),
195
+ canonicalLength: canonical.length,
196
+ sample: canonical.substring(0, 200) + "...",
197
+ });
198
+ }
186
199
  // Use MurmurHash3 x64 for consistent hashing
187
- return x64hash128(canonical);
200
+ const hash = x64hash128(canonical);
201
+ if (debug) {
202
+ console.log("[RabbitTracker] Generated fingerprintHash:", hash.substring(0, 32));
203
+ }
204
+ return hash;
188
205
  }
189
206
  /**
190
207
  * Generate a visitor ID from fingerprint hash
@@ -193,8 +210,33 @@
193
210
  // Use first 16 characters of the hash for visitor ID
194
211
  return "vis_" + fingerprintHash.substring(0, 16);
195
212
  }
213
+ /**
214
+ * Check if a key represents an unstable component that should be excluded from hashing
215
+ */
216
+ function isUnstableKey(key) {
217
+ // Unstable keys that change between requests:
218
+ // - duration: collection time varies
219
+ // - timestamp: changes every request
220
+ // - error: error messages can vary
221
+ // - errors: error arrays can vary
222
+ // - collectionTime: varies per request
223
+ const unstableKeys = [
224
+ "duration",
225
+ "timestamp",
226
+ "error",
227
+ "errors",
228
+ "collectionTime",
229
+ "startTime",
230
+ "endTime",
231
+ "_timestamp", // internal timestamp fields
232
+ ];
233
+ return (unstableKeys.includes(key) ||
234
+ key.endsWith("_timestamp") ||
235
+ key.endsWith("Timestamp"));
236
+ }
196
237
  /**
197
238
  * Canonicalize an object deeply with sorted keys and normalized primitives
239
+ * CRITICAL: Filters out unstable components that vary between requests
198
240
  */
199
241
  function canonicalizeStable(input) {
200
242
  if (input === null || input === undefined)
@@ -209,6 +251,10 @@
209
251
  const keys = Object.keys(input).sort();
210
252
  const out = {};
211
253
  for (const k of keys) {
254
+ // CRITICAL: Skip unstable keys that cause hash instability
255
+ if (isUnstableKey(k)) {
256
+ continue;
257
+ }
212
258
  const v = canonicalizeStable(input[k]);
213
259
  if (v !== null)
214
260
  out[k] = v;
@@ -229,9 +275,22 @@
229
275
  /**
230
276
  * Compute stable core hash from a minimal, invariant vector
231
277
  */
232
- function hashStableCore(coreVector) {
233
- const canonical = JSON.stringify(canonicalizeStable(coreVector));
234
- return x64hash128(canonical);
278
+ function hashStableCore(coreVector, debug = false) {
279
+ const canonicalized = canonicalizeStable(coreVector);
280
+ const canonical = JSON.stringify(canonicalized);
281
+ if (debug) {
282
+ console.log("[RabbitTracker] hashStableCore debug:", {
283
+ originalKeys: Object.keys(coreVector),
284
+ canonicalizedKeys: Object.keys(canonicalized),
285
+ canonicalLength: canonical.length,
286
+ sample: canonical.substring(0, 200) + "...",
287
+ });
288
+ }
289
+ const hash = x64hash128(canonical);
290
+ if (debug) {
291
+ console.log("[RabbitTracker] Generated stableCoreHash:", hash.substring(0, 32));
292
+ }
293
+ return hash;
235
294
  }
236
295
 
237
296
  /**
@@ -4942,7 +5001,7 @@
4942
5001
  "system",
4943
5002
  ].includes(componentType);
4944
5003
  // Reduce retries for WebGL to prevent context leaks
4945
- const maxRetries = isWebGLComponent ? 2 : (isCriticalComponent ? 3 : 1);
5004
+ const maxRetries = isWebGLComponent ? 2 : isCriticalComponent ? 3 : 1;
4946
5005
  const retryDelays = isWebGLComponent ? [200, 500] : [100, 250, 500]; // Longer delays for WebGL
4947
5006
  for (let attempt = 0; attempt < maxRetries; attempt++) {
4948
5007
  try {
@@ -5574,7 +5633,9 @@
5574
5633
  // Fallback to legacy detection if imports fail
5575
5634
  const ua = uaStr;
5576
5635
  const m = ua.match(/(chrome|safari|firefox|edge|edg|brave|opera|opr|chromium)/i);
5577
- const family = (m?.[1] || ua.split(" ")[0] || "unknown").toLowerCase();
5636
+ const family = (m?.[1] ||
5637
+ ua.split(" ")[0] ||
5638
+ "unknown").toLowerCase();
5578
5639
  return family;
5579
5640
  }
5580
5641
  })(),
@@ -5582,8 +5643,10 @@
5582
5643
  timezone: browser?.timezone || "unknown",
5583
5644
  // PRIORITY 3: Primary language only (most stable language preference)
5584
5645
  // Remove secondary languages as they can change more frequently
5585
- primaryLanguage: Array.isArray(browser?.languages) && browser.languages.length > 0 && browser.languages[0]
5586
- ? browser.languages[0].toLowerCase().split('-')[0] // Only language code (en, pt, fr), not region
5646
+ primaryLanguage: Array.isArray(browser?.languages) &&
5647
+ browser.languages.length > 0 &&
5648
+ browser.languages[0]
5649
+ ? browser.languages[0].toLowerCase().split("-")[0] // Only language code (en, pt, fr), not region
5587
5650
  : "unknown",
5588
5651
  // PRIORITY 4: Platform (ultra-stable - OS doesn't change)
5589
5652
  platform: (browser?.platform || "unknown").toLowerCase(),
@@ -5691,11 +5754,24 @@
5691
5754
  // Simplified ratio for matching (rounded to prevent micro-variations)
5692
5755
  ratio: height > 0 ? Math.round((width / height) * 100) / 100 : 0,
5693
5756
  // Size category for general device classification
5694
- sizeCategory: width >= 2560 ? "large" : width >= 1920 ? "desktop" :
5695
- width >= 1024 ? "laptop" : width >= 768 ? "tablet" : "mobile"
5757
+ sizeCategory: width >= 2560
5758
+ ? "large"
5759
+ : width >= 1920
5760
+ ? "desktop"
5761
+ : width >= 1024
5762
+ ? "laptop"
5763
+ : width >= 768
5764
+ ? "tablet"
5765
+ : "mobile",
5696
5766
  };
5697
5767
  })()
5698
- : { bucket: "unknown", aspectClass: "unknown", densityClass: "unknown", ratio: 0, sizeCategory: "unknown" },
5768
+ : {
5769
+ bucket: "unknown",
5770
+ aspectClass: "unknown",
5771
+ densityClass: "unknown",
5772
+ ratio: 0,
5773
+ sizeCategory: "unknown",
5774
+ },
5699
5775
  // CRITICAL: Do NOT include availability flags (canvas/webgl/audio) in stableCoreVector
5700
5776
  // These flags can change between requests due to timeouts, permissions, or hardware issues
5701
5777
  // and would cause the stableCoreHash to change, breaking visitor identification
@@ -5806,10 +5882,13 @@
5806
5882
  arch: archBucket,
5807
5883
  class: getHardwareClass(coresBucket, memoryBucket),
5808
5884
  // Touch capability for mobile/desktop differentiation
5809
- touch: deviceSignals$1.touchSupport?.maxTouchPoints ?
5810
- (deviceSignals$1.touchSupport.maxTouchPoints >= 10 ? "multi" :
5811
- deviceSignals$1.touchSupport.maxTouchPoints >= 5 ? "standard" : "basic") :
5812
- "none"
5885
+ touch: deviceSignals$1.touchSupport?.maxTouchPoints
5886
+ ? deviceSignals$1.touchSupport.maxTouchPoints >= 10
5887
+ ? "multi"
5888
+ : deviceSignals$1.touchSupport.maxTouchPoints >= 5
5889
+ ? "standard"
5890
+ : "basic"
5891
+ : "none",
5813
5892
  };
5814
5893
  })(),
5815
5894
  // PRIORITY 8: Enhanced color and display capabilities bucketing
@@ -5835,10 +5914,14 @@
5835
5914
  if (!gamut)
5836
5915
  return "unknown";
5837
5916
  switch (gamut) {
5838
- case "rec2020": return "professional"; // Professional/HDR displays
5839
- case "p3": return "enhanced"; // Modern displays (Apple, etc.)
5840
- case "srgb": return "standard"; // Standard displays
5841
- default: return "unknown";
5917
+ case "rec2020":
5918
+ return "professional"; // Professional/HDR displays
5919
+ case "p3":
5920
+ return "enhanced"; // Modern displays (Apple, etc.)
5921
+ case "srgb":
5922
+ return "standard"; // Standard displays
5923
+ default:
5924
+ return "unknown";
5842
5925
  }
5843
5926
  };
5844
5927
  // Combined display quality classification
@@ -5862,7 +5945,7 @@
5862
5945
  return {
5863
5946
  depth: depthClass,
5864
5947
  gamut: gamutClass,
5865
- class: getDisplayClass(depthClass, gamutClass)
5948
+ class: getDisplayClass(depthClass, gamutClass),
5866
5949
  };
5867
5950
  })(),
5868
5951
  };
@@ -5881,7 +5964,9 @@
5881
5964
  // Add a simple checksum for additional uniqueness (deterministic)
5882
5965
  let checksum = 0;
5883
5966
  for (let i = 0; i < entropyString.length; i++) {
5884
- checksum = ((checksum << 5) - checksum + entropyString.charCodeAt(i)) & 0xffffffff;
5967
+ checksum =
5968
+ ((checksum << 5) - checksum + entropyString.charCodeAt(i)) &
5969
+ 0xffffffff;
5885
5970
  }
5886
5971
  coreVector._entropy = Math.abs(checksum).toString(36).substring(0, 8);
5887
5972
  }
@@ -5897,7 +5982,7 @@
5897
5982
  // - Vendor flavors (can change)
5898
5983
  // - Exact hardware values (use buckets)
5899
5984
  // Generate ultra-stable hash with additional collision resistance
5900
- const stableCoreHash = hashStableCore(coreVector);
5985
+ const stableCoreHash = hashStableCore(coreVector, opts.debug);
5901
5986
  // CRITICAL: Generate enhanced fingerprint hash combining unstable + stable components
5902
5987
  // This prevents collisions by adding stable entropy to the fingerprint hash
5903
5988
  const enhancedComponentValues = {
@@ -5906,7 +5991,7 @@
5906
5991
  // REMOVED: _hourlyEntropy - was causing visitor ID instability (same user = different IDs)
5907
5992
  };
5908
5993
  // Generate collision-resistant fingerprint hash
5909
- const enhancedHash = hashFingerprint(enhancedComponentValues);
5994
+ const enhancedHash = hashFingerprint(enhancedComponentValues, opts.debug);
5910
5995
  // Update finalData with the enhanced hash
5911
5996
  finalData.hash = enhancedHash;
5912
5997
  // Log stable core components for debugging collision issues