@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 +106 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +106 -25
- package/dist/index.esm.js.map +1 -1
- package/dist/sdk.js +106 -25
- package/dist/sdk.js.map +1 -1
- package/dist/sdk.min.js +1 -1
- package/dist/src/modules/fingerprint/hashing.d.ts +2 -2
- package/dist/src/modules/fingerprint/hashing.d.ts.map +1 -1
- package/dist/src/modules/fingerprint.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
232
|
-
|
|
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 :
|
|
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] ||
|
|
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) &&
|
|
5584
|
-
|
|
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
|
|
5693
|
-
|
|
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
|
-
: {
|
|
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
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
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":
|
|
5837
|
-
|
|
5838
|
-
case "
|
|
5839
|
-
|
|
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 =
|
|
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
|