@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.d.ts
CHANGED
|
@@ -1289,7 +1289,7 @@ declare function hash32(input: string): string;
|
|
|
1289
1289
|
/**
|
|
1290
1290
|
* Hash fingerprint components into a stable identifier
|
|
1291
1291
|
*/
|
|
1292
|
-
declare function hashFingerprint(components: Record<string, any
|
|
1292
|
+
declare function hashFingerprint(components: Record<string, any>, debug?: boolean): string;
|
|
1293
1293
|
/**
|
|
1294
1294
|
* Generate a visitor ID from fingerprint hash
|
|
1295
1295
|
*/
|
package/dist/index.esm.js
CHANGED
|
@@ -172,13 +172,26 @@ function hash32(input) {
|
|
|
172
172
|
/**
|
|
173
173
|
* Hash fingerprint components into a stable identifier
|
|
174
174
|
*/
|
|
175
|
-
function hashFingerprint(components) {
|
|
175
|
+
function hashFingerprint(components, debug = false) {
|
|
176
176
|
// Convert components to canonical string representation using deep sorting
|
|
177
177
|
// CRITICAL: Do NOT use JSON.stringify(obj, keys) as it filters out nested properties!
|
|
178
178
|
// Use canonicalizeStable instead which handles deep sorting and normalization.
|
|
179
|
-
const
|
|
179
|
+
const canonicalized = canonicalizeStable(components);
|
|
180
|
+
const canonical = JSON.stringify(canonicalized);
|
|
181
|
+
if (debug) {
|
|
182
|
+
console.log("[RabbitTracker] hashFingerprint debug:", {
|
|
183
|
+
originalKeys: Object.keys(components),
|
|
184
|
+
canonicalizedKeys: Object.keys(canonicalized),
|
|
185
|
+
canonicalLength: canonical.length,
|
|
186
|
+
sample: canonical.substring(0, 200) + "...",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
180
189
|
// Use MurmurHash3 x64 for consistent hashing
|
|
181
|
-
|
|
190
|
+
const hash = x64hash128(canonical);
|
|
191
|
+
if (debug) {
|
|
192
|
+
console.log("[RabbitTracker] Generated fingerprintHash:", hash.substring(0, 32));
|
|
193
|
+
}
|
|
194
|
+
return hash;
|
|
182
195
|
}
|
|
183
196
|
/**
|
|
184
197
|
* Generate a visitor ID from fingerprint hash
|
|
@@ -187,8 +200,33 @@ function generateVisitorId(fingerprintHash) {
|
|
|
187
200
|
// Use first 16 characters of the hash for visitor ID
|
|
188
201
|
return "vis_" + fingerprintHash.substring(0, 16);
|
|
189
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Check if a key represents an unstable component that should be excluded from hashing
|
|
205
|
+
*/
|
|
206
|
+
function isUnstableKey(key) {
|
|
207
|
+
// Unstable keys that change between requests:
|
|
208
|
+
// - duration: collection time varies
|
|
209
|
+
// - timestamp: changes every request
|
|
210
|
+
// - error: error messages can vary
|
|
211
|
+
// - errors: error arrays can vary
|
|
212
|
+
// - collectionTime: varies per request
|
|
213
|
+
const unstableKeys = [
|
|
214
|
+
"duration",
|
|
215
|
+
"timestamp",
|
|
216
|
+
"error",
|
|
217
|
+
"errors",
|
|
218
|
+
"collectionTime",
|
|
219
|
+
"startTime",
|
|
220
|
+
"endTime",
|
|
221
|
+
"_timestamp", // internal timestamp fields
|
|
222
|
+
];
|
|
223
|
+
return (unstableKeys.includes(key) ||
|
|
224
|
+
key.endsWith("_timestamp") ||
|
|
225
|
+
key.endsWith("Timestamp"));
|
|
226
|
+
}
|
|
190
227
|
/**
|
|
191
228
|
* Canonicalize an object deeply with sorted keys and normalized primitives
|
|
229
|
+
* CRITICAL: Filters out unstable components that vary between requests
|
|
192
230
|
*/
|
|
193
231
|
function canonicalizeStable(input) {
|
|
194
232
|
if (input === null || input === undefined)
|
|
@@ -203,6 +241,10 @@ function canonicalizeStable(input) {
|
|
|
203
241
|
const keys = Object.keys(input).sort();
|
|
204
242
|
const out = {};
|
|
205
243
|
for (const k of keys) {
|
|
244
|
+
// CRITICAL: Skip unstable keys that cause hash instability
|
|
245
|
+
if (isUnstableKey(k)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
206
248
|
const v = canonicalizeStable(input[k]);
|
|
207
249
|
if (v !== null)
|
|
208
250
|
out[k] = v;
|
|
@@ -223,9 +265,22 @@ function canonicalizeStable(input) {
|
|
|
223
265
|
/**
|
|
224
266
|
* Compute stable core hash from a minimal, invariant vector
|
|
225
267
|
*/
|
|
226
|
-
function hashStableCore(coreVector) {
|
|
227
|
-
const
|
|
228
|
-
|
|
268
|
+
function hashStableCore(coreVector, debug = false) {
|
|
269
|
+
const canonicalized = canonicalizeStable(coreVector);
|
|
270
|
+
const canonical = JSON.stringify(canonicalized);
|
|
271
|
+
if (debug) {
|
|
272
|
+
console.log("[RabbitTracker] hashStableCore debug:", {
|
|
273
|
+
originalKeys: Object.keys(coreVector),
|
|
274
|
+
canonicalizedKeys: Object.keys(canonicalized),
|
|
275
|
+
canonicalLength: canonical.length,
|
|
276
|
+
sample: canonical.substring(0, 200) + "...",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const hash = x64hash128(canonical);
|
|
280
|
+
if (debug) {
|
|
281
|
+
console.log("[RabbitTracker] Generated stableCoreHash:", hash.substring(0, 32));
|
|
282
|
+
}
|
|
283
|
+
return hash;
|
|
229
284
|
}
|
|
230
285
|
|
|
231
286
|
/**
|
|
@@ -4936,7 +4991,7 @@ async function collectComponent(componentType, collector, options) {
|
|
|
4936
4991
|
"system",
|
|
4937
4992
|
].includes(componentType);
|
|
4938
4993
|
// Reduce retries for WebGL to prevent context leaks
|
|
4939
|
-
const maxRetries = isWebGLComponent ? 2 :
|
|
4994
|
+
const maxRetries = isWebGLComponent ? 2 : isCriticalComponent ? 3 : 1;
|
|
4940
4995
|
const retryDelays = isWebGLComponent ? [200, 500] : [100, 250, 500]; // Longer delays for WebGL
|
|
4941
4996
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
4942
4997
|
try {
|
|
@@ -5568,7 +5623,9 @@ async function collectFingerprint(options = {}) {
|
|
|
5568
5623
|
// Fallback to legacy detection if imports fail
|
|
5569
5624
|
const ua = uaStr;
|
|
5570
5625
|
const m = ua.match(/(chrome|safari|firefox|edge|edg|brave|opera|opr|chromium)/i);
|
|
5571
|
-
const family = (m?.[1] ||
|
|
5626
|
+
const family = (m?.[1] ||
|
|
5627
|
+
ua.split(" ")[0] ||
|
|
5628
|
+
"unknown").toLowerCase();
|
|
5572
5629
|
return family;
|
|
5573
5630
|
}
|
|
5574
5631
|
})(),
|
|
@@ -5576,8 +5633,10 @@ async function collectFingerprint(options = {}) {
|
|
|
5576
5633
|
timezone: browser?.timezone || "unknown",
|
|
5577
5634
|
// PRIORITY 3: Primary language only (most stable language preference)
|
|
5578
5635
|
// Remove secondary languages as they can change more frequently
|
|
5579
|
-
primaryLanguage: Array.isArray(browser?.languages) &&
|
|
5580
|
-
|
|
5636
|
+
primaryLanguage: Array.isArray(browser?.languages) &&
|
|
5637
|
+
browser.languages.length > 0 &&
|
|
5638
|
+
browser.languages[0]
|
|
5639
|
+
? browser.languages[0].toLowerCase().split("-")[0] // Only language code (en, pt, fr), not region
|
|
5581
5640
|
: "unknown",
|
|
5582
5641
|
// PRIORITY 4: Platform (ultra-stable - OS doesn't change)
|
|
5583
5642
|
platform: (browser?.platform || "unknown").toLowerCase(),
|
|
@@ -5685,11 +5744,24 @@ async function collectFingerprint(options = {}) {
|
|
|
5685
5744
|
// Simplified ratio for matching (rounded to prevent micro-variations)
|
|
5686
5745
|
ratio: height > 0 ? Math.round((width / height) * 100) / 100 : 0,
|
|
5687
5746
|
// Size category for general device classification
|
|
5688
|
-
sizeCategory: width >= 2560
|
|
5689
|
-
|
|
5747
|
+
sizeCategory: width >= 2560
|
|
5748
|
+
? "large"
|
|
5749
|
+
: width >= 1920
|
|
5750
|
+
? "desktop"
|
|
5751
|
+
: width >= 1024
|
|
5752
|
+
? "laptop"
|
|
5753
|
+
: width >= 768
|
|
5754
|
+
? "tablet"
|
|
5755
|
+
: "mobile",
|
|
5690
5756
|
};
|
|
5691
5757
|
})()
|
|
5692
|
-
: {
|
|
5758
|
+
: {
|
|
5759
|
+
bucket: "unknown",
|
|
5760
|
+
aspectClass: "unknown",
|
|
5761
|
+
densityClass: "unknown",
|
|
5762
|
+
ratio: 0,
|
|
5763
|
+
sizeCategory: "unknown",
|
|
5764
|
+
},
|
|
5693
5765
|
// CRITICAL: Do NOT include availability flags (canvas/webgl/audio) in stableCoreVector
|
|
5694
5766
|
// These flags can change between requests due to timeouts, permissions, or hardware issues
|
|
5695
5767
|
// and would cause the stableCoreHash to change, breaking visitor identification
|
|
@@ -5800,10 +5872,13 @@ async function collectFingerprint(options = {}) {
|
|
|
5800
5872
|
arch: archBucket,
|
|
5801
5873
|
class: getHardwareClass(coresBucket, memoryBucket),
|
|
5802
5874
|
// Touch capability for mobile/desktop differentiation
|
|
5803
|
-
touch: deviceSignals$1.touchSupport?.maxTouchPoints
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5875
|
+
touch: deviceSignals$1.touchSupport?.maxTouchPoints
|
|
5876
|
+
? deviceSignals$1.touchSupport.maxTouchPoints >= 10
|
|
5877
|
+
? "multi"
|
|
5878
|
+
: deviceSignals$1.touchSupport.maxTouchPoints >= 5
|
|
5879
|
+
? "standard"
|
|
5880
|
+
: "basic"
|
|
5881
|
+
: "none",
|
|
5807
5882
|
};
|
|
5808
5883
|
})(),
|
|
5809
5884
|
// PRIORITY 8: Enhanced color and display capabilities bucketing
|
|
@@ -5829,10 +5904,14 @@ async function collectFingerprint(options = {}) {
|
|
|
5829
5904
|
if (!gamut)
|
|
5830
5905
|
return "unknown";
|
|
5831
5906
|
switch (gamut) {
|
|
5832
|
-
case "rec2020":
|
|
5833
|
-
|
|
5834
|
-
case "
|
|
5835
|
-
|
|
5907
|
+
case "rec2020":
|
|
5908
|
+
return "professional"; // Professional/HDR displays
|
|
5909
|
+
case "p3":
|
|
5910
|
+
return "enhanced"; // Modern displays (Apple, etc.)
|
|
5911
|
+
case "srgb":
|
|
5912
|
+
return "standard"; // Standard displays
|
|
5913
|
+
default:
|
|
5914
|
+
return "unknown";
|
|
5836
5915
|
}
|
|
5837
5916
|
};
|
|
5838
5917
|
// Combined display quality classification
|
|
@@ -5856,7 +5935,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5856
5935
|
return {
|
|
5857
5936
|
depth: depthClass,
|
|
5858
5937
|
gamut: gamutClass,
|
|
5859
|
-
class: getDisplayClass(depthClass, gamutClass)
|
|
5938
|
+
class: getDisplayClass(depthClass, gamutClass),
|
|
5860
5939
|
};
|
|
5861
5940
|
})(),
|
|
5862
5941
|
};
|
|
@@ -5875,7 +5954,9 @@ async function collectFingerprint(options = {}) {
|
|
|
5875
5954
|
// Add a simple checksum for additional uniqueness (deterministic)
|
|
5876
5955
|
let checksum = 0;
|
|
5877
5956
|
for (let i = 0; i < entropyString.length; i++) {
|
|
5878
|
-
checksum =
|
|
5957
|
+
checksum =
|
|
5958
|
+
((checksum << 5) - checksum + entropyString.charCodeAt(i)) &
|
|
5959
|
+
0xffffffff;
|
|
5879
5960
|
}
|
|
5880
5961
|
coreVector._entropy = Math.abs(checksum).toString(36).substring(0, 8);
|
|
5881
5962
|
}
|
|
@@ -5891,7 +5972,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5891
5972
|
// - Vendor flavors (can change)
|
|
5892
5973
|
// - Exact hardware values (use buckets)
|
|
5893
5974
|
// Generate ultra-stable hash with additional collision resistance
|
|
5894
|
-
const stableCoreHash = hashStableCore(coreVector);
|
|
5975
|
+
const stableCoreHash = hashStableCore(coreVector, opts.debug);
|
|
5895
5976
|
// CRITICAL: Generate enhanced fingerprint hash combining unstable + stable components
|
|
5896
5977
|
// This prevents collisions by adding stable entropy to the fingerprint hash
|
|
5897
5978
|
const enhancedComponentValues = {
|
|
@@ -5900,7 +5981,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5900
5981
|
// REMOVED: _hourlyEntropy - was causing visitor ID instability (same user = different IDs)
|
|
5901
5982
|
};
|
|
5902
5983
|
// Generate collision-resistant fingerprint hash
|
|
5903
|
-
const enhancedHash = hashFingerprint(enhancedComponentValues);
|
|
5984
|
+
const enhancedHash = hashFingerprint(enhancedComponentValues, opts.debug);
|
|
5904
5985
|
// Update finalData with the enhanced hash
|
|
5905
5986
|
finalData.hash = enhancedHash;
|
|
5906
5987
|
// Log stable core components for debugging collision issues
|