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