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