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