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