/** * Storage helper for data element storage duration * Handles SESSION (in-memory) and VISITOR (AsyncStorage with 30-day expiry) storage */ import AsyncStorage from '@react-native-async-storage/async-storage'; import { DeStorageDuration } from '../models/mp-client-sdk'; import { Logger } from './logger'; // AsyncStorage key for visitor-scoped data elements const VISITOR_STORE_KEY = '_mp_de_visitor_store'; // 30 days in milliseconds const VISITOR_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000; // Interface for stored visitor values (includes timestamp for expiry check) interface StoredVisitorValue { value: any; timestamp: number; } export class StorageHelper { // In-memory store for SESSION-scoped values (cleared on app restart) private static sessionStore: Record = {}; // In-memory cache of visitor store (loaded from AsyncStorage on init) private static visitorStoreCache: Record = {}; // Flag to track if initialized private static isInitialized = false; /** * Initialize the storage helper * Loads visitor store from AsyncStorage and cleans expired values * Should be called once during SDK initialization */ static async initialize(): Promise { if (this.isInitialized) { return; } try { // Load visitor store from AsyncStorage const storedData = await AsyncStorage.getItem(VISITOR_STORE_KEY); if (storedData) { this.visitorStoreCache = JSON.parse(storedData); Logger.logDbg( 'StorageHelper: Loaded visitor store with', Object.keys(this.visitorStoreCache).length, 'items' ); } // Clean expired visitor data await this.clearExpiredVisitorData(); this.isInitialized = true; Logger.logDbg('StorageHelper: Initialized successfully'); } catch (err) { Logger.logError('StorageHelper: Error initializing', err); // Reset to empty state on error this.visitorStoreCache = {}; this.isInitialized = true; } } /** * Clear visitor-scoped values older than 30 days */ static async clearExpiredVisitorData(): Promise { try { const now = Date.now(); const keysToRemove: string[] = []; for (const key of Object.keys(this.visitorStoreCache)) { const entry = this.visitorStoreCache[key]; if (entry && now - entry.timestamp > VISITOR_EXPIRY_MS) { keysToRemove.push(key); } } if (keysToRemove.length > 0) { for (const key of keysToRemove) { delete this.visitorStoreCache[key]; } // Persist the cleaned store await this.persistVisitorStore(); Logger.logDbg( 'StorageHelper: Cleared', keysToRemove.length, 'expired visitor values' ); } } catch (err) { Logger.logError('StorageHelper: Error clearing expired data', err); } } /** * Persist the visitor store cache to AsyncStorage */ private static async persistVisitorStore(): Promise { try { await AsyncStorage.setItem( VISITOR_STORE_KEY, JSON.stringify(this.visitorStoreCache) ); } catch (err) { Logger.logError('StorageHelper: Error persisting visitor store', err); } } /** * Check if a value is defined (not null, undefined, or empty string) */ private static isDefined(value: any): boolean { return value !== null && value !== undefined && value !== ''; } /** * Store a data element value based on storage duration * @param deKey - The data element key * @param value - The value to store * @param storageDuration - The storage duration type */ static deStorageDurationSet( deKey: string, value: any, storageDuration: DeStorageDuration ): void { try { if (!this.isDefined(deKey) || !this.isDefined(value)) { // Key and value are required return; } if (!storageDuration) { storageDuration = DeStorageDuration.EVENT; } switch (storageDuration) { case DeStorageDuration.SESSION: // Store in memory (cleared on app restart) this.sessionStore[deKey] = value; Logger.logDbg('StorageHelper: Stored SESSION value for', deKey); break; case DeStorageDuration.VISITOR: // Store in AsyncStorage cache with timestamp this.visitorStoreCache[deKey] = { value: value, timestamp: Date.now(), }; // Persist to AsyncStorage in background this.persistVisitorStore().catch((err) => { Logger.logError( 'StorageHelper: Error persisting visitor value', err ); }); Logger.logDbg('StorageHelper: Stored VISITOR value for', deKey); break; case DeStorageDuration.EVENT: case DeStorageDuration.NONE: default: // No persistence needed for EVENT or NONE break; } } catch (err) { Logger.logError('StorageHelper: Error in deStorageDurationSet', err); } } /** * Retrieve a data element value based on storage duration * @param deKey - The data element key * @param defaultValue - Default value if not found * @param storageDuration - The storage duration type * @returns The stored value or default value */ static deStorageDurationGet( deKey: string, defaultValue: any, storageDuration: DeStorageDuration ): any { try { if (!this.isDefined(deKey)) { return defaultValue; } if (!storageDuration) { storageDuration = DeStorageDuration.EVENT; } switch (storageDuration) { case DeStorageDuration.SESSION: { // Get from in-memory session store const sessionValue = this.sessionStore[deKey]; if (this.isDefined(sessionValue)) { return sessionValue; } return defaultValue; } case DeStorageDuration.VISITOR: { // Get from visitor store cache const visitorEntry = this.visitorStoreCache[deKey]; if (visitorEntry && this.isDefined(visitorEntry.value)) { // Check if not expired if (Date.now() - visitorEntry.timestamp <= VISITOR_EXPIRY_MS) { return visitorEntry.value; } else { // Value is expired, remove it delete this.visitorStoreCache[deKey]; this.persistVisitorStore().catch((err) => { Logger.logError( 'StorageHelper: Error removing expired value', err ); }); } } return defaultValue; } case DeStorageDuration.EVENT: case DeStorageDuration.NONE: default: // No retrieval for EVENT or NONE return defaultValue; } } catch (err) { Logger.logError('StorageHelper: Error in deStorageDurationGet', err); return defaultValue; } } /** * Clear all session-scoped values (called on new session if needed) */ static clearSessionStore(): void { this.sessionStore = {}; Logger.logDbg('StorageHelper: Cleared session store'); } /** * Clear all visitor-scoped values */ static async clearVisitorStore(): Promise { try { this.visitorStoreCache = {}; await AsyncStorage.removeItem(VISITOR_STORE_KEY); Logger.logDbg('StorageHelper: Cleared visitor store'); } catch (err) { Logger.logError('StorageHelper: Error clearing visitor store', err); } } }