@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.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
- // Simple event capture
19042
- this.events.push(event);
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
- console.log("[Zaplier] Session replay started - simple mode");
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
- // Simple payload structure like official example
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() - (performance.timeOrigin || 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.3.7";
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: '3.0.0'
21289
+ version: '1.6.0'
20427
21290
  };
20428
21291
  /**
20429
21292
  * Auto-initialization from script tag data attributes