@zaplier/sdk 1.0.4 → 1.0.6

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