@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/index.cjs +112 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +112 -27
- package/dist/index.esm.js.map +1 -1
- package/dist/sdk.js +112 -27
- 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
|
@@ -151,8 +151,12 @@ function x64hash128(input, seed = 0) {
|
|
|
151
151
|
h1 = x64Add(h1, h2);
|
|
152
152
|
h2 = x64Add(h2, h1);
|
|
153
153
|
// Convert to hex string
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
// CRITICAL: Use >>> 0 to convert to unsigned 32-bit integer before converting to hex
|
|
155
|
+
// This prevents negative numbers from generating hashes with minus signs (e.g., "-6b43973f")
|
|
156
|
+
const hex1 = (h1[0] >>> 0).toString(16).padStart(8, "0") +
|
|
157
|
+
(h1[1] >>> 0).toString(16).padStart(8, "0");
|
|
158
|
+
const hex2 = (h2[0] >>> 0).toString(16).padStart(8, "0") +
|
|
159
|
+
(h2[1] >>> 0).toString(16).padStart(8, "0");
|
|
156
160
|
return hex1 + hex2;
|
|
157
161
|
}
|
|
158
162
|
/**
|
|
@@ -172,13 +176,26 @@ function hash32(input) {
|
|
|
172
176
|
/**
|
|
173
177
|
* Hash fingerprint components into a stable identifier
|
|
174
178
|
*/
|
|
175
|
-
function hashFingerprint(components) {
|
|
179
|
+
function hashFingerprint(components, debug = false) {
|
|
176
180
|
// Convert components to canonical string representation using deep sorting
|
|
177
181
|
// CRITICAL: Do NOT use JSON.stringify(obj, keys) as it filters out nested properties!
|
|
178
182
|
// Use canonicalizeStable instead which handles deep sorting and normalization.
|
|
179
|
-
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
|
+
}
|
|
180
193
|
// Use MurmurHash3 x64 for consistent hashing
|
|
181
|
-
|
|
194
|
+
const hash = x64hash128(canonical);
|
|
195
|
+
if (debug) {
|
|
196
|
+
console.log("[RabbitTracker] Generated fingerprintHash:", hash.substring(0, 32));
|
|
197
|
+
}
|
|
198
|
+
return hash;
|
|
182
199
|
}
|
|
183
200
|
/**
|
|
184
201
|
* Generate a visitor ID from fingerprint hash
|
|
@@ -187,8 +204,33 @@ function generateVisitorId(fingerprintHash) {
|
|
|
187
204
|
// Use first 16 characters of the hash for visitor ID
|
|
188
205
|
return "vis_" + fingerprintHash.substring(0, 16);
|
|
189
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
|
+
}
|
|
190
231
|
/**
|
|
191
232
|
* Canonicalize an object deeply with sorted keys and normalized primitives
|
|
233
|
+
* CRITICAL: Filters out unstable components that vary between requests
|
|
192
234
|
*/
|
|
193
235
|
function canonicalizeStable(input) {
|
|
194
236
|
if (input === null || input === undefined)
|
|
@@ -203,6 +245,10 @@ function canonicalizeStable(input) {
|
|
|
203
245
|
const keys = Object.keys(input).sort();
|
|
204
246
|
const out = {};
|
|
205
247
|
for (const k of keys) {
|
|
248
|
+
// CRITICAL: Skip unstable keys that cause hash instability
|
|
249
|
+
if (isUnstableKey(k)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
206
252
|
const v = canonicalizeStable(input[k]);
|
|
207
253
|
if (v !== null)
|
|
208
254
|
out[k] = v;
|
|
@@ -223,9 +269,22 @@ function canonicalizeStable(input) {
|
|
|
223
269
|
/**
|
|
224
270
|
* Compute stable core hash from a minimal, invariant vector
|
|
225
271
|
*/
|
|
226
|
-
function hashStableCore(coreVector) {
|
|
227
|
-
const
|
|
228
|
-
|
|
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;
|
|
229
288
|
}
|
|
230
289
|
|
|
231
290
|
/**
|
|
@@ -4936,7 +4995,7 @@ async function collectComponent(componentType, collector, options) {
|
|
|
4936
4995
|
"system",
|
|
4937
4996
|
].includes(componentType);
|
|
4938
4997
|
// Reduce retries for WebGL to prevent context leaks
|
|
4939
|
-
const maxRetries = isWebGLComponent ? 2 :
|
|
4998
|
+
const maxRetries = isWebGLComponent ? 2 : isCriticalComponent ? 3 : 1;
|
|
4940
4999
|
const retryDelays = isWebGLComponent ? [200, 500] : [100, 250, 500]; // Longer delays for WebGL
|
|
4941
5000
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
4942
5001
|
try {
|
|
@@ -5568,7 +5627,9 @@ async function collectFingerprint(options = {}) {
|
|
|
5568
5627
|
// Fallback to legacy detection if imports fail
|
|
5569
5628
|
const ua = uaStr;
|
|
5570
5629
|
const m = ua.match(/(chrome|safari|firefox|edge|edg|brave|opera|opr|chromium)/i);
|
|
5571
|
-
const family = (m?.[1] ||
|
|
5630
|
+
const family = (m?.[1] ||
|
|
5631
|
+
ua.split(" ")[0] ||
|
|
5632
|
+
"unknown").toLowerCase();
|
|
5572
5633
|
return family;
|
|
5573
5634
|
}
|
|
5574
5635
|
})(),
|
|
@@ -5576,8 +5637,10 @@ async function collectFingerprint(options = {}) {
|
|
|
5576
5637
|
timezone: browser?.timezone || "unknown",
|
|
5577
5638
|
// PRIORITY 3: Primary language only (most stable language preference)
|
|
5578
5639
|
// Remove secondary languages as they can change more frequently
|
|
5579
|
-
primaryLanguage: Array.isArray(browser?.languages) &&
|
|
5580
|
-
|
|
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
|
|
5581
5644
|
: "unknown",
|
|
5582
5645
|
// PRIORITY 4: Platform (ultra-stable - OS doesn't change)
|
|
5583
5646
|
platform: (browser?.platform || "unknown").toLowerCase(),
|
|
@@ -5685,11 +5748,24 @@ async function collectFingerprint(options = {}) {
|
|
|
5685
5748
|
// Simplified ratio for matching (rounded to prevent micro-variations)
|
|
5686
5749
|
ratio: height > 0 ? Math.round((width / height) * 100) / 100 : 0,
|
|
5687
5750
|
// Size category for general device classification
|
|
5688
|
-
sizeCategory: width >= 2560
|
|
5689
|
-
|
|
5751
|
+
sizeCategory: width >= 2560
|
|
5752
|
+
? "large"
|
|
5753
|
+
: width >= 1920
|
|
5754
|
+
? "desktop"
|
|
5755
|
+
: width >= 1024
|
|
5756
|
+
? "laptop"
|
|
5757
|
+
: width >= 768
|
|
5758
|
+
? "tablet"
|
|
5759
|
+
: "mobile",
|
|
5690
5760
|
};
|
|
5691
5761
|
})()
|
|
5692
|
-
: {
|
|
5762
|
+
: {
|
|
5763
|
+
bucket: "unknown",
|
|
5764
|
+
aspectClass: "unknown",
|
|
5765
|
+
densityClass: "unknown",
|
|
5766
|
+
ratio: 0,
|
|
5767
|
+
sizeCategory: "unknown",
|
|
5768
|
+
},
|
|
5693
5769
|
// CRITICAL: Do NOT include availability flags (canvas/webgl/audio) in stableCoreVector
|
|
5694
5770
|
// These flags can change between requests due to timeouts, permissions, or hardware issues
|
|
5695
5771
|
// and would cause the stableCoreHash to change, breaking visitor identification
|
|
@@ -5800,10 +5876,13 @@ async function collectFingerprint(options = {}) {
|
|
|
5800
5876
|
arch: archBucket,
|
|
5801
5877
|
class: getHardwareClass(coresBucket, memoryBucket),
|
|
5802
5878
|
// Touch capability for mobile/desktop differentiation
|
|
5803
|
-
touch: deviceSignals$1.touchSupport?.maxTouchPoints
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
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",
|
|
5807
5886
|
};
|
|
5808
5887
|
})(),
|
|
5809
5888
|
// PRIORITY 8: Enhanced color and display capabilities bucketing
|
|
@@ -5829,10 +5908,14 @@ async function collectFingerprint(options = {}) {
|
|
|
5829
5908
|
if (!gamut)
|
|
5830
5909
|
return "unknown";
|
|
5831
5910
|
switch (gamut) {
|
|
5832
|
-
case "rec2020":
|
|
5833
|
-
|
|
5834
|
-
case "
|
|
5835
|
-
|
|
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";
|
|
5836
5919
|
}
|
|
5837
5920
|
};
|
|
5838
5921
|
// Combined display quality classification
|
|
@@ -5856,7 +5939,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5856
5939
|
return {
|
|
5857
5940
|
depth: depthClass,
|
|
5858
5941
|
gamut: gamutClass,
|
|
5859
|
-
class: getDisplayClass(depthClass, gamutClass)
|
|
5942
|
+
class: getDisplayClass(depthClass, gamutClass),
|
|
5860
5943
|
};
|
|
5861
5944
|
})(),
|
|
5862
5945
|
};
|
|
@@ -5875,7 +5958,9 @@ async function collectFingerprint(options = {}) {
|
|
|
5875
5958
|
// Add a simple checksum for additional uniqueness (deterministic)
|
|
5876
5959
|
let checksum = 0;
|
|
5877
5960
|
for (let i = 0; i < entropyString.length; i++) {
|
|
5878
|
-
checksum =
|
|
5961
|
+
checksum =
|
|
5962
|
+
((checksum << 5) - checksum + entropyString.charCodeAt(i)) &
|
|
5963
|
+
0xffffffff;
|
|
5879
5964
|
}
|
|
5880
5965
|
coreVector._entropy = Math.abs(checksum).toString(36).substring(0, 8);
|
|
5881
5966
|
}
|
|
@@ -5891,7 +5976,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5891
5976
|
// - Vendor flavors (can change)
|
|
5892
5977
|
// - Exact hardware values (use buckets)
|
|
5893
5978
|
// Generate ultra-stable hash with additional collision resistance
|
|
5894
|
-
const stableCoreHash = hashStableCore(coreVector);
|
|
5979
|
+
const stableCoreHash = hashStableCore(coreVector, opts.debug);
|
|
5895
5980
|
// CRITICAL: Generate enhanced fingerprint hash combining unstable + stable components
|
|
5896
5981
|
// This prevents collisions by adding stable entropy to the fingerprint hash
|
|
5897
5982
|
const enhancedComponentValues = {
|
|
@@ -5900,7 +5985,7 @@ async function collectFingerprint(options = {}) {
|
|
|
5900
5985
|
// REMOVED: _hourlyEntropy - was causing visitor ID instability (same user = different IDs)
|
|
5901
5986
|
};
|
|
5902
5987
|
// Generate collision-resistant fingerprint hash
|
|
5903
|
-
const enhancedHash = hashFingerprint(enhancedComponentValues);
|
|
5988
|
+
const enhancedHash = hashFingerprint(enhancedComponentValues, opts.debug);
|
|
5904
5989
|
// Update finalData with the enhanced hash
|
|
5905
5990
|
finalData.hash = enhancedHash;
|
|
5906
5991
|
// Log stable core components for debugging collision issues
|