@zaplier/sdk 1.6.1 → 1.6.3

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.d.ts CHANGED
@@ -748,6 +748,7 @@ declare class ZaplierSDK implements ZaplierSDK$1 {
748
748
  private heatmapEngine?;
749
749
  private antiAdblockManager?;
750
750
  private autoTracker?;
751
+ private visitorIdentityManager?;
751
752
  constructor(userConfig: SDKConfig);
752
753
  /**
753
754
  * Initialize SDK
@@ -776,7 +777,7 @@ declare class ZaplierSDK implements ZaplierSDK$1 {
776
777
  private initializeAntiAdblock;
777
778
  /**
778
779
  * Collect fingerprint and generate visitor ID (IMPROVED - 2024)
779
- * Enhanced with better incognito detection
780
+ * Enhanced with better incognito detection and localStorage persistence
780
781
  */
781
782
  private collectFingerprint;
782
783
  /**
@@ -1355,6 +1356,7 @@ declare function hash32(input: string): string;
1355
1356
  declare function hashFingerprint(components: Record<string, any>, debug?: boolean): string;
1356
1357
  /**
1357
1358
  * Generate a visitor ID from fingerprint hash
1359
+ * Returns a proper UUID v4 format for database compatibility
1358
1360
  */
1359
1361
  declare function generateVisitorId(fingerprintHash: string): string;
1360
1362
 
package/dist/index.esm.js CHANGED
@@ -199,10 +199,19 @@ function hashFingerprint(components, debug = false) {
199
199
  }
200
200
  /**
201
201
  * Generate a visitor ID from fingerprint hash
202
+ * Returns a proper UUID v4 format for database compatibility
202
203
  */
203
204
  function generateVisitorId(fingerprintHash) {
204
- // Use first 16 characters of the hash for visitor ID
205
- return "vis_" + fingerprintHash.substring(0, 16);
205
+ // Generate UUID v4 format from fingerprint hash
206
+ const hash = fingerprintHash.length >= 32 ? fingerprintHash : (fingerprintHash + fingerprintHash + fingerprintHash + fingerprintHash).substring(0, 32);
207
+ // Extract bytes for UUID construction
208
+ const hex = hash.substring(0, 32);
209
+ // Set version 4 and variant bits according to RFC 4122
210
+ const chars = hex.split('');
211
+ chars[12] = '4'; // Version 4
212
+ chars[16] = (parseInt(chars[16] || '0', 16) & 0x3 | 0x8).toString(16); // Variant 10
213
+ // Format as UUID: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
214
+ return `${chars.slice(0, 8).join('')}-${chars.slice(8, 12).join('')}-${chars.slice(12, 16).join('')}-${chars.slice(16, 20).join('')}-${chars.slice(20, 32).join('')}`;
206
215
  }
207
216
  /**
208
217
  * Check if a key represents an unstable component that should be excluded from hashing
@@ -19608,6 +19617,292 @@ class AutoTracker {
19608
19617
  }
19609
19618
  }
19610
19619
 
19620
+ /**
19621
+ * Visitor Persistence Manager for SDK
19622
+ * Implements client-side visitor identification with localStorage camouflage
19623
+ */
19624
+ // Camuflaged storage keys to avoid detection/blocking
19625
+ const STORAGE_KEYS = {
19626
+ session: "__zp_s", // Session identifier
19627
+ visitor: "__zp_v", // Visitor identifier (camuflado)
19628
+ device: "__zp_d", // Device fingerprint cache
19629
+ prefs: "__zp_p", // User preferences (decoy storage)
19630
+ analytics: "__zp_a", // Analytics preferences (additional decoy)
19631
+ };
19632
+ /**
19633
+ * Multi-layer persistence manager with fallbacks
19634
+ */
19635
+ class PersistenceManager {
19636
+ // 1. LocalStorage (primary) - Most persistent
19637
+ static setLocal(key, value) {
19638
+ try {
19639
+ if (typeof window !== "undefined" && window.localStorage) {
19640
+ localStorage.setItem(key, value);
19641
+ return true;
19642
+ }
19643
+ }
19644
+ catch (e) {
19645
+ // Storage disabled/private mode
19646
+ }
19647
+ return false;
19648
+ }
19649
+ static getLocal(key) {
19650
+ try {
19651
+ if (typeof window !== "undefined" && window.localStorage) {
19652
+ return localStorage.getItem(key);
19653
+ }
19654
+ }
19655
+ catch (e) {
19656
+ // Storage disabled/private mode
19657
+ }
19658
+ return null;
19659
+ }
19660
+ // 2. SessionStorage (secondary) - Session-only
19661
+ static setSession(key, value) {
19662
+ try {
19663
+ if (typeof window !== "undefined" && window.sessionStorage) {
19664
+ sessionStorage.setItem(key, value);
19665
+ return true;
19666
+ }
19667
+ }
19668
+ catch (e) {
19669
+ // Storage disabled/private mode
19670
+ }
19671
+ return false;
19672
+ }
19673
+ static getSession(key) {
19674
+ try {
19675
+ if (typeof window !== "undefined" && window.sessionStorage) {
19676
+ return sessionStorage.getItem(key);
19677
+ }
19678
+ }
19679
+ catch (e) {
19680
+ // Storage disabled/private mode
19681
+ }
19682
+ return null;
19683
+ }
19684
+ // 3. Cookie (tertiary) - Cross-session with expiration
19685
+ static setCookie(key, value, days = 365) {
19686
+ try {
19687
+ if (typeof document !== "undefined") {
19688
+ const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
19689
+ document.cookie = `${key}=${value}; expires=${expires}; path=/; SameSite=Lax`;
19690
+ return true;
19691
+ }
19692
+ }
19693
+ catch (e) {
19694
+ // Cookies disabled
19695
+ }
19696
+ return false;
19697
+ }
19698
+ static getCookie(key) {
19699
+ try {
19700
+ if (typeof document !== "undefined") {
19701
+ const name = key + "=";
19702
+ const decodedCookie = decodeURIComponent(document.cookie);
19703
+ const ca = decodedCookie.split(";");
19704
+ for (let i = 0; i < ca.length; i++) {
19705
+ let c = ca[i];
19706
+ if (c) {
19707
+ while (c.charAt(0) === " ") {
19708
+ c = c.substring(1);
19709
+ }
19710
+ if (c.indexOf(name) === 0) {
19711
+ return c.substring(name.length, c.length);
19712
+ }
19713
+ }
19714
+ }
19715
+ }
19716
+ }
19717
+ catch (e) {
19718
+ // Cookies disabled
19719
+ }
19720
+ return null;
19721
+ }
19722
+ // 4. Memory (fallback) - Current session only
19723
+ static setMemory(key, value) {
19724
+ this.memoryStore.set(key, value);
19725
+ return true;
19726
+ }
19727
+ static getMemory(key) {
19728
+ return this.memoryStore.get(key) || null;
19729
+ }
19730
+ // Multi-layer get with fallbacks
19731
+ static get(key) {
19732
+ // Try localStorage first (most persistent)
19733
+ let value = this.getLocal(key);
19734
+ if (value)
19735
+ return { value, method: "localStorage" };
19736
+ // Try sessionStorage (session-only)
19737
+ value = this.getSession(key);
19738
+ if (value)
19739
+ return { value, method: "sessionStorage" };
19740
+ // Try cookies (cross-session)
19741
+ value = this.getCookie(key);
19742
+ if (value)
19743
+ return { value, method: "cookie" };
19744
+ // Try memory (current session)
19745
+ value = this.getMemory(key);
19746
+ if (value)
19747
+ return { value, method: "memory" };
19748
+ return { value: null, method: "none" };
19749
+ }
19750
+ // Multi-layer set with fallbacks
19751
+ static set(key, value) {
19752
+ // Try localStorage first (most persistent)
19753
+ if (this.setLocal(key, value)) {
19754
+ return { success: true, method: "localStorage" };
19755
+ }
19756
+ // Try sessionStorage (session-only)
19757
+ if (this.setSession(key, value)) {
19758
+ return { success: true, method: "sessionStorage" };
19759
+ }
19760
+ // Try cookies (cross-session)
19761
+ if (this.setCookie(key, value)) {
19762
+ return { success: true, method: "cookie" };
19763
+ }
19764
+ // Fallback to memory (current session)
19765
+ this.setMemory(key, value);
19766
+ return { success: true, method: "memory" };
19767
+ }
19768
+ }
19769
+ PersistenceManager.memoryStore = new Map();
19770
+ /**
19771
+ * Advanced Visitor Identity Manager with Camouflaged Storage
19772
+ */
19773
+ class VisitorIdentityManager {
19774
+ generateVisitorId() {
19775
+ // Generate UUID v4 format instead of vis_ prefix
19776
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
19777
+ // Set version 4
19778
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
19779
+ // Set variant
19780
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
19781
+ const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
19782
+ return `${hex.substr(0, 8)}-${hex.substr(8, 4)}-${hex.substr(12, 4)}-${hex.substr(16, 4)}-${hex.substr(20, 12)}`;
19783
+ }
19784
+ generateVisitorIdFromHash(hash) {
19785
+ // Generate deterministic UUID v4 format from fingerprint hash
19786
+ // This ensures same fingerprint = same visitor ID
19787
+ const hashExtended = hash.length >= 32 ? hash : (hash + hash + hash + hash).substring(0, 32);
19788
+ // Extract hex characters for UUID construction
19789
+ const hex = hashExtended.substring(0, 32);
19790
+ const chars = hex.split('');
19791
+ // Set version 4 and variant bits according to RFC 4122
19792
+ chars[12] = '4'; // Version 4
19793
+ chars[16] = (parseInt(chars[16] || '0', 16) & 0x3 | 0x8).toString(16); // Variant 10
19794
+ // Format as UUID: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
19795
+ return `${chars.slice(0, 8).join('')}-${chars.slice(8, 12).join('')}-${chars.slice(12, 16).join('')}-${chars.slice(16, 20).join('')}-${chars.slice(20, 32).join('')}`;
19796
+ }
19797
+ generateSessionId() {
19798
+ return "ses_" + Array.from(crypto.getRandomValues(new Uint8Array(8)))
19799
+ .map(b => b.toString(16).padStart(2, '0'))
19800
+ .join('') + "_" + Date.now().toString(36);
19801
+ }
19802
+ createCamouflageData(visitorId, sessionId, stableCoreHash) {
19803
+ return {
19804
+ theme: "auto",
19805
+ lang: (navigator.language || "en-US").substring(0, 2),
19806
+ tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
19807
+ analytics: true,
19808
+ cookies: true,
19809
+ _v: visitorId, // Hidden visitor ID
19810
+ _s: sessionId, // Hidden session ID
19811
+ _sc: stableCoreHash, // Hidden stable core
19812
+ ts: Date.now(),
19813
+ };
19814
+ }
19815
+ extractFromCamouflage(data) {
19816
+ try {
19817
+ const parsed = JSON.parse(data);
19818
+ if (parsed._v && parsed._s && parsed._sc) {
19819
+ return {
19820
+ visitorId: parsed._v,
19821
+ sessionId: parsed._s,
19822
+ stableCoreHash: parsed._sc,
19823
+ persistenceMethod: "localStorage",
19824
+ confidence: 0.95,
19825
+ createdAt: parsed.ts || Date.now(),
19826
+ lastSeen: Date.now(),
19827
+ reused: true,
19828
+ };
19829
+ }
19830
+ }
19831
+ catch (e) {
19832
+ // Invalid data
19833
+ }
19834
+ return null;
19835
+ }
19836
+ async getOrCreateVisitorIdentity(params) {
19837
+ const { stableCoreHash } = params;
19838
+ // Try to recover existing identity from camouflaged storage
19839
+ const { value: storedData, method } = PersistenceManager.get(STORAGE_KEYS.prefs);
19840
+ if (storedData) {
19841
+ const existingIdentity = this.extractFromCamouflage(storedData);
19842
+ if (existingIdentity && existingIdentity.stableCoreHash === stableCoreHash) {
19843
+ // Update last seen time
19844
+ const updatedCamouflage = this.createCamouflageData(existingIdentity.visitorId, existingIdentity.sessionId, stableCoreHash);
19845
+ PersistenceManager.set(STORAGE_KEYS.prefs, JSON.stringify(updatedCamouflage));
19846
+ return {
19847
+ ...existingIdentity,
19848
+ persistenceMethod: method,
19849
+ lastSeen: Date.now(),
19850
+ reused: true,
19851
+ };
19852
+ }
19853
+ }
19854
+ // Create new visitor identity (deterministic from fingerprint)
19855
+ // Use stableCoreHash for deterministic visitor ID generation
19856
+ // This ensures same fingerprint = same visitor ID across devices and sessions
19857
+ const deterministicVisitorId = this.generateVisitorIdFromHash(params.stableCoreHash);
19858
+ const newSessionId = params.sessionId || this.generateSessionId();
19859
+ const newIdentity = {
19860
+ visitorId: deterministicVisitorId,
19861
+ sessionId: newSessionId,
19862
+ stableCoreHash,
19863
+ deviceFingerprint: params.deviceFingerprint,
19864
+ persistenceMethod: "localStorage",
19865
+ confidence: 0.90,
19866
+ createdAt: Date.now(),
19867
+ lastSeen: Date.now(),
19868
+ reused: false,
19869
+ };
19870
+ // Store in camouflaged format
19871
+ const camouflageData = this.createCamouflageData(deterministicVisitorId, newSessionId, stableCoreHash);
19872
+ const { method: storageMethod } = PersistenceManager.set(STORAGE_KEYS.prefs, JSON.stringify(camouflageData));
19873
+ newIdentity.persistenceMethod = storageMethod;
19874
+ // Also store device fingerprint cache for faster lookups
19875
+ PersistenceManager.set(STORAGE_KEYS.device, params.deviceFingerprint);
19876
+ return newIdentity;
19877
+ }
19878
+ // Get current visitor ID without creating new one
19879
+ getCurrentVisitorId() {
19880
+ const { value: storedData } = PersistenceManager.get(STORAGE_KEYS.prefs);
19881
+ if (storedData) {
19882
+ const identity = this.extractFromCamouflage(storedData);
19883
+ return identity?.visitorId || null;
19884
+ }
19885
+ return null;
19886
+ }
19887
+ // Clear all stored identity data
19888
+ clearIdentity() {
19889
+ // Remove from all storage layers
19890
+ try {
19891
+ PersistenceManager.setLocal(STORAGE_KEYS.prefs, "");
19892
+ PersistenceManager.setLocal(STORAGE_KEYS.device, "");
19893
+ PersistenceManager.setSession(STORAGE_KEYS.prefs, "");
19894
+ PersistenceManager.setSession(STORAGE_KEYS.device, "");
19895
+ PersistenceManager.setCookie(STORAGE_KEYS.prefs, "", -1); // Expire immediately
19896
+ PersistenceManager.setCookie(STORAGE_KEYS.device, "", -1);
19897
+ PersistenceManager.setMemory(STORAGE_KEYS.prefs, "");
19898
+ PersistenceManager.setMemory(STORAGE_KEYS.device, "");
19899
+ }
19900
+ catch (e) {
19901
+ // Silent fail
19902
+ }
19903
+ }
19904
+ }
19905
+
19611
19906
  /**
19612
19907
  * Zaplier SDK v1.0.0
19613
19908
  * 100% Cookieless Analytics Tracking
@@ -20043,10 +20338,12 @@ class ZaplierSDK {
20043
20338
  }
20044
20339
  /**
20045
20340
  * Collect fingerprint and generate visitor ID (IMPROVED - 2024)
20046
- * Enhanced with better incognito detection
20341
+ * Enhanced with better incognito detection and localStorage persistence
20047
20342
  */
20048
20343
  async collectFingerprint() {
20049
20344
  try {
20345
+ // Initialize visitor identity manager
20346
+ this.visitorIdentityManager = new VisitorIdentityManager();
20050
20347
  // Use appropriate fingerprinting based on GDPR mode
20051
20348
  const result = this.config.gdprMode
20052
20349
  ? await getLightweightFingerprint()
@@ -20064,6 +20361,26 @@ class ZaplierSDK {
20064
20361
  sdkVersion: this.version,
20065
20362
  };
20066
20363
  }
20364
+ // Generate or retrieve visitor identity using persistent storage
20365
+ const visitorIdentity = await this.visitorIdentityManager.getOrCreateVisitorIdentity({
20366
+ fingerprintHash: result.data.hash,
20367
+ stableCoreHash: result.data.stableCoreHash,
20368
+ deviceFingerprint: result.data.hash,
20369
+ sessionId: this.sessionId,
20370
+ userAgent: navigator.userAgent,
20371
+ ipAddress: undefined, // Will be determined server-side
20372
+ });
20373
+ // Set the persistent visitor ID
20374
+ this.visitorId = visitorIdentity.visitorId;
20375
+ if (this.config.debug) {
20376
+ console.log("[Zaplier] Visitor identity resolved:", {
20377
+ visitorId: this.visitorId,
20378
+ sessionId: this.sessionId,
20379
+ persistenceMethod: visitorIdentity.persistenceMethod,
20380
+ confidence: visitorIdentity.confidence,
20381
+ isNewVisitor: !visitorIdentity.reused,
20382
+ });
20383
+ }
20067
20384
  if (this.config.debug) {
20068
20385
  console.log("[Zaplier] Fingerprint collected:", {
20069
20386
  components: result.collectedComponents,
@@ -20170,6 +20487,10 @@ class ZaplierSDK {
20170
20487
  try {
20171
20488
  const payload = {
20172
20489
  // workspaceId moved to query parameter
20490
+ // Visitor ID (persistido via localStorage camuflado)
20491
+ visitorId: this.visitorId,
20492
+ // Session ID (gerado por sessão)
20493
+ sessionId: this.sessionId,
20173
20494
  fingerprintHash: this.fingerprint?.hash,
20174
20495
  stableCoreHash: this.fingerprint?.stableCoreHash,
20175
20496
  stableCoreVector: this.fingerprint?.stableCoreVector,