@zaplier/sdk 1.0.8 → 1.1.0
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 +330 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.js +330 -142
- package/dist/index.esm.js.map +1 -1
- package/dist/sdk.js +330 -142
- package/dist/sdk.js.map +1 -1
- package/dist/sdk.min.js +1 -1
- package/dist/src/modules/fingerprint/browser.d.ts.map +1 -1
- package/dist/src/modules/fingerprint/canvas.d.ts.map +1 -1
- package/dist/src/modules/fingerprint/device-signals.d.ts.map +1 -1
- package/dist/src/modules/fingerprint/hashing.d.ts.map +1 -1
- package/dist/src/modules/fingerprint/webgl.d.ts.map +1 -1
- package/dist/src/modules/fingerprint.d.ts.map +1 -1
- package/dist/src/sdk.d.ts +5 -0
- package/dist/src/sdk.d.ts.map +1 -1
- package/dist/src/utils/webgl-cache.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/sdk.js
CHANGED
|
@@ -262,8 +262,22 @@
|
|
|
262
262
|
return out;
|
|
263
263
|
}
|
|
264
264
|
if (typeof input === "number") {
|
|
265
|
-
//
|
|
266
|
-
|
|
265
|
+
// Enhanced floating point normalization for maximum stability
|
|
266
|
+
if (!Number.isFinite(input))
|
|
267
|
+
return 0;
|
|
268
|
+
// Handle very small numbers (round to 0)
|
|
269
|
+
if (Math.abs(input) < 0.0001)
|
|
270
|
+
return 0;
|
|
271
|
+
// Handle very large numbers (clamp to reasonable range)
|
|
272
|
+
if (Math.abs(input) > 1000000) {
|
|
273
|
+
return input > 0 ? 1000000 : -1e6;
|
|
274
|
+
}
|
|
275
|
+
// For screen ratios and similar values, use more precision
|
|
276
|
+
if (input >= 0.1 && input <= 10) {
|
|
277
|
+
return Number(input.toFixed(3));
|
|
278
|
+
}
|
|
279
|
+
// For other numbers, use 2 decimal places
|
|
280
|
+
return Number(input.toFixed(2));
|
|
267
281
|
}
|
|
268
282
|
if (typeof input === "string") {
|
|
269
283
|
return input.trim().toLowerCase();
|
|
@@ -1238,15 +1252,18 @@
|
|
|
1238
1252
|
// Fallback
|
|
1239
1253
|
}
|
|
1240
1254
|
}
|
|
1241
|
-
// Fallback: use timezone offset
|
|
1255
|
+
// Fallback: use timezone offset (STABLE - use standard time offset to avoid DST changes)
|
|
1242
1256
|
if (!timezone) {
|
|
1243
|
-
|
|
1257
|
+
// Use January date to get standard time offset (avoiding DST variations)
|
|
1258
|
+
const januaryDate = new Date(2024, 0, 1); // January 1st
|
|
1259
|
+
const offset = januaryDate.getTimezoneOffset();
|
|
1244
1260
|
const sign = offset > 0 ? '-' : '+';
|
|
1245
1261
|
const hours = Math.floor(Math.abs(offset) / 60);
|
|
1246
1262
|
const minutes = Math.abs(offset) % 60;
|
|
1247
1263
|
timezone = `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
1248
1264
|
}
|
|
1249
|
-
|
|
1265
|
+
// Use January date for stable timezone offset (avoiding DST fluctuations)
|
|
1266
|
+
const timezoneOffset = new Date(2024, 0, 1).getTimezoneOffset();
|
|
1250
1267
|
return { timezone, timezoneOffset };
|
|
1251
1268
|
}
|
|
1252
1269
|
catch {
|
|
@@ -1594,25 +1611,40 @@
|
|
|
1594
1611
|
// Get pixel data for analysis
|
|
1595
1612
|
const imageData = ctx.getImageData(10, 10, 30, 40);
|
|
1596
1613
|
const pixels = imageData.data;
|
|
1597
|
-
// Calculate sub-pixel characteristics
|
|
1614
|
+
// Calculate sub-pixel characteristics with enhanced stability
|
|
1598
1615
|
let redSum = 0, greenSum = 0, blueSum = 0;
|
|
1599
1616
|
let edgeVariance = 0;
|
|
1617
|
+
let validPixels = 0;
|
|
1600
1618
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1619
|
+
const r = pixels[i] || 0;
|
|
1620
|
+
const g = pixels[i + 1] || 0;
|
|
1621
|
+
const b = pixels[i + 2] || 0;
|
|
1622
|
+
const a = pixels[i + 3] || 0;
|
|
1623
|
+
// Only process non-transparent pixels for stability
|
|
1624
|
+
if (a > 25) { // Higher threshold than before
|
|
1625
|
+
redSum += r;
|
|
1626
|
+
greenSum += g;
|
|
1627
|
+
blueSum += b;
|
|
1628
|
+
validPixels++;
|
|
1629
|
+
// Calculate edge variance with larger step for stability
|
|
1630
|
+
if (i >= 16) { // Skip more pixels for stability
|
|
1631
|
+
const prevR = pixels[i - 16] || 0;
|
|
1632
|
+
const diff = Math.abs(r - prevR);
|
|
1633
|
+
// Only count significant differences to reduce noise
|
|
1634
|
+
if (diff >= 8) { // Higher threshold
|
|
1635
|
+
edgeVariance += diff;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (validPixels === 0) {
|
|
1641
|
+
return "no-valid-pixels";
|
|
1642
|
+
}
|
|
1643
|
+
// Round to nearest 10 for maximum stability
|
|
1644
|
+
const avgRed = Math.round((redSum / validPixels) / 10) * 10;
|
|
1645
|
+
const avgGreen = Math.round((greenSum / validPixels) / 10) * 10;
|
|
1646
|
+
const avgBlue = Math.round((blueSum / validPixels) / 10) * 10;
|
|
1647
|
+
const avgVariance = Math.round((edgeVariance / validPixels) / 10) * 10;
|
|
1616
1648
|
return `${avgRed}-${avgGreen}-${avgBlue}-${avgVariance}`;
|
|
1617
1649
|
}
|
|
1618
1650
|
catch (error) {
|
|
@@ -2214,7 +2246,8 @@
|
|
|
2214
2246
|
*/
|
|
2215
2247
|
function getCachedWebGLContext() {
|
|
2216
2248
|
try {
|
|
2217
|
-
|
|
2249
|
+
// Use performance.now() for more stable timing measurements
|
|
2250
|
+
const now = performance.now();
|
|
2218
2251
|
// Check if cache is valid and not expired
|
|
2219
2252
|
if (webglCache && webglCache.isValid) {
|
|
2220
2253
|
if (now - webglCache.timestamp < CACHE_TIMEOUT) {
|
|
@@ -2675,12 +2708,14 @@
|
|
|
2675
2708
|
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
|
|
2676
2709
|
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
|
|
2677
2710
|
if (!vertexShader || !fragmentShader) {
|
|
2678
|
-
|
|
2711
|
+
// Fallback to basic parameters hash if shader compilation fails
|
|
2712
|
+
return generateFallbackWebGLHash(gl);
|
|
2679
2713
|
}
|
|
2680
2714
|
// Create program
|
|
2681
2715
|
const program = createProgram(gl, vertexShader, fragmentShader);
|
|
2682
2716
|
if (!program) {
|
|
2683
|
-
|
|
2717
|
+
// Fallback to basic parameters hash if program creation fails
|
|
2718
|
+
return generateFallbackWebGLHash(gl);
|
|
2684
2719
|
}
|
|
2685
2720
|
gl.useProgram(program);
|
|
2686
2721
|
// Create complex geometry that differentiates GPU rendering
|
|
@@ -2737,7 +2772,35 @@
|
|
|
2737
2772
|
return hash.toString(16);
|
|
2738
2773
|
}
|
|
2739
2774
|
catch (error) {
|
|
2740
|
-
|
|
2775
|
+
// Fallback to basic parameters hash if rendering fails
|
|
2776
|
+
return generateFallbackWebGLHash(gl);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Generate fallback WebGL hash when rendering fails
|
|
2781
|
+
* Uses basic WebGL parameters to ensure consistent fingerprinting
|
|
2782
|
+
*/
|
|
2783
|
+
function generateFallbackWebGLHash(gl) {
|
|
2784
|
+
try {
|
|
2785
|
+
// Collect basic but stable WebGL parameters
|
|
2786
|
+
const vendor = gl.getParameter(gl.VENDOR) || 'unknown';
|
|
2787
|
+
const renderer = gl.getParameter(gl.RENDERER) || 'unknown';
|
|
2788
|
+
const version = gl.getParameter(gl.VERSION) || 'unknown';
|
|
2789
|
+
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) || 0;
|
|
2790
|
+
const maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS) || 0;
|
|
2791
|
+
const maxViewportDims = gl.getParameter(gl.MAX_VIEWPORT_DIMS) || [0, 0];
|
|
2792
|
+
// Combine parameters into string
|
|
2793
|
+
const combined = `${vendor}|${renderer}|${version}|${maxTextureSize}|${maxVertexAttribs}|${maxViewportDims}`;
|
|
2794
|
+
// Generate simple hash
|
|
2795
|
+
let hash = 0;
|
|
2796
|
+
for (let i = 0; i < combined.length; i++) {
|
|
2797
|
+
const char = combined.charCodeAt(i);
|
|
2798
|
+
hash = ((hash << 5) - hash + char) & 0xffffffff;
|
|
2799
|
+
}
|
|
2800
|
+
return `fallback_${Math.abs(hash).toString(16)}`;
|
|
2801
|
+
}
|
|
2802
|
+
catch {
|
|
2803
|
+
return 'webgl_unavailable';
|
|
2741
2804
|
}
|
|
2742
2805
|
}
|
|
2743
2806
|
/**
|
|
@@ -5071,91 +5134,67 @@
|
|
|
5071
5134
|
gdprMode: opts.gdprMode,
|
|
5072
5135
|
components: [],
|
|
5073
5136
|
};
|
|
5074
|
-
// Collect
|
|
5137
|
+
// OPTIMIZATION: Collect independent fingerprint components in parallel
|
|
5138
|
+
// This reduces total collection time from 3-5s to 500ms-1s
|
|
5139
|
+
const parallelTasks = [];
|
|
5140
|
+
// Group 1: Independent rendering components (can run in parallel)
|
|
5075
5141
|
if (shouldIncludeComponent("canvas", opts) && isCanvasAvailable$1()) {
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
}
|
|
5081
|
-
else {
|
|
5082
|
-
failedComponents.push({
|
|
5083
|
-
component: "canvas",
|
|
5084
|
-
error: "Collection failed or timeout",
|
|
5085
|
-
});
|
|
5086
|
-
}
|
|
5142
|
+
parallelTasks.push(collectComponent("canvas", getCanvasFingerprint, opts).then(result => ({
|
|
5143
|
+
component: "canvas",
|
|
5144
|
+
result
|
|
5145
|
+
})));
|
|
5087
5146
|
}
|
|
5088
|
-
// Collect WebGL fingerprint
|
|
5089
5147
|
if (shouldIncludeComponent("webgl", opts) && isWebGLAvailable()) {
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
}
|
|
5095
|
-
else {
|
|
5096
|
-
failedComponents.push({
|
|
5097
|
-
component: "webgl",
|
|
5098
|
-
error: "Collection failed or timeout",
|
|
5099
|
-
});
|
|
5100
|
-
}
|
|
5148
|
+
parallelTasks.push(collectComponent("webgl", getWebGLFingerprint, opts).then(result => ({
|
|
5149
|
+
component: "webgl",
|
|
5150
|
+
result
|
|
5151
|
+
})));
|
|
5101
5152
|
}
|
|
5102
|
-
// Collect Audio fingerprint
|
|
5103
5153
|
if (shouldIncludeComponent("audio", opts) && isAudioAvailable()) {
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
}
|
|
5109
|
-
else {
|
|
5110
|
-
failedComponents.push({
|
|
5111
|
-
component: "audio",
|
|
5112
|
-
error: "Collection failed or timeout",
|
|
5113
|
-
});
|
|
5114
|
-
}
|
|
5154
|
+
parallelTasks.push(collectComponent("audio", getAudioFingerprint, opts).then(result => ({
|
|
5155
|
+
component: "audio",
|
|
5156
|
+
result
|
|
5157
|
+
})));
|
|
5115
5158
|
}
|
|
5116
|
-
//
|
|
5159
|
+
// Group 2: Fast synchronous components (can run in parallel)
|
|
5117
5160
|
if (shouldIncludeComponent("fonts", opts) && isFontDetectionAvailable()) {
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
}
|
|
5123
|
-
else {
|
|
5124
|
-
failedComponents.push({
|
|
5125
|
-
component: "fonts",
|
|
5126
|
-
error: "Collection failed or timeout",
|
|
5127
|
-
});
|
|
5128
|
-
}
|
|
5161
|
+
parallelTasks.push(collectComponent("fonts", () => getFontFingerprint(!opts.gdprMode), opts).then(result => ({
|
|
5162
|
+
component: "fonts",
|
|
5163
|
+
result
|
|
5164
|
+
})));
|
|
5129
5165
|
}
|
|
5130
|
-
// Collect Screen fingerprint (always available)
|
|
5131
5166
|
if (shouldIncludeComponent("screen", opts) && isScreenAvailable()) {
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
collectedComponents.push("browser");
|
|
5167
|
+
parallelTasks.push(collectComponent("screen", getScreenFingerprint, opts).then(result => ({
|
|
5168
|
+
component: "screen",
|
|
5169
|
+
result
|
|
5170
|
+
})));
|
|
5171
|
+
}
|
|
5172
|
+
if (shouldIncludeComponent("browser", opts) && isBrowserFingerprintingAvailable()) {
|
|
5173
|
+
parallelTasks.push(collectComponent("browser", getBrowserFingerprint, opts).then(result => ({
|
|
5174
|
+
component: "browser",
|
|
5175
|
+
result
|
|
5176
|
+
})));
|
|
5177
|
+
}
|
|
5178
|
+
// Execute all parallel tasks and handle results
|
|
5179
|
+
const parallelResults = await Promise.allSettled(parallelTasks);
|
|
5180
|
+
parallelResults.forEach((promiseResult, index) => {
|
|
5181
|
+
if (promiseResult.status === 'fulfilled' && promiseResult.value.result) {
|
|
5182
|
+
const { component, result } = promiseResult.value;
|
|
5183
|
+
fingerprintData[component] = result;
|
|
5184
|
+
collectedComponents.push(component);
|
|
5151
5185
|
}
|
|
5152
5186
|
else {
|
|
5187
|
+
// Extract component name from the corresponding task
|
|
5188
|
+
const taskComponent = parallelTasks[index];
|
|
5189
|
+
const componentName = taskComponent?.toString().match(/component: "(\w+)"/)?.[1] || 'unknown';
|
|
5153
5190
|
failedComponents.push({
|
|
5154
|
-
component:
|
|
5155
|
-
error:
|
|
5191
|
+
component: componentName,
|
|
5192
|
+
error: promiseResult.status === 'rejected'
|
|
5193
|
+
? promiseResult.reason?.message || "Promise rejected"
|
|
5194
|
+
: "Collection failed or timeout",
|
|
5156
5195
|
});
|
|
5157
5196
|
}
|
|
5158
|
-
}
|
|
5197
|
+
});
|
|
5159
5198
|
// ADDED: Collect Incognito Detection (IMPROVED - 2024)
|
|
5160
5199
|
// Only in enhanced mode for better accuracy
|
|
5161
5200
|
if (!opts.gdprMode) {
|
|
@@ -5182,9 +5221,9 @@
|
|
|
5182
5221
|
// Collect Hardware fingerprint (NEW - inspired by FingerprintJS)
|
|
5183
5222
|
if (shouldIncludeComponent("hardware", opts)) {
|
|
5184
5223
|
try {
|
|
5185
|
-
const
|
|
5186
|
-
if (isHardwareDetectionAvailable()) {
|
|
5187
|
-
const result = getHardwareFingerprint();
|
|
5224
|
+
const hardwareModule = await Promise.resolve().then(function () { return hardware; }).catch(() => null);
|
|
5225
|
+
if (hardwareModule?.isHardwareDetectionAvailable?.()) {
|
|
5226
|
+
const result = hardwareModule.getHardwareFingerprint?.();
|
|
5188
5227
|
if (result) {
|
|
5189
5228
|
fingerprintData.hardware = {
|
|
5190
5229
|
value: result,
|
|
@@ -5193,6 +5232,19 @@
|
|
|
5193
5232
|
collectedComponents.push("hardware");
|
|
5194
5233
|
}
|
|
5195
5234
|
}
|
|
5235
|
+
else if (!hardwareModule) {
|
|
5236
|
+
// Fallback for when module fails to load
|
|
5237
|
+
const fallbackHardware = {
|
|
5238
|
+
hardwareConcurrency: navigator.hardwareConcurrency || 0,
|
|
5239
|
+
deviceMemory: navigator.deviceMemory || 0,
|
|
5240
|
+
platform: navigator.platform || 'unknown'
|
|
5241
|
+
};
|
|
5242
|
+
fingerprintData.hardware = {
|
|
5243
|
+
value: fallbackHardware,
|
|
5244
|
+
duration: 0,
|
|
5245
|
+
};
|
|
5246
|
+
collectedComponents.push("hardware");
|
|
5247
|
+
}
|
|
5196
5248
|
}
|
|
5197
5249
|
catch (error) {
|
|
5198
5250
|
failedComponents.push({
|
|
@@ -5652,7 +5704,9 @@
|
|
|
5652
5704
|
platform: (browser?.platform || "unknown").toLowerCase(),
|
|
5653
5705
|
// PRIORITY 5: Device type (critical for differentiation, ultra-stable)
|
|
5654
5706
|
deviceType,
|
|
5655
|
-
// PRIORITY 6: Enhanced screen resolution
|
|
5707
|
+
// PRIORITY 6: Enhanced screen resolution with exact dimensions for mobile differentiation
|
|
5708
|
+
// For mobile devices, we NEED exact dimensions to differentiate between similar models (S22 vs A54)
|
|
5709
|
+
// For desktop, bucketing is sufficient as hardware varies more
|
|
5656
5710
|
screen: screen
|
|
5657
5711
|
? (() => {
|
|
5658
5712
|
// Use enhanced screen buckets for modern display landscape
|
|
@@ -5747,6 +5801,19 @@
|
|
|
5747
5801
|
return "enhanced"; // Slightly enhanced DPI
|
|
5748
5802
|
return "standard"; // Standard 1x displays
|
|
5749
5803
|
};
|
|
5804
|
+
// CRITICAL: Do NOT include exact dimensions in stableCoreVector for mobile!
|
|
5805
|
+
// Reason: Screen dimensions change when:
|
|
5806
|
+
// - Chrome address bar appears/disappears (scroll behavior)
|
|
5807
|
+
// - Virtual keyboard opens/closes
|
|
5808
|
+
// - Screen rotates (portrait ↔ landscape)
|
|
5809
|
+
// - Browser zoom changes
|
|
5810
|
+
// These changes would cause stableCoreHash to change, breaking visitor identification
|
|
5811
|
+
//
|
|
5812
|
+
// INSTEAD: Use ONLY stable screen characteristics (bucket, aspect, density)
|
|
5813
|
+
// For device differentiation (S22 vs A54), use:
|
|
5814
|
+
// - screenFrame (bezel measurements)
|
|
5815
|
+
// - Combination of bucket + hardware + display
|
|
5816
|
+
// - Vector similarity in IdentityResolver
|
|
5750
5817
|
return {
|
|
5751
5818
|
bucket: getScreenBucket(width),
|
|
5752
5819
|
aspectClass: getAspectRatioClass(width, height),
|
|
@@ -5763,6 +5830,11 @@
|
|
|
5763
5830
|
: width >= 768
|
|
5764
5831
|
? "tablet"
|
|
5765
5832
|
: "mobile",
|
|
5833
|
+
// ❌ REMOVED: exact dimensions (causes instability on mobile)
|
|
5834
|
+
// Device differentiation is now handled by:
|
|
5835
|
+
// 1. screenFrame (bezel measurements - stable)
|
|
5836
|
+
// 2. hardware + display combination
|
|
5837
|
+
// 3. Vector similarity in IdentityResolver
|
|
5766
5838
|
};
|
|
5767
5839
|
})()
|
|
5768
5840
|
: {
|
|
@@ -5891,7 +5963,18 @@
|
|
|
5891
5963
|
: "none",
|
|
5892
5964
|
};
|
|
5893
5965
|
})(),
|
|
5894
|
-
// PRIORITY 8:
|
|
5966
|
+
// PRIORITY 8: Screen frame (bezel measurements) for mobile device differentiation
|
|
5967
|
+
// CRITICAL: This is STABLE across page loads and provides device-specific fingerprint
|
|
5968
|
+
// Samsung S22 vs A54 have different screen frames despite similar resolutions
|
|
5969
|
+
screenFrame: deviceSignals$1?.screenFrame
|
|
5970
|
+
? {
|
|
5971
|
+
top: deviceSignals$1.screenFrame.top || 0,
|
|
5972
|
+
right: deviceSignals$1.screenFrame.right || 0,
|
|
5973
|
+
bottom: deviceSignals$1.screenFrame.bottom || 0,
|
|
5974
|
+
left: deviceSignals$1.screenFrame.left || 0,
|
|
5975
|
+
}
|
|
5976
|
+
: undefined,
|
|
5977
|
+
// PRIORITY 9: Enhanced color and display capabilities bucketing
|
|
5895
5978
|
display: (() => {
|
|
5896
5979
|
const depth = deviceSignals$1.colorDepth;
|
|
5897
5980
|
const gamut = deviceSignals$1.colorGamut;
|
|
@@ -7375,7 +7458,11 @@
|
|
|
7375
7458
|
enable: () => {
|
|
7376
7459
|
this.config.heatmap = true;
|
|
7377
7460
|
if (!this.heatmapEngine && this.isInitialized) {
|
|
7378
|
-
|
|
7461
|
+
// Ensure sessionId is available (should be set during initialization)
|
|
7462
|
+
if (!this.sessionId) {
|
|
7463
|
+
this.sessionId = this.generateSessionId();
|
|
7464
|
+
}
|
|
7465
|
+
const sessionId = this.sessionId;
|
|
7379
7466
|
this.heatmapEngine = new HeatmapEngine(sessionId, {
|
|
7380
7467
|
trackClicks: true,
|
|
7381
7468
|
trackScrollDepth: true,
|
|
@@ -7422,7 +7509,11 @@
|
|
|
7422
7509
|
enable: () => {
|
|
7423
7510
|
this.config.replay = true;
|
|
7424
7511
|
if (!this.replayEngine && this.isInitialized) {
|
|
7425
|
-
|
|
7512
|
+
// Ensure sessionId is available (should be set during initialization)
|
|
7513
|
+
if (!this.sessionId) {
|
|
7514
|
+
this.sessionId = this.generateSessionId();
|
|
7515
|
+
}
|
|
7516
|
+
const sessionId = this.sessionId;
|
|
7426
7517
|
this.replayEngine = new SessionReplayEngine(sessionId, {
|
|
7427
7518
|
sampleRate: this.config.replaySampling,
|
|
7428
7519
|
maskSensitiveFields: this.config.replayMaskInputs,
|
|
@@ -7446,7 +7537,11 @@
|
|
|
7446
7537
|
},
|
|
7447
7538
|
start: () => {
|
|
7448
7539
|
if (!this.replayEngine && this.isInitialized) {
|
|
7449
|
-
|
|
7540
|
+
// Ensure sessionId is available (should be set during initialization)
|
|
7541
|
+
if (!this.sessionId) {
|
|
7542
|
+
this.sessionId = this.generateSessionId();
|
|
7543
|
+
}
|
|
7544
|
+
const sessionId = this.sessionId;
|
|
7450
7545
|
this.replayEngine = new SessionReplayEngine(sessionId, {
|
|
7451
7546
|
sampleRate: 1.0, // Force recording when manually started
|
|
7452
7547
|
maskSensitiveFields: this.config.replayMaskInputs,
|
|
@@ -7536,9 +7631,12 @@
|
|
|
7536
7631
|
*/
|
|
7537
7632
|
initializeTrackingEngines() {
|
|
7538
7633
|
try {
|
|
7539
|
-
// Generate session ID
|
|
7634
|
+
// Generate session ID once and only once during initialization
|
|
7540
7635
|
if (!this.sessionId) {
|
|
7541
7636
|
this.sessionId = this.generateSessionId();
|
|
7637
|
+
if (this.config.debug) {
|
|
7638
|
+
console.log("[Zaplier] Generated session ID:", this.sessionId);
|
|
7639
|
+
}
|
|
7542
7640
|
}
|
|
7543
7641
|
// Initialize Session Replay Engine
|
|
7544
7642
|
if (this.config.replay) {
|
|
@@ -7574,7 +7672,33 @@
|
|
|
7574
7672
|
* Generate session ID
|
|
7575
7673
|
*/
|
|
7576
7674
|
generateSessionId() {
|
|
7577
|
-
|
|
7675
|
+
// Create deterministic session ID based on page load time and stable browser characteristics
|
|
7676
|
+
const pageLoadTime = performance.timeOrigin || Date.now();
|
|
7677
|
+
const browserFingerprint = this.getBrowserFingerprint();
|
|
7678
|
+
// Use page load time + browser fingerprint for session uniqueness within same page load
|
|
7679
|
+
return pageLoadTime.toString(36) + browserFingerprint.substring(0, 8);
|
|
7680
|
+
}
|
|
7681
|
+
/**
|
|
7682
|
+
* Get stable browser characteristics for session ID generation
|
|
7683
|
+
* Uses only stable, privacy-safe values that don't change during session
|
|
7684
|
+
*/
|
|
7685
|
+
getBrowserFingerprint() {
|
|
7686
|
+
const components = [
|
|
7687
|
+
navigator.userAgent || '',
|
|
7688
|
+
navigator.platform || '',
|
|
7689
|
+
screen.width || 0,
|
|
7690
|
+
screen.height || 0,
|
|
7691
|
+
new Date(2024, 0, 1).getTimezoneOffset(),
|
|
7692
|
+
navigator.language || '',
|
|
7693
|
+
navigator.hardwareConcurrency || 0
|
|
7694
|
+
];
|
|
7695
|
+
const combined = components.join('|');
|
|
7696
|
+
let hash = 0;
|
|
7697
|
+
for (let i = 0; i < combined.length; i++) {
|
|
7698
|
+
const char = combined.charCodeAt(i);
|
|
7699
|
+
hash = ((hash << 5) - hash + char) & 0xffffffff;
|
|
7700
|
+
}
|
|
7701
|
+
return Math.abs(hash).toString(36);
|
|
7578
7702
|
}
|
|
7579
7703
|
/**
|
|
7580
7704
|
* Initialize Anti-Adblock Manager
|
|
@@ -7642,7 +7766,7 @@
|
|
|
7642
7766
|
}
|
|
7643
7767
|
// Fallback: collect entropy for diagnostics only; do NOT set local visitorId
|
|
7644
7768
|
try {
|
|
7645
|
-
// Collect
|
|
7769
|
+
// Collect stable entropy sources (no random values for consistency)
|
|
7646
7770
|
const entropyComponents = [
|
|
7647
7771
|
navigator.userAgent || "",
|
|
7648
7772
|
navigator.language || "",
|
|
@@ -7650,44 +7774,40 @@
|
|
|
7650
7774
|
screen.width || 0,
|
|
7651
7775
|
screen.height || 0,
|
|
7652
7776
|
screen.colorDepth || 0,
|
|
7653
|
-
new Date().getTimezoneOffset(),
|
|
7654
|
-
// Hardware-specific
|
|
7777
|
+
new Date(2024, 0, 1).getTimezoneOffset(),
|
|
7778
|
+
// Hardware-specific (stable values)
|
|
7655
7779
|
navigator.hardwareConcurrency || 0,
|
|
7656
7780
|
navigator.deviceMemory || 0,
|
|
7657
7781
|
navigator.maxTouchPoints || 0,
|
|
7658
|
-
//
|
|
7659
|
-
performance.
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
performance.memory?.
|
|
7666
|
-
Math.random() * 1000000,
|
|
7667
|
-
performance.memory?.totalJSHeapSize ||
|
|
7668
|
-
Math.random() * 1000000,
|
|
7782
|
+
// Use page load time (stable during session) instead of current time
|
|
7783
|
+
performance.timeOrigin || 0,
|
|
7784
|
+
// Deterministic entropy from browser characteristics
|
|
7785
|
+
(navigator.plugins ? navigator.plugins.length : 0),
|
|
7786
|
+
(navigator.mimeTypes ? navigator.mimeTypes.length : 0),
|
|
7787
|
+
// Memory usage pattern (if available, use rounded values for stability)
|
|
7788
|
+
Math.floor((performance.memory?.usedJSHeapSize || 0) / 1000000),
|
|
7789
|
+
Math.floor((performance.memory?.totalJSHeapSize || 0) / 1000000),
|
|
7669
7790
|
// Connection info (if available)
|
|
7670
7791
|
navigator.connection?.effectiveType || "",
|
|
7671
|
-
navigator.connection?.downlink ||
|
|
7792
|
+
Math.floor(navigator.connection?.downlink || 0),
|
|
7672
7793
|
// Additional browser-specific entropy
|
|
7673
7794
|
window.outerHeight || 0,
|
|
7674
7795
|
window.outerWidth || 0,
|
|
7675
|
-
window.devicePixelRatio || 1,
|
|
7676
|
-
//
|
|
7677
|
-
|
|
7796
|
+
Math.floor((window.devicePixelRatio || 1) * 100) / 100, // Round to 2 decimals
|
|
7797
|
+
// Canvas support (deterministic)
|
|
7798
|
+
typeof document.createElement('canvas').getContext === 'function' ? 1 : 0,
|
|
7678
7799
|
];
|
|
7679
|
-
// Create
|
|
7680
|
-
const entropyString = entropyComponents.join("|")
|
|
7681
|
-
//
|
|
7682
|
-
let
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
window.crypto.getRandomValues(array);
|
|
7686
|
-
cryptoRandom = Array.from(array).join("");
|
|
7800
|
+
// Create deterministic entropy string
|
|
7801
|
+
const entropyString = entropyComponents.join("|");
|
|
7802
|
+
// Add deterministic checksum instead of random values
|
|
7803
|
+
let checksum = 0;
|
|
7804
|
+
for (let i = 0; i < entropyString.length; i++) {
|
|
7805
|
+
checksum = ((checksum << 5) - checksum + entropyString.charCodeAt(i)) & 0xffffffff;
|
|
7687
7806
|
}
|
|
7807
|
+
const deterministic = Math.abs(checksum).toString(36);
|
|
7688
7808
|
// Advanced hash function with better distribution
|
|
7689
7809
|
let hash = 0;
|
|
7690
|
-
const finalString = entropyString +
|
|
7810
|
+
const finalString = entropyString + deterministic;
|
|
7691
7811
|
for (let i = 0; i < finalString.length; i++) {
|
|
7692
7812
|
const char = finalString.charCodeAt(i);
|
|
7693
7813
|
hash = ((hash << 5) - hash + char) & 0xffffffff;
|
|
@@ -10603,8 +10723,8 @@
|
|
|
10603
10723
|
return timezone;
|
|
10604
10724
|
}
|
|
10605
10725
|
}
|
|
10606
|
-
// Fallback: timezone offset
|
|
10607
|
-
const offset = new Date().getTimezoneOffset();
|
|
10726
|
+
// Fallback: timezone offset (STABLE - use standard time to avoid DST changes)
|
|
10727
|
+
const offset = new Date(2024, 0, 1).getTimezoneOffset();
|
|
10608
10728
|
return `UTC${offset > 0 ? '-' : '+'}${Math.abs(offset / 60)}`;
|
|
10609
10729
|
}
|
|
10610
10730
|
catch {
|
|
@@ -12432,16 +12552,84 @@
|
|
|
12432
12552
|
}
|
|
12433
12553
|
/**
|
|
12434
12554
|
* Get CPU architecture signature (ARM vs x86)
|
|
12435
|
-
* Uses
|
|
12555
|
+
* Uses multiple detection methods for maximum reliability
|
|
12436
12556
|
*/
|
|
12437
12557
|
function getArchitecture() {
|
|
12438
12558
|
try {
|
|
12439
|
-
//
|
|
12440
|
-
const
|
|
12441
|
-
const
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12559
|
+
// Method 1: Enhanced platform string detection (most reliable)
|
|
12560
|
+
const platform = navigator.platform?.toLowerCase() || '';
|
|
12561
|
+
const userAgent = navigator.userAgent?.toLowerCase() || '';
|
|
12562
|
+
// CRITICAL FIX: Prioritize desktop x86_64 detection first
|
|
12563
|
+
// Windows x64/x86 detection (highest priority for x86_64)
|
|
12564
|
+
if (platform.includes('win') && (userAgent.includes('wow64') || userAgent.includes('x64') || userAgent.includes('x86_64'))) {
|
|
12565
|
+
return 0; // x86_64 - Windows
|
|
12566
|
+
}
|
|
12567
|
+
// Linux x86_64 detection
|
|
12568
|
+
if (platform.includes('linux') && (platform.includes('x86_64') || userAgent.includes('x86_64'))) {
|
|
12569
|
+
return 0; // x86_64 - Linux
|
|
12570
|
+
}
|
|
12571
|
+
// MacOS Intel detection (before ARM check)
|
|
12572
|
+
if (platform.includes('mac') && (platform.includes('intel') || userAgent.includes('intel mac'))) {
|
|
12573
|
+
return 0; // x86_64 - Intel Mac
|
|
12574
|
+
}
|
|
12575
|
+
// Generic x86/x64 detection
|
|
12576
|
+
if (platform.includes('x86') || platform.includes('x64') ||
|
|
12577
|
+
platform.includes('intel') || platform.includes('amd64') ||
|
|
12578
|
+
userAgent.includes('x86') || userAgent.includes('amd64')) {
|
|
12579
|
+
return 0; // x86_64
|
|
12580
|
+
}
|
|
12581
|
+
// ARM detection (after x86_64 checks)
|
|
12582
|
+
if (platform.includes('arm') || userAgent.includes('arm') ||
|
|
12583
|
+
userAgent.includes('aarch64') || platform.includes('aarch64')) {
|
|
12584
|
+
return 255; // ARM
|
|
12585
|
+
}
|
|
12586
|
+
// Method 2: WebGL vendor detection (secondary check)
|
|
12587
|
+
try {
|
|
12588
|
+
const canvas = document.createElement('canvas');
|
|
12589
|
+
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
12590
|
+
if (gl && 'getExtension' in gl && 'getParameter' in gl) {
|
|
12591
|
+
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
12592
|
+
if (debugInfo) {
|
|
12593
|
+
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)?.toLowerCase() || '';
|
|
12594
|
+
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)?.toLowerCase() || '';
|
|
12595
|
+
// Enhanced x86_64 GPU detection (desktop GPUs)
|
|
12596
|
+
if (vendor.includes('intel') || vendor.includes('nvidia') || vendor.includes('amd') ||
|
|
12597
|
+
renderer.includes('intel') || renderer.includes('nvidia') || renderer.includes('amd') ||
|
|
12598
|
+
renderer.includes('radeon') || renderer.includes('geforce') || renderer.includes('quadro') ||
|
|
12599
|
+
renderer.includes('iris') || renderer.includes('uhd') || renderer.includes('hd graphics')) {
|
|
12600
|
+
return 0; // x86_64 - Desktop GPU
|
|
12601
|
+
}
|
|
12602
|
+
// ARM GPU detection (mobile and ARM devices)
|
|
12603
|
+
if (renderer.includes('mali') || renderer.includes('adreno') ||
|
|
12604
|
+
renderer.includes('powervr') || renderer.includes('tegra') ||
|
|
12605
|
+
renderer.includes('apple gpu') || vendor.includes('arm') ||
|
|
12606
|
+
renderer.includes('videocore')) {
|
|
12607
|
+
return 255; // ARM - Mobile/ARM GPU
|
|
12608
|
+
}
|
|
12609
|
+
}
|
|
12610
|
+
}
|
|
12611
|
+
}
|
|
12612
|
+
catch {
|
|
12613
|
+
// WebGL detection failed, continue
|
|
12614
|
+
}
|
|
12615
|
+
// Method 3: Fallback floating point NaN behavior (least reliable)
|
|
12616
|
+
try {
|
|
12617
|
+
const f = new Float32Array(1);
|
|
12618
|
+
const u8 = new Uint8Array(f.buffer);
|
|
12619
|
+
f[0] = Infinity;
|
|
12620
|
+
f[0] = f[0] - f[0]; // Should produce NaN
|
|
12621
|
+
const nanBehavior = u8[3];
|
|
12622
|
+
// Only trust this if platform detection was inconclusive
|
|
12623
|
+
// AND we have strong indicators from other methods
|
|
12624
|
+
if (platform.includes('linux') && nanBehavior === 255) {
|
|
12625
|
+
// Linux on ARM is less common, but validate with other signals
|
|
12626
|
+
return nanBehavior;
|
|
12627
|
+
}
|
|
12628
|
+
return nanBehavior;
|
|
12629
|
+
}
|
|
12630
|
+
catch {
|
|
12631
|
+
return undefined;
|
|
12632
|
+
}
|
|
12445
12633
|
}
|
|
12446
12634
|
catch {
|
|
12447
12635
|
return undefined;
|