@zaplier/sdk 1.4.2 → 1.6.2
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 +876 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +56 -5
- package/dist/index.esm.js +876 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/sdk.js +876 -13
- package/dist/sdk.js.map +1 -1
- package/dist/sdk.min.js +1 -1
- package/dist/src/modules/auto-tracker.d.ts +101 -0
- package/dist/src/modules/auto-tracker.d.ts.map +1 -0
- package/dist/src/modules/global-interface.d.ts +14 -0
- package/dist/src/modules/global-interface.d.ts.map +1 -1
- package/dist/src/modules/session-replay.d.ts +40 -1
- package/dist/src/modules/session-replay.d.ts.map +1 -1
- package/dist/src/modules/visitor-persistence.d.ts +36 -0
- package/dist/src/modules/visitor-persistence.d.ts.map +1 -0
- package/dist/src/sdk.d.ts +26 -1
- package/dist/src/sdk.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -19016,14 +19016,20 @@ var n;
|
|
|
19016
19016
|
* Based on official rrweb example for maximum compatibility
|
|
19017
19017
|
*/
|
|
19018
19018
|
class SessionReplayEngine {
|
|
19019
|
-
constructor(sessionId, config = {}) {
|
|
19019
|
+
constructor(sessionId, visitorId, config = {}) {
|
|
19020
19020
|
this.events = [];
|
|
19021
19021
|
this.isActive = false;
|
|
19022
|
+
this.isPaused = false;
|
|
19023
|
+
this.lastActivityTime = Date.now();
|
|
19024
|
+
this.sessionStartTime = Date.now();
|
|
19022
19025
|
this.sessionId = sessionId;
|
|
19026
|
+
this.visitorId = visitorId;
|
|
19023
19027
|
this.config = {
|
|
19024
19028
|
enabled: true,
|
|
19025
19029
|
sampleRate: 1.0,
|
|
19026
19030
|
batchInterval: 10000, // 10 seconds like official example
|
|
19031
|
+
inactivityTimeout: 30000, // 30 seconds of inactivity
|
|
19032
|
+
pauseOnInactive: true, // Pause recording during inactivity
|
|
19027
19033
|
...config,
|
|
19028
19034
|
};
|
|
19029
19035
|
}
|
|
@@ -19042,13 +19048,18 @@ class SessionReplayEngine {
|
|
|
19042
19048
|
// Simple rrweb recording configuration like official example
|
|
19043
19049
|
this.rrwebStopRecord = record({
|
|
19044
19050
|
emit: (event) => {
|
|
19045
|
-
//
|
|
19046
|
-
this.
|
|
19051
|
+
// Update activity time on any event
|
|
19052
|
+
this.onActivity();
|
|
19053
|
+
// Only capture events if not paused
|
|
19054
|
+
if (!this.isPaused) {
|
|
19055
|
+
this.events.push(event);
|
|
19056
|
+
}
|
|
19047
19057
|
},
|
|
19048
19058
|
});
|
|
19049
19059
|
this.isActive = true;
|
|
19050
19060
|
this.startBatchTimer();
|
|
19051
|
-
|
|
19061
|
+
this.startInactivityTracking();
|
|
19062
|
+
console.log("[Zaplier] Session replay started - with inactivity detection");
|
|
19052
19063
|
return true;
|
|
19053
19064
|
}
|
|
19054
19065
|
catch (error) {
|
|
@@ -19072,6 +19083,10 @@ class SessionReplayEngine {
|
|
|
19072
19083
|
clearInterval(this.batchTimer);
|
|
19073
19084
|
this.batchTimer = undefined;
|
|
19074
19085
|
}
|
|
19086
|
+
if (this.inactivityTimer) {
|
|
19087
|
+
clearTimeout(this.inactivityTimer);
|
|
19088
|
+
this.inactivityTimer = undefined;
|
|
19089
|
+
}
|
|
19075
19090
|
// Send final batch
|
|
19076
19091
|
this.sendBatch();
|
|
19077
19092
|
}
|
|
@@ -19090,17 +19105,21 @@ class SessionReplayEngine {
|
|
|
19090
19105
|
if (this.events.length === 0) {
|
|
19091
19106
|
return;
|
|
19092
19107
|
}
|
|
19093
|
-
//
|
|
19108
|
+
// Enhanced payload structure with visitor linking
|
|
19094
19109
|
const payload = {
|
|
19095
19110
|
sessionId: this.sessionId,
|
|
19111
|
+
visitorId: this.visitorId,
|
|
19096
19112
|
events: [...this.events], // Copy events array
|
|
19097
19113
|
metadata: {
|
|
19098
19114
|
userAgent: navigator.userAgent,
|
|
19099
19115
|
timestamp: Date.now(),
|
|
19100
19116
|
startUrl: window.location.href,
|
|
19101
|
-
duration: Date.now() -
|
|
19117
|
+
duration: Date.now() - this.sessionStartTime,
|
|
19118
|
+
activeTime: this.getActiveTime(),
|
|
19102
19119
|
funnelSteps: [],
|
|
19103
19120
|
hasConversion: false,
|
|
19121
|
+
eventsCount: this.events.length,
|
|
19122
|
+
isPaused: this.isPaused,
|
|
19104
19123
|
},
|
|
19105
19124
|
};
|
|
19106
19125
|
// Reset events array like official example
|
|
@@ -19149,6 +19168,714 @@ class SessionReplayEngine {
|
|
|
19149
19168
|
isRecording() {
|
|
19150
19169
|
return this.isActive;
|
|
19151
19170
|
}
|
|
19171
|
+
/**
|
|
19172
|
+
* Check if recording is paused
|
|
19173
|
+
*/
|
|
19174
|
+
isPausedState() {
|
|
19175
|
+
return this.isPaused;
|
|
19176
|
+
}
|
|
19177
|
+
/**
|
|
19178
|
+
* Start inactivity tracking
|
|
19179
|
+
*/
|
|
19180
|
+
startInactivityTracking() {
|
|
19181
|
+
if (!this.config.pauseOnInactive)
|
|
19182
|
+
return;
|
|
19183
|
+
// Listen for user activity events
|
|
19184
|
+
const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
|
|
19185
|
+
activityEvents.forEach(event => {
|
|
19186
|
+
document.addEventListener(event, this.onActivity.bind(this), true);
|
|
19187
|
+
});
|
|
19188
|
+
this.resetInactivityTimer();
|
|
19189
|
+
}
|
|
19190
|
+
/**
|
|
19191
|
+
* Handle activity event
|
|
19192
|
+
*/
|
|
19193
|
+
onActivity() {
|
|
19194
|
+
this.lastActivityTime = Date.now();
|
|
19195
|
+
// Resume if paused
|
|
19196
|
+
if (this.isPaused) {
|
|
19197
|
+
this.resume();
|
|
19198
|
+
}
|
|
19199
|
+
this.resetInactivityTimer();
|
|
19200
|
+
}
|
|
19201
|
+
/**
|
|
19202
|
+
* Reset inactivity timer
|
|
19203
|
+
*/
|
|
19204
|
+
resetInactivityTimer() {
|
|
19205
|
+
if (this.inactivityTimer) {
|
|
19206
|
+
clearTimeout(this.inactivityTimer);
|
|
19207
|
+
}
|
|
19208
|
+
this.inactivityTimer = window.setTimeout(() => {
|
|
19209
|
+
this.pauseForInactivity();
|
|
19210
|
+
}, this.config.inactivityTimeout);
|
|
19211
|
+
}
|
|
19212
|
+
/**
|
|
19213
|
+
* Pause recording due to inactivity
|
|
19214
|
+
*/
|
|
19215
|
+
pauseForInactivity() {
|
|
19216
|
+
if (this.isPaused)
|
|
19217
|
+
return;
|
|
19218
|
+
this.isPaused = true;
|
|
19219
|
+
console.log("[Zaplier] Session replay paused due to inactivity");
|
|
19220
|
+
}
|
|
19221
|
+
/**
|
|
19222
|
+
* Resume recording after activity
|
|
19223
|
+
*/
|
|
19224
|
+
resume() {
|
|
19225
|
+
if (!this.isPaused)
|
|
19226
|
+
return;
|
|
19227
|
+
this.isPaused = false;
|
|
19228
|
+
console.log("[Zaplier] Session replay resumed after activity");
|
|
19229
|
+
}
|
|
19230
|
+
/**
|
|
19231
|
+
* Get total active time (excluding paused periods)
|
|
19232
|
+
*/
|
|
19233
|
+
getActiveTime() {
|
|
19234
|
+
const totalTime = Date.now() - this.sessionStartTime;
|
|
19235
|
+
// For now, return total time. In production, track pause periods accurately
|
|
19236
|
+
return totalTime;
|
|
19237
|
+
}
|
|
19238
|
+
/**
|
|
19239
|
+
* Get visitor ID
|
|
19240
|
+
*/
|
|
19241
|
+
getVisitorId() {
|
|
19242
|
+
return this.visitorId;
|
|
19243
|
+
}
|
|
19244
|
+
}
|
|
19245
|
+
|
|
19246
|
+
/**
|
|
19247
|
+
* Auto Tracker - Sistema de tracking automático via data-* attributes
|
|
19248
|
+
*
|
|
19249
|
+
* Suporta:
|
|
19250
|
+
* - data-track-click="event-name"
|
|
19251
|
+
* - data-track-scroll="event-name"
|
|
19252
|
+
* - data-track-view="event-name"
|
|
19253
|
+
* - data-track-hover="event-name"
|
|
19254
|
+
* - data-track-form="event-name"
|
|
19255
|
+
*/
|
|
19256
|
+
class AutoTracker {
|
|
19257
|
+
constructor(sdkInstance, config = {}) {
|
|
19258
|
+
this.observedElements = new Set();
|
|
19259
|
+
/**
|
|
19260
|
+
* Event handlers (bound to this)
|
|
19261
|
+
*/
|
|
19262
|
+
this.handleClick = (event) => {
|
|
19263
|
+
const target = event.target;
|
|
19264
|
+
if (target.hasAttribute("data-track-click")) ;
|
|
19265
|
+
};
|
|
19266
|
+
this.handleScroll = () => {
|
|
19267
|
+
// Scroll is handled by element-specific listeners
|
|
19268
|
+
};
|
|
19269
|
+
this.handleHover = (event) => {
|
|
19270
|
+
// Hover is handled by element-specific listeners
|
|
19271
|
+
};
|
|
19272
|
+
this.handleForm = (event) => {
|
|
19273
|
+
// Form is handled by element-specific listeners
|
|
19274
|
+
};
|
|
19275
|
+
this.sdkInstance = sdkInstance;
|
|
19276
|
+
this.config = {
|
|
19277
|
+
enabled: true,
|
|
19278
|
+
trackClicks: true,
|
|
19279
|
+
trackScrolls: true,
|
|
19280
|
+
trackViews: true,
|
|
19281
|
+
trackHovers: true,
|
|
19282
|
+
trackForms: true,
|
|
19283
|
+
debug: false,
|
|
19284
|
+
...config,
|
|
19285
|
+
};
|
|
19286
|
+
}
|
|
19287
|
+
/**
|
|
19288
|
+
* Inicializar auto tracking
|
|
19289
|
+
*/
|
|
19290
|
+
start() {
|
|
19291
|
+
if (!this.config.enabled)
|
|
19292
|
+
return;
|
|
19293
|
+
if (this.config.debug) {
|
|
19294
|
+
console.log("[Zaplier AutoTracker] Iniciando auto tracking");
|
|
19295
|
+
}
|
|
19296
|
+
// Observer para novos elementos no DOM
|
|
19297
|
+
this.observeDOM();
|
|
19298
|
+
// Processar elementos já existentes
|
|
19299
|
+
this.processExistingElements();
|
|
19300
|
+
// Setup intersection observer para views
|
|
19301
|
+
if (this.config.trackViews) {
|
|
19302
|
+
this.setupViewTracking();
|
|
19303
|
+
}
|
|
19304
|
+
}
|
|
19305
|
+
/**
|
|
19306
|
+
* Parar auto tracking
|
|
19307
|
+
*/
|
|
19308
|
+
stop() {
|
|
19309
|
+
document.removeEventListener("click", this.handleClick);
|
|
19310
|
+
document.removeEventListener("scroll", this.handleScroll);
|
|
19311
|
+
document.removeEventListener("mouseover", this.handleHover);
|
|
19312
|
+
document.removeEventListener("submit", this.handleForm);
|
|
19313
|
+
if (this.intersectionObserver) {
|
|
19314
|
+
this.intersectionObserver.disconnect();
|
|
19315
|
+
}
|
|
19316
|
+
this.observedElements.clear();
|
|
19317
|
+
}
|
|
19318
|
+
/**
|
|
19319
|
+
* Observar mudanças no DOM para novos elementos
|
|
19320
|
+
*/
|
|
19321
|
+
observeDOM() {
|
|
19322
|
+
const observer = new MutationObserver((mutations) => {
|
|
19323
|
+
mutations.forEach((mutation) => {
|
|
19324
|
+
mutation.addedNodes.forEach((node) => {
|
|
19325
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
19326
|
+
this.processElement(node);
|
|
19327
|
+
}
|
|
19328
|
+
});
|
|
19329
|
+
});
|
|
19330
|
+
});
|
|
19331
|
+
observer.observe(document.body, {
|
|
19332
|
+
childList: true,
|
|
19333
|
+
subtree: true,
|
|
19334
|
+
});
|
|
19335
|
+
}
|
|
19336
|
+
/**
|
|
19337
|
+
* Processar elementos já existentes no DOM
|
|
19338
|
+
*/
|
|
19339
|
+
processExistingElements() {
|
|
19340
|
+
// Buscar todos os elementos com data-track-*
|
|
19341
|
+
const trackElements = document.querySelectorAll("[data-track-click], [data-track-scroll], [data-track-view], [data-track-hover], [data-track-form]");
|
|
19342
|
+
trackElements.forEach((element) => {
|
|
19343
|
+
this.processElement(element);
|
|
19344
|
+
});
|
|
19345
|
+
}
|
|
19346
|
+
/**
|
|
19347
|
+
* Processar um elemento específico
|
|
19348
|
+
*/
|
|
19349
|
+
processElement(element) {
|
|
19350
|
+
// Click tracking
|
|
19351
|
+
if (this.config.trackClicks && element.hasAttribute("data-track-click")) {
|
|
19352
|
+
this.setupClickTracking(element);
|
|
19353
|
+
}
|
|
19354
|
+
// Scroll tracking
|
|
19355
|
+
if (this.config.trackScrolls && element.hasAttribute("data-track-scroll")) {
|
|
19356
|
+
this.setupScrollTracking(element);
|
|
19357
|
+
}
|
|
19358
|
+
// View tracking
|
|
19359
|
+
if (this.config.trackViews && element.hasAttribute("data-track-view")) {
|
|
19360
|
+
this.setupElementViewTracking(element);
|
|
19361
|
+
}
|
|
19362
|
+
// Hover tracking
|
|
19363
|
+
if (this.config.trackHovers && element.hasAttribute("data-track-hover")) {
|
|
19364
|
+
this.setupHoverTracking(element);
|
|
19365
|
+
}
|
|
19366
|
+
// Form tracking
|
|
19367
|
+
if (this.config.trackForms && element.hasAttribute("data-track-form")) {
|
|
19368
|
+
this.setupFormTracking(element);
|
|
19369
|
+
}
|
|
19370
|
+
}
|
|
19371
|
+
/**
|
|
19372
|
+
* Setup click tracking
|
|
19373
|
+
*/
|
|
19374
|
+
setupClickTracking(element) {
|
|
19375
|
+
element.addEventListener("click", (event) => {
|
|
19376
|
+
const eventName = element.getAttribute("data-track-click");
|
|
19377
|
+
if (!eventName)
|
|
19378
|
+
return;
|
|
19379
|
+
const metadata = this.extractMetadata(element);
|
|
19380
|
+
this.trackEvent(eventName, {
|
|
19381
|
+
type: "click",
|
|
19382
|
+
element: element.tagName.toLowerCase(),
|
|
19383
|
+
...metadata,
|
|
19384
|
+
});
|
|
19385
|
+
if (this.config.debug) {
|
|
19386
|
+
console.log(`[AutoTracker] Click tracked: ${eventName}`, metadata);
|
|
19387
|
+
}
|
|
19388
|
+
});
|
|
19389
|
+
}
|
|
19390
|
+
/**
|
|
19391
|
+
* Setup scroll tracking
|
|
19392
|
+
*/
|
|
19393
|
+
setupScrollTracking(element) {
|
|
19394
|
+
let hasTriggered = false;
|
|
19395
|
+
const threshold = parseFloat(element.getAttribute("data-scroll-threshold") || "0.5");
|
|
19396
|
+
const handleScroll = () => {
|
|
19397
|
+
if (hasTriggered)
|
|
19398
|
+
return;
|
|
19399
|
+
const rect = element.getBoundingClientRect();
|
|
19400
|
+
const elementHeight = rect.height;
|
|
19401
|
+
const visibleHeight = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0);
|
|
19402
|
+
const visibilityRatio = Math.max(0, visibleHeight) / elementHeight;
|
|
19403
|
+
if (visibilityRatio >= threshold) {
|
|
19404
|
+
hasTriggered = true;
|
|
19405
|
+
const eventName = element.getAttribute("data-track-scroll");
|
|
19406
|
+
if (!eventName)
|
|
19407
|
+
return;
|
|
19408
|
+
const metadata = this.extractMetadata(element);
|
|
19409
|
+
this.trackEvent(eventName, {
|
|
19410
|
+
type: "scroll",
|
|
19411
|
+
element: element.tagName.toLowerCase(),
|
|
19412
|
+
threshold,
|
|
19413
|
+
scrollDepth: window.scrollY,
|
|
19414
|
+
...metadata,
|
|
19415
|
+
});
|
|
19416
|
+
if (this.config.debug) {
|
|
19417
|
+
console.log(`[AutoTracker] Scroll tracked: ${eventName}`, metadata);
|
|
19418
|
+
}
|
|
19419
|
+
}
|
|
19420
|
+
};
|
|
19421
|
+
document.addEventListener("scroll", handleScroll, { passive: true });
|
|
19422
|
+
// Check immediately in case element is already in view
|
|
19423
|
+
setTimeout(handleScroll, 100);
|
|
19424
|
+
}
|
|
19425
|
+
/**
|
|
19426
|
+
* Setup view tracking usando Intersection Observer
|
|
19427
|
+
*/
|
|
19428
|
+
setupViewTracking() {
|
|
19429
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
19430
|
+
entries.forEach((entry) => {
|
|
19431
|
+
if (entry.isIntersecting) {
|
|
19432
|
+
const element = entry.target;
|
|
19433
|
+
const eventName = element.getAttribute("data-track-view");
|
|
19434
|
+
if (!eventName)
|
|
19435
|
+
return;
|
|
19436
|
+
const metadata = this.extractMetadata(element);
|
|
19437
|
+
this.trackEvent(eventName, {
|
|
19438
|
+
type: "view",
|
|
19439
|
+
element: element.tagName.toLowerCase(),
|
|
19440
|
+
intersectionRatio: entry.intersectionRatio,
|
|
19441
|
+
...metadata,
|
|
19442
|
+
});
|
|
19443
|
+
if (this.config.debug) {
|
|
19444
|
+
console.log(`[AutoTracker] View tracked: ${eventName}`, metadata);
|
|
19445
|
+
}
|
|
19446
|
+
// Remove from observation after first trigger
|
|
19447
|
+
this.intersectionObserver?.unobserve(element);
|
|
19448
|
+
}
|
|
19449
|
+
});
|
|
19450
|
+
}, {
|
|
19451
|
+
threshold: 0.5, // 50% visible
|
|
19452
|
+
rootMargin: "0px",
|
|
19453
|
+
});
|
|
19454
|
+
}
|
|
19455
|
+
/**
|
|
19456
|
+
* Setup view tracking para elemento específico
|
|
19457
|
+
*/
|
|
19458
|
+
setupElementViewTracking(element) {
|
|
19459
|
+
if (!this.intersectionObserver)
|
|
19460
|
+
return;
|
|
19461
|
+
if (!this.observedElements.has(element)) {
|
|
19462
|
+
this.intersectionObserver.observe(element);
|
|
19463
|
+
this.observedElements.add(element);
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19466
|
+
/**
|
|
19467
|
+
* Setup hover tracking
|
|
19468
|
+
*/
|
|
19469
|
+
setupHoverTracking(element) {
|
|
19470
|
+
let hoverStartTime;
|
|
19471
|
+
const minHoverTime = parseInt(element.getAttribute("data-hover-time") || "1000");
|
|
19472
|
+
element.addEventListener("mouseenter", () => {
|
|
19473
|
+
hoverStartTime = Date.now();
|
|
19474
|
+
});
|
|
19475
|
+
element.addEventListener("mouseleave", () => {
|
|
19476
|
+
const hoverDuration = Date.now() - hoverStartTime;
|
|
19477
|
+
if (hoverDuration >= minHoverTime) {
|
|
19478
|
+
const eventName = element.getAttribute("data-track-hover");
|
|
19479
|
+
if (!eventName)
|
|
19480
|
+
return;
|
|
19481
|
+
const metadata = this.extractMetadata(element);
|
|
19482
|
+
this.trackEvent(eventName, {
|
|
19483
|
+
type: "hover",
|
|
19484
|
+
element: element.tagName.toLowerCase(),
|
|
19485
|
+
hoverDuration,
|
|
19486
|
+
minHoverTime,
|
|
19487
|
+
...metadata,
|
|
19488
|
+
});
|
|
19489
|
+
if (this.config.debug) {
|
|
19490
|
+
console.log(`[AutoTracker] Hover tracked: ${eventName}`, metadata);
|
|
19491
|
+
}
|
|
19492
|
+
}
|
|
19493
|
+
});
|
|
19494
|
+
}
|
|
19495
|
+
/**
|
|
19496
|
+
* Setup form tracking
|
|
19497
|
+
*/
|
|
19498
|
+
setupFormTracking(element) {
|
|
19499
|
+
if (element.tagName.toLowerCase() !== "form")
|
|
19500
|
+
return;
|
|
19501
|
+
element.addEventListener("submit", (event) => {
|
|
19502
|
+
const eventName = element.getAttribute("data-track-form");
|
|
19503
|
+
if (!eventName)
|
|
19504
|
+
return;
|
|
19505
|
+
const formData = new FormData(element);
|
|
19506
|
+
const metadata = this.extractMetadata(element);
|
|
19507
|
+
// Extract form fields (without sensitive data)
|
|
19508
|
+
const fields = {};
|
|
19509
|
+
for (const [key, value] of formData.entries()) {
|
|
19510
|
+
// Skip sensitive fields
|
|
19511
|
+
if (!this.isSensitiveField(key)) {
|
|
19512
|
+
fields[key] =
|
|
19513
|
+
typeof value === "string" ? value.substring(0, 100) : "file";
|
|
19514
|
+
}
|
|
19515
|
+
}
|
|
19516
|
+
this.trackEvent(eventName, {
|
|
19517
|
+
type: "form_submit",
|
|
19518
|
+
element: "form",
|
|
19519
|
+
fieldCount: Array.from(formData.entries()).length,
|
|
19520
|
+
fields,
|
|
19521
|
+
...metadata,
|
|
19522
|
+
});
|
|
19523
|
+
if (this.config.debug) {
|
|
19524
|
+
console.log(`[AutoTracker] Form submit tracked: ${eventName}`, metadata);
|
|
19525
|
+
}
|
|
19526
|
+
});
|
|
19527
|
+
}
|
|
19528
|
+
/**
|
|
19529
|
+
* Extrair metadata do elemento
|
|
19530
|
+
*/
|
|
19531
|
+
extractMetadata(element) {
|
|
19532
|
+
const metadata = {};
|
|
19533
|
+
// Extrair todos os data-meta-* attributes
|
|
19534
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
19535
|
+
if (attr.name.startsWith("data-meta-")) {
|
|
19536
|
+
const key = attr.name.replace("data-meta-", "");
|
|
19537
|
+
metadata[key] = attr.value;
|
|
19538
|
+
}
|
|
19539
|
+
});
|
|
19540
|
+
// Adicionar informações básicas
|
|
19541
|
+
if (element.id)
|
|
19542
|
+
metadata.elementId = element.id;
|
|
19543
|
+
if (element.className)
|
|
19544
|
+
metadata.elementClass = element.className;
|
|
19545
|
+
// Text content (limitado)
|
|
19546
|
+
const textContent = element.textContent?.trim();
|
|
19547
|
+
if (textContent && textContent.length > 0) {
|
|
19548
|
+
metadata.textContent = textContent.substring(0, 50);
|
|
19549
|
+
}
|
|
19550
|
+
// Position info
|
|
19551
|
+
const rect = element.getBoundingClientRect();
|
|
19552
|
+
metadata.elementPosition = {
|
|
19553
|
+
x: Math.round(rect.left),
|
|
19554
|
+
y: Math.round(rect.top),
|
|
19555
|
+
width: Math.round(rect.width),
|
|
19556
|
+
height: Math.round(rect.height),
|
|
19557
|
+
};
|
|
19558
|
+
return metadata;
|
|
19559
|
+
}
|
|
19560
|
+
/**
|
|
19561
|
+
* Verificar se um campo é sensível
|
|
19562
|
+
*/
|
|
19563
|
+
isSensitiveField(fieldName) {
|
|
19564
|
+
const sensitivePatterns = [
|
|
19565
|
+
/password/i,
|
|
19566
|
+
/pass/i,
|
|
19567
|
+
/pwd/i,
|
|
19568
|
+
/secret/i,
|
|
19569
|
+
/token/i,
|
|
19570
|
+
/api[_-]?key/i,
|
|
19571
|
+
/credit[_-]?card/i,
|
|
19572
|
+
/ssn/i,
|
|
19573
|
+
/social/i,
|
|
19574
|
+
/tax/i,
|
|
19575
|
+
];
|
|
19576
|
+
return sensitivePatterns.some((pattern) => pattern.test(fieldName));
|
|
19577
|
+
}
|
|
19578
|
+
/**
|
|
19579
|
+
* Enviar evento para o SDK
|
|
19580
|
+
*/
|
|
19581
|
+
trackEvent(eventName, metadata) {
|
|
19582
|
+
if (!this.sdkInstance)
|
|
19583
|
+
return;
|
|
19584
|
+
try {
|
|
19585
|
+
this.sdkInstance.trackCustomEvent(eventName, {
|
|
19586
|
+
autoTracked: true,
|
|
19587
|
+
timestamp: Date.now(),
|
|
19588
|
+
url: window.location.href,
|
|
19589
|
+
...metadata,
|
|
19590
|
+
});
|
|
19591
|
+
}
|
|
19592
|
+
catch (error) {
|
|
19593
|
+
if (this.config.debug) {
|
|
19594
|
+
console.error("[AutoTracker] Error sending event:", error);
|
|
19595
|
+
}
|
|
19596
|
+
}
|
|
19597
|
+
}
|
|
19598
|
+
/**
|
|
19599
|
+
* Configurar tracking
|
|
19600
|
+
*/
|
|
19601
|
+
configure(config) {
|
|
19602
|
+
this.config = { ...this.config, ...config };
|
|
19603
|
+
}
|
|
19604
|
+
/**
|
|
19605
|
+
* Obter estatísticas
|
|
19606
|
+
*/
|
|
19607
|
+
getStats() {
|
|
19608
|
+
return {
|
|
19609
|
+
observedElements: this.observedElements.size,
|
|
19610
|
+
config: this.config,
|
|
19611
|
+
};
|
|
19612
|
+
}
|
|
19613
|
+
}
|
|
19614
|
+
|
|
19615
|
+
/**
|
|
19616
|
+
* Visitor Persistence Manager for SDK
|
|
19617
|
+
* Implements client-side visitor identification with localStorage camouflage
|
|
19618
|
+
*/
|
|
19619
|
+
// Camuflaged storage keys to avoid detection/blocking
|
|
19620
|
+
const STORAGE_KEYS = {
|
|
19621
|
+
session: "__zp_s", // Session identifier
|
|
19622
|
+
visitor: "__zp_v", // Visitor identifier (camuflado)
|
|
19623
|
+
device: "__zp_d", // Device fingerprint cache
|
|
19624
|
+
prefs: "__zp_p", // User preferences (decoy storage)
|
|
19625
|
+
analytics: "__zp_a", // Analytics preferences (additional decoy)
|
|
19626
|
+
};
|
|
19627
|
+
/**
|
|
19628
|
+
* Multi-layer persistence manager with fallbacks
|
|
19629
|
+
*/
|
|
19630
|
+
class PersistenceManager {
|
|
19631
|
+
// 1. LocalStorage (primary) - Most persistent
|
|
19632
|
+
static setLocal(key, value) {
|
|
19633
|
+
try {
|
|
19634
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
19635
|
+
localStorage.setItem(key, value);
|
|
19636
|
+
return true;
|
|
19637
|
+
}
|
|
19638
|
+
}
|
|
19639
|
+
catch (e) {
|
|
19640
|
+
// Storage disabled/private mode
|
|
19641
|
+
}
|
|
19642
|
+
return false;
|
|
19643
|
+
}
|
|
19644
|
+
static getLocal(key) {
|
|
19645
|
+
try {
|
|
19646
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
19647
|
+
return localStorage.getItem(key);
|
|
19648
|
+
}
|
|
19649
|
+
}
|
|
19650
|
+
catch (e) {
|
|
19651
|
+
// Storage disabled/private mode
|
|
19652
|
+
}
|
|
19653
|
+
return null;
|
|
19654
|
+
}
|
|
19655
|
+
// 2. SessionStorage (secondary) - Session-only
|
|
19656
|
+
static setSession(key, value) {
|
|
19657
|
+
try {
|
|
19658
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
19659
|
+
sessionStorage.setItem(key, value);
|
|
19660
|
+
return true;
|
|
19661
|
+
}
|
|
19662
|
+
}
|
|
19663
|
+
catch (e) {
|
|
19664
|
+
// Storage disabled/private mode
|
|
19665
|
+
}
|
|
19666
|
+
return false;
|
|
19667
|
+
}
|
|
19668
|
+
static getSession(key) {
|
|
19669
|
+
try {
|
|
19670
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
19671
|
+
return sessionStorage.getItem(key);
|
|
19672
|
+
}
|
|
19673
|
+
}
|
|
19674
|
+
catch (e) {
|
|
19675
|
+
// Storage disabled/private mode
|
|
19676
|
+
}
|
|
19677
|
+
return null;
|
|
19678
|
+
}
|
|
19679
|
+
// 3. Cookie (tertiary) - Cross-session with expiration
|
|
19680
|
+
static setCookie(key, value, days = 365) {
|
|
19681
|
+
try {
|
|
19682
|
+
if (typeof document !== "undefined") {
|
|
19683
|
+
const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
|
|
19684
|
+
document.cookie = `${key}=${value}; expires=${expires}; path=/; SameSite=Lax`;
|
|
19685
|
+
return true;
|
|
19686
|
+
}
|
|
19687
|
+
}
|
|
19688
|
+
catch (e) {
|
|
19689
|
+
// Cookies disabled
|
|
19690
|
+
}
|
|
19691
|
+
return false;
|
|
19692
|
+
}
|
|
19693
|
+
static getCookie(key) {
|
|
19694
|
+
try {
|
|
19695
|
+
if (typeof document !== "undefined") {
|
|
19696
|
+
const name = key + "=";
|
|
19697
|
+
const decodedCookie = decodeURIComponent(document.cookie);
|
|
19698
|
+
const ca = decodedCookie.split(";");
|
|
19699
|
+
for (let i = 0; i < ca.length; i++) {
|
|
19700
|
+
let c = ca[i];
|
|
19701
|
+
if (c) {
|
|
19702
|
+
while (c.charAt(0) === " ") {
|
|
19703
|
+
c = c.substring(1);
|
|
19704
|
+
}
|
|
19705
|
+
if (c.indexOf(name) === 0) {
|
|
19706
|
+
return c.substring(name.length, c.length);
|
|
19707
|
+
}
|
|
19708
|
+
}
|
|
19709
|
+
}
|
|
19710
|
+
}
|
|
19711
|
+
}
|
|
19712
|
+
catch (e) {
|
|
19713
|
+
// Cookies disabled
|
|
19714
|
+
}
|
|
19715
|
+
return null;
|
|
19716
|
+
}
|
|
19717
|
+
// 4. Memory (fallback) - Current session only
|
|
19718
|
+
static setMemory(key, value) {
|
|
19719
|
+
this.memoryStore.set(key, value);
|
|
19720
|
+
return true;
|
|
19721
|
+
}
|
|
19722
|
+
static getMemory(key) {
|
|
19723
|
+
return this.memoryStore.get(key) || null;
|
|
19724
|
+
}
|
|
19725
|
+
// Multi-layer get with fallbacks
|
|
19726
|
+
static get(key) {
|
|
19727
|
+
// Try localStorage first (most persistent)
|
|
19728
|
+
let value = this.getLocal(key);
|
|
19729
|
+
if (value)
|
|
19730
|
+
return { value, method: "localStorage" };
|
|
19731
|
+
// Try sessionStorage (session-only)
|
|
19732
|
+
value = this.getSession(key);
|
|
19733
|
+
if (value)
|
|
19734
|
+
return { value, method: "sessionStorage" };
|
|
19735
|
+
// Try cookies (cross-session)
|
|
19736
|
+
value = this.getCookie(key);
|
|
19737
|
+
if (value)
|
|
19738
|
+
return { value, method: "cookie" };
|
|
19739
|
+
// Try memory (current session)
|
|
19740
|
+
value = this.getMemory(key);
|
|
19741
|
+
if (value)
|
|
19742
|
+
return { value, method: "memory" };
|
|
19743
|
+
return { value: null, method: "none" };
|
|
19744
|
+
}
|
|
19745
|
+
// Multi-layer set with fallbacks
|
|
19746
|
+
static set(key, value) {
|
|
19747
|
+
// Try localStorage first (most persistent)
|
|
19748
|
+
if (this.setLocal(key, value)) {
|
|
19749
|
+
return { success: true, method: "localStorage" };
|
|
19750
|
+
}
|
|
19751
|
+
// Try sessionStorage (session-only)
|
|
19752
|
+
if (this.setSession(key, value)) {
|
|
19753
|
+
return { success: true, method: "sessionStorage" };
|
|
19754
|
+
}
|
|
19755
|
+
// Try cookies (cross-session)
|
|
19756
|
+
if (this.setCookie(key, value)) {
|
|
19757
|
+
return { success: true, method: "cookie" };
|
|
19758
|
+
}
|
|
19759
|
+
// Fallback to memory (current session)
|
|
19760
|
+
this.setMemory(key, value);
|
|
19761
|
+
return { success: true, method: "memory" };
|
|
19762
|
+
}
|
|
19763
|
+
}
|
|
19764
|
+
PersistenceManager.memoryStore = new Map();
|
|
19765
|
+
/**
|
|
19766
|
+
* Advanced Visitor Identity Manager with Camouflaged Storage
|
|
19767
|
+
*/
|
|
19768
|
+
class VisitorIdentityManager {
|
|
19769
|
+
generateVisitorId() {
|
|
19770
|
+
return "vis_" + Array.from(crypto.getRandomValues(new Uint8Array(16)))
|
|
19771
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
19772
|
+
.join('');
|
|
19773
|
+
}
|
|
19774
|
+
generateSessionId() {
|
|
19775
|
+
return "ses_" + Array.from(crypto.getRandomValues(new Uint8Array(8)))
|
|
19776
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
19777
|
+
.join('') + "_" + Date.now().toString(36);
|
|
19778
|
+
}
|
|
19779
|
+
createCamouflageData(visitorId, sessionId, stableCoreHash) {
|
|
19780
|
+
return {
|
|
19781
|
+
theme: "auto",
|
|
19782
|
+
lang: (navigator.language || "en-US").substring(0, 2),
|
|
19783
|
+
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
19784
|
+
analytics: true,
|
|
19785
|
+
cookies: true,
|
|
19786
|
+
_v: visitorId, // Hidden visitor ID
|
|
19787
|
+
_s: sessionId, // Hidden session ID
|
|
19788
|
+
_sc: stableCoreHash, // Hidden stable core
|
|
19789
|
+
ts: Date.now(),
|
|
19790
|
+
};
|
|
19791
|
+
}
|
|
19792
|
+
extractFromCamouflage(data) {
|
|
19793
|
+
try {
|
|
19794
|
+
const parsed = JSON.parse(data);
|
|
19795
|
+
if (parsed._v && parsed._s && parsed._sc) {
|
|
19796
|
+
return {
|
|
19797
|
+
visitorId: parsed._v,
|
|
19798
|
+
sessionId: parsed._s,
|
|
19799
|
+
stableCoreHash: parsed._sc,
|
|
19800
|
+
persistenceMethod: "localStorage",
|
|
19801
|
+
confidence: 0.95,
|
|
19802
|
+
createdAt: parsed.ts || Date.now(),
|
|
19803
|
+
lastSeen: Date.now(),
|
|
19804
|
+
reused: true,
|
|
19805
|
+
};
|
|
19806
|
+
}
|
|
19807
|
+
}
|
|
19808
|
+
catch (e) {
|
|
19809
|
+
// Invalid data
|
|
19810
|
+
}
|
|
19811
|
+
return null;
|
|
19812
|
+
}
|
|
19813
|
+
async getOrCreateVisitorIdentity(params) {
|
|
19814
|
+
const { stableCoreHash } = params;
|
|
19815
|
+
// Try to recover existing identity from camouflaged storage
|
|
19816
|
+
const { value: storedData, method } = PersistenceManager.get(STORAGE_KEYS.prefs);
|
|
19817
|
+
if (storedData) {
|
|
19818
|
+
const existingIdentity = this.extractFromCamouflage(storedData);
|
|
19819
|
+
if (existingIdentity && existingIdentity.stableCoreHash === stableCoreHash) {
|
|
19820
|
+
// Update last seen time
|
|
19821
|
+
const updatedCamouflage = this.createCamouflageData(existingIdentity.visitorId, existingIdentity.sessionId, stableCoreHash);
|
|
19822
|
+
PersistenceManager.set(STORAGE_KEYS.prefs, JSON.stringify(updatedCamouflage));
|
|
19823
|
+
return {
|
|
19824
|
+
...existingIdentity,
|
|
19825
|
+
persistenceMethod: method,
|
|
19826
|
+
lastSeen: Date.now(),
|
|
19827
|
+
reused: true,
|
|
19828
|
+
};
|
|
19829
|
+
}
|
|
19830
|
+
}
|
|
19831
|
+
// Create new visitor identity
|
|
19832
|
+
const newVisitorId = this.generateVisitorId();
|
|
19833
|
+
const newSessionId = params.sessionId || this.generateSessionId();
|
|
19834
|
+
const newIdentity = {
|
|
19835
|
+
visitorId: newVisitorId,
|
|
19836
|
+
sessionId: newSessionId,
|
|
19837
|
+
stableCoreHash,
|
|
19838
|
+
deviceFingerprint: params.deviceFingerprint,
|
|
19839
|
+
persistenceMethod: "localStorage",
|
|
19840
|
+
confidence: 0.90,
|
|
19841
|
+
createdAt: Date.now(),
|
|
19842
|
+
lastSeen: Date.now(),
|
|
19843
|
+
reused: false,
|
|
19844
|
+
};
|
|
19845
|
+
// Store in camouflaged format
|
|
19846
|
+
const camouflageData = this.createCamouflageData(newVisitorId, newSessionId, stableCoreHash);
|
|
19847
|
+
const { method: storageMethod } = PersistenceManager.set(STORAGE_KEYS.prefs, JSON.stringify(camouflageData));
|
|
19848
|
+
newIdentity.persistenceMethod = storageMethod;
|
|
19849
|
+
// Also store device fingerprint cache for faster lookups
|
|
19850
|
+
PersistenceManager.set(STORAGE_KEYS.device, params.deviceFingerprint);
|
|
19851
|
+
return newIdentity;
|
|
19852
|
+
}
|
|
19853
|
+
// Get current visitor ID without creating new one
|
|
19854
|
+
getCurrentVisitorId() {
|
|
19855
|
+
const { value: storedData } = PersistenceManager.get(STORAGE_KEYS.prefs);
|
|
19856
|
+
if (storedData) {
|
|
19857
|
+
const identity = this.extractFromCamouflage(storedData);
|
|
19858
|
+
return identity?.visitorId || null;
|
|
19859
|
+
}
|
|
19860
|
+
return null;
|
|
19861
|
+
}
|
|
19862
|
+
// Clear all stored identity data
|
|
19863
|
+
clearIdentity() {
|
|
19864
|
+
// Remove from all storage layers
|
|
19865
|
+
try {
|
|
19866
|
+
PersistenceManager.setLocal(STORAGE_KEYS.prefs, "");
|
|
19867
|
+
PersistenceManager.setLocal(STORAGE_KEYS.device, "");
|
|
19868
|
+
PersistenceManager.setSession(STORAGE_KEYS.prefs, "");
|
|
19869
|
+
PersistenceManager.setSession(STORAGE_KEYS.device, "");
|
|
19870
|
+
PersistenceManager.setCookie(STORAGE_KEYS.prefs, "", -1); // Expire immediately
|
|
19871
|
+
PersistenceManager.setCookie(STORAGE_KEYS.device, "", -1);
|
|
19872
|
+
PersistenceManager.setMemory(STORAGE_KEYS.prefs, "");
|
|
19873
|
+
PersistenceManager.setMemory(STORAGE_KEYS.device, "");
|
|
19874
|
+
}
|
|
19875
|
+
catch (e) {
|
|
19876
|
+
// Silent fail
|
|
19877
|
+
}
|
|
19878
|
+
}
|
|
19152
19879
|
}
|
|
19153
19880
|
|
|
19154
19881
|
/**
|
|
@@ -19182,7 +19909,7 @@ const DEFAULT_CONFIG = {
|
|
|
19182
19909
|
*/
|
|
19183
19910
|
class ZaplierSDK {
|
|
19184
19911
|
constructor(userConfig) {
|
|
19185
|
-
this.version = "1.
|
|
19912
|
+
this.version = "1.6.0";
|
|
19186
19913
|
this.isInitialized = false;
|
|
19187
19914
|
this.eventQueue = [];
|
|
19188
19915
|
/**
|
|
@@ -19253,8 +19980,10 @@ class ZaplierSDK {
|
|
|
19253
19980
|
}
|
|
19254
19981
|
const sessionId = this.sessionId;
|
|
19255
19982
|
// When explicitly enabled, use 100% sample rate
|
|
19256
|
-
this.replayEngine = new SessionReplayEngine(sessionId, {
|
|
19983
|
+
this.replayEngine = new SessionReplayEngine(sessionId, this.backendVisitorId || 'unknown', {
|
|
19257
19984
|
sampleRate: 1.0, // Force 100% when explicitly enabled
|
|
19985
|
+
inactivityTimeout: 30000,
|
|
19986
|
+
pauseOnInactive: true,
|
|
19258
19987
|
});
|
|
19259
19988
|
// Connect to anti-adblock manager
|
|
19260
19989
|
if (this.antiAdblockManager) {
|
|
@@ -19279,8 +20008,10 @@ class ZaplierSDK {
|
|
|
19279
20008
|
this.sessionId = this.generateSessionId();
|
|
19280
20009
|
}
|
|
19281
20010
|
const sessionId = this.sessionId;
|
|
19282
|
-
this.replayEngine = new SessionReplayEngine(sessionId, {
|
|
20011
|
+
this.replayEngine = new SessionReplayEngine(sessionId, this.backendVisitorId || 'unknown', {
|
|
19283
20012
|
sampleRate: 1.0, // Force 100% when explicitly enabled
|
|
20013
|
+
inactivityTimeout: 30000,
|
|
20014
|
+
pauseOnInactive: true,
|
|
19284
20015
|
});
|
|
19285
20016
|
// Connect to anti-adblock manager
|
|
19286
20017
|
if (this.antiAdblockManager) {
|
|
@@ -19319,8 +20050,10 @@ class ZaplierSDK {
|
|
|
19319
20050
|
this.sessionId = this.generateSessionId();
|
|
19320
20051
|
}
|
|
19321
20052
|
const sessionId = this.sessionId;
|
|
19322
|
-
this.replayEngine = new SessionReplayEngine(sessionId, {
|
|
20053
|
+
this.replayEngine = new SessionReplayEngine(sessionId, this.backendVisitorId || 'unknown', {
|
|
19323
20054
|
sampleRate: 1.0, // Force recording when manually started
|
|
20055
|
+
inactivityTimeout: 30000,
|
|
20056
|
+
pauseOnInactive: true,
|
|
19324
20057
|
});
|
|
19325
20058
|
// Connect to anti-adblock manager
|
|
19326
20059
|
if (this.antiAdblockManager) {
|
|
@@ -19352,6 +20085,45 @@ class ZaplierSDK {
|
|
|
19352
20085
|
this.trackConversion("funnel_conversion", data.value, data.currency, data);
|
|
19353
20086
|
},
|
|
19354
20087
|
};
|
|
20088
|
+
/**
|
|
20089
|
+
* Auto Tracker API
|
|
20090
|
+
*/
|
|
20091
|
+
this.autoTrack = {
|
|
20092
|
+
enable: () => {
|
|
20093
|
+
if (!this.autoTracker) {
|
|
20094
|
+
this.initializeAutoTracker();
|
|
20095
|
+
}
|
|
20096
|
+
else {
|
|
20097
|
+
this.autoTracker.configure({ enabled: true });
|
|
20098
|
+
}
|
|
20099
|
+
if (this.config.debug) {
|
|
20100
|
+
console.log("[Zaplier] Auto tracking enabled");
|
|
20101
|
+
}
|
|
20102
|
+
},
|
|
20103
|
+
disable: () => {
|
|
20104
|
+
if (this.autoTracker) {
|
|
20105
|
+
this.autoTracker.stop();
|
|
20106
|
+
this.autoTracker = undefined;
|
|
20107
|
+
}
|
|
20108
|
+
if (this.config.debug) {
|
|
20109
|
+
console.log("[Zaplier] Auto tracking disabled");
|
|
20110
|
+
}
|
|
20111
|
+
},
|
|
20112
|
+
configure: (config) => {
|
|
20113
|
+
if (this.autoTracker) {
|
|
20114
|
+
this.autoTracker.configure(config);
|
|
20115
|
+
}
|
|
20116
|
+
if (this.config.debug) {
|
|
20117
|
+
console.log("[Zaplier] Auto tracking configured:", config);
|
|
20118
|
+
}
|
|
20119
|
+
},
|
|
20120
|
+
getStats: () => {
|
|
20121
|
+
return this.autoTracker ? this.autoTracker.getStats() : null;
|
|
20122
|
+
},
|
|
20123
|
+
isEnabled: () => {
|
|
20124
|
+
return !!this.autoTracker;
|
|
20125
|
+
},
|
|
20126
|
+
};
|
|
19355
20127
|
// Validate required config
|
|
19356
20128
|
if (!userConfig.token) {
|
|
19357
20129
|
throw new Error("Zaplier: token is required");
|
|
@@ -19393,6 +20165,8 @@ class ZaplierSDK {
|
|
|
19393
20165
|
this.initializeAntiAdblock();
|
|
19394
20166
|
// Then initialize tracking engines (they'll connect to anti-adblock)
|
|
19395
20167
|
this.initializeTrackingEngines();
|
|
20168
|
+
// Initialize auto tracker
|
|
20169
|
+
this.initializeAutoTracker();
|
|
19396
20170
|
// Process queued events
|
|
19397
20171
|
this.processEventQueue();
|
|
19398
20172
|
if (this.config.debug) {
|
|
@@ -19423,8 +20197,10 @@ class ZaplierSDK {
|
|
|
19423
20197
|
// When replay is explicitly enabled, use 100% sample rate to ensure recording
|
|
19424
20198
|
// The default replaySampling (0.1) is only for automatic/production sampling
|
|
19425
20199
|
const sampleRate = this.config.replay === true ? 1.0 : this.config.replaySampling;
|
|
19426
|
-
this.replayEngine = new SessionReplayEngine(this.sessionId, {
|
|
20200
|
+
this.replayEngine = new SessionReplayEngine(this.sessionId, this.backendVisitorId || 'unknown', {
|
|
19427
20201
|
sampleRate: sampleRate,
|
|
20202
|
+
inactivityTimeout: 30000,
|
|
20203
|
+
pauseOnInactive: true,
|
|
19428
20204
|
});
|
|
19429
20205
|
// Connect SDK instance for transport
|
|
19430
20206
|
this.replayEngine.setSDKInstance(this);
|
|
@@ -19459,6 +20235,29 @@ class ZaplierSDK {
|
|
|
19459
20235
|
console.error("[Zaplier] Failed to initialize tracking engines:", error);
|
|
19460
20236
|
}
|
|
19461
20237
|
}
|
|
20238
|
+
/**
|
|
20239
|
+
* Initialize Auto Tracker for data-* attributes
|
|
20240
|
+
*/
|
|
20241
|
+
initializeAutoTracker() {
|
|
20242
|
+
try {
|
|
20243
|
+
this.autoTracker = new AutoTracker(this, {
|
|
20244
|
+
enabled: true,
|
|
20245
|
+
trackClicks: true,
|
|
20246
|
+
trackScrolls: true,
|
|
20247
|
+
trackViews: true,
|
|
20248
|
+
trackHovers: true,
|
|
20249
|
+
trackForms: true,
|
|
20250
|
+
debug: this.config.debug,
|
|
20251
|
+
});
|
|
20252
|
+
this.autoTracker.start();
|
|
20253
|
+
if (this.config.debug) {
|
|
20254
|
+
console.log("[Zaplier] Auto Tracker initialized and started");
|
|
20255
|
+
}
|
|
20256
|
+
}
|
|
20257
|
+
catch (error) {
|
|
20258
|
+
console.error("[Zaplier] Failed to initialize Auto Tracker:", error);
|
|
20259
|
+
}
|
|
20260
|
+
}
|
|
19462
20261
|
/**
|
|
19463
20262
|
* Generate session ID
|
|
19464
20263
|
*/
|
|
@@ -19514,10 +20313,12 @@ class ZaplierSDK {
|
|
|
19514
20313
|
}
|
|
19515
20314
|
/**
|
|
19516
20315
|
* Collect fingerprint and generate visitor ID (IMPROVED - 2024)
|
|
19517
|
-
* Enhanced with better incognito detection
|
|
20316
|
+
* Enhanced with better incognito detection and localStorage persistence
|
|
19518
20317
|
*/
|
|
19519
20318
|
async collectFingerprint() {
|
|
19520
20319
|
try {
|
|
20320
|
+
// Initialize visitor identity manager
|
|
20321
|
+
this.visitorIdentityManager = new VisitorIdentityManager();
|
|
19521
20322
|
// Use appropriate fingerprinting based on GDPR mode
|
|
19522
20323
|
const result = this.config.gdprMode
|
|
19523
20324
|
? await getLightweightFingerprint()
|
|
@@ -19535,6 +20336,26 @@ class ZaplierSDK {
|
|
|
19535
20336
|
sdkVersion: this.version,
|
|
19536
20337
|
};
|
|
19537
20338
|
}
|
|
20339
|
+
// Generate or retrieve visitor identity using persistent storage
|
|
20340
|
+
const visitorIdentity = await this.visitorIdentityManager.getOrCreateVisitorIdentity({
|
|
20341
|
+
fingerprintHash: result.data.hash,
|
|
20342
|
+
stableCoreHash: result.data.stableCoreHash,
|
|
20343
|
+
deviceFingerprint: result.data.hash,
|
|
20344
|
+
sessionId: this.sessionId,
|
|
20345
|
+
userAgent: navigator.userAgent,
|
|
20346
|
+
ipAddress: undefined, // Will be determined server-side
|
|
20347
|
+
});
|
|
20348
|
+
// Set the persistent visitor ID
|
|
20349
|
+
this.visitorId = visitorIdentity.visitorId;
|
|
20350
|
+
if (this.config.debug) {
|
|
20351
|
+
console.log("[Zaplier] Visitor identity resolved:", {
|
|
20352
|
+
visitorId: this.visitorId,
|
|
20353
|
+
sessionId: this.sessionId,
|
|
20354
|
+
persistenceMethod: visitorIdentity.persistenceMethod,
|
|
20355
|
+
confidence: visitorIdentity.confidence,
|
|
20356
|
+
isNewVisitor: !visitorIdentity.reused,
|
|
20357
|
+
});
|
|
20358
|
+
}
|
|
19538
20359
|
if (this.config.debug) {
|
|
19539
20360
|
console.log("[Zaplier] Fingerprint collected:", {
|
|
19540
20361
|
components: result.collectedComponents,
|
|
@@ -19641,6 +20462,10 @@ class ZaplierSDK {
|
|
|
19641
20462
|
try {
|
|
19642
20463
|
const payload = {
|
|
19643
20464
|
// workspaceId moved to query parameter
|
|
20465
|
+
// Visitor ID (persistido via localStorage camuflado)
|
|
20466
|
+
visitorId: this.visitorId,
|
|
20467
|
+
// Session ID (gerado por sessão)
|
|
20468
|
+
sessionId: this.sessionId,
|
|
19644
20469
|
fingerprintHash: this.fingerprint?.hash,
|
|
19645
20470
|
stableCoreHash: this.fingerprint?.stableCoreHash,
|
|
19646
20471
|
stableCoreVector: this.fingerprint?.stableCoreVector,
|
|
@@ -20401,6 +21226,44 @@ const Zaplier = {
|
|
|
20401
21226
|
globalInstance.replay.markConversion(data);
|
|
20402
21227
|
}
|
|
20403
21228
|
},
|
|
21229
|
+
/**
|
|
21230
|
+
* Auto Tracker API
|
|
21231
|
+
*/
|
|
21232
|
+
autoTrack: {
|
|
21233
|
+
enable: () => {
|
|
21234
|
+
if (!globalInstance) {
|
|
21235
|
+
console.warn('[Zaplier] SDK not initialized. Call Zaplier.init() first');
|
|
21236
|
+
return;
|
|
21237
|
+
}
|
|
21238
|
+
globalInstance.autoTrack.enable();
|
|
21239
|
+
},
|
|
21240
|
+
disable: () => {
|
|
21241
|
+
if (!globalInstance) {
|
|
21242
|
+
console.warn('[Zaplier] SDK not initialized. Call Zaplier.init() first');
|
|
21243
|
+
return;
|
|
21244
|
+
}
|
|
21245
|
+
globalInstance.autoTrack.disable();
|
|
21246
|
+
},
|
|
21247
|
+
configure: (config) => {
|
|
21248
|
+
if (!globalInstance) {
|
|
21249
|
+
console.warn('[Zaplier] SDK not initialized. Call Zaplier.init() first');
|
|
21250
|
+
return;
|
|
21251
|
+
}
|
|
21252
|
+
globalInstance.autoTrack.configure(config);
|
|
21253
|
+
},
|
|
21254
|
+
getStats: () => {
|
|
21255
|
+
if (!globalInstance) {
|
|
21256
|
+
return null;
|
|
21257
|
+
}
|
|
21258
|
+
return globalInstance.autoTrack.getStats();
|
|
21259
|
+
},
|
|
21260
|
+
isEnabled: () => {
|
|
21261
|
+
if (!globalInstance) {
|
|
21262
|
+
return false;
|
|
21263
|
+
}
|
|
21264
|
+
return globalInstance.autoTrack.isEnabled();
|
|
21265
|
+
}
|
|
21266
|
+
},
|
|
20404
21267
|
/**
|
|
20405
21268
|
* Debug and utility functions
|
|
20406
21269
|
*/
|
|
@@ -20427,7 +21290,7 @@ const Zaplier = {
|
|
|
20427
21290
|
/**
|
|
20428
21291
|
* Version info
|
|
20429
21292
|
*/
|
|
20430
|
-
version: '
|
|
21293
|
+
version: '1.6.0'
|
|
20431
21294
|
};
|
|
20432
21295
|
/**
|
|
20433
21296
|
* Auto-initialization from script tag data attributes
|