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