UNPKG

@omniconvert/server-side-testing-sdk

Version:

TypeScript SDK for server-side A/B testing and experimentation

260 lines 9.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalStorageDriver = void 0; /** * LocalStorage-based storage driver for browser environments * Replaces Redis functionality with browser localStorage */ class LocalStorageDriver { constructor(ttl = LocalStorageDriver.DEFAULT_TTL) { this.ttl = ttl; this.cleanupExpiredItems(); } /** * Get data from localStorage */ get(key) { const storageKey = this.buildKey(key); const ttlKey = this.buildTtlKey(key); try { // Check if item has expired const expirationTime = localStorage.getItem(ttlKey); if (expirationTime) { const expiration = parseInt(expirationTime, 10); if (Date.now() > expiration) { // Item has expired, remove it this.remove(key); return null; } } const compressedValue = localStorage.getItem(storageKey); if (!compressedValue) { return null; } // Try to decompress the value (if it was compressed) try { return this.decompress(compressedValue); } catch { // If decompression fails, return the value as-is return compressedValue; } } catch (error) { console.error('LocalStorageDriver::get error:', error); return null; } } /** * Save data to localStorage */ save(key, data) { const storageKey = this.buildKey(key); const ttlKey = this.buildTtlKey(key); try { // Serialize data if it's not already a string let serializedData; if (typeof data === 'string') { serializedData = data; } else { serializedData = JSON.stringify(data); } // Compress the data to save space const compressedData = this.compress(serializedData); // Set expiration time const expirationTime = Date.now() + this.ttl * 1000; // Store both the data and its expiration time localStorage.setItem(storageKey, compressedData); localStorage.setItem(ttlKey, expirationTime.toString()); return true; } catch (error) { console.error('LocalStorageDriver::save error:', error); // If localStorage is full, try to cleanup and retry once if (error instanceof DOMException && error.name === 'QuotaExceededError') { this.cleanupExpiredItems(); try { const compressedData = this.compress(typeof data === 'string' ? data : JSON.stringify(data)); const expirationTime = Date.now() + this.ttl * 1000; localStorage.setItem(storageKey, compressedData); localStorage.setItem(ttlKey, expirationTime.toString()); return true; } catch { console.warn('LocalStorage quota exceeded even after cleanup'); } } return false; } } /** * Remove an item from localStorage */ remove(key) { const storageKey = this.buildKey(key); const ttlKey = this.buildTtlKey(key); try { localStorage.removeItem(storageKey); localStorage.removeItem(ttlKey); } catch (error) { console.error('LocalStorageDriver::remove error:', error); } } /** * Clear all items with our prefix */ clear() { try { const keysToRemove = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(LocalStorageDriver.KEY_PREFIX)) { keysToRemove.push(key); } } keysToRemove.forEach(key => localStorage.removeItem(key)); } catch (error) { console.error('LocalStorageDriver::clear error:', error); } } /** * Get all keys with our prefix */ getAllKeys() { const keys = []; try { for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(LocalStorageDriver.KEY_PREFIX)) { // Remove prefix and TTL suffix to get original key const originalKey = key .substring(LocalStorageDriver.KEY_PREFIX.length + 1) .replace(LocalStorageDriver.TTL_SUFFIX, ''); if (!key.endsWith(LocalStorageDriver.TTL_SUFFIX) && !keys.includes(originalKey)) { keys.push(originalKey); } } } } catch (error) { console.error('LocalStorageDriver::getAllKeys error:', error); } return keys; } /** * Check if localStorage is available */ static isAvailable() { try { const testKey = '__localStorage_test__'; localStorage.setItem(testKey, 'test'); localStorage.removeItem(testKey); return true; } catch { return false; } } /** * Get storage usage information */ getStorageInfo() { try { let used = 0; // Calculate used space for our keys for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(LocalStorageDriver.KEY_PREFIX)) { const value = localStorage.getItem(key); used += (key.length + (value?.length || 0)) * 2; // * 2 for UTF-16 encoding } } // Estimate total localStorage capacity (varies by browser, typically 5-10MB) const total = 5 * 1024 * 1024; // 5MB default estimate const available = total - used; return { used, total, available }; } catch (error) { console.error('LocalStorageDriver::getStorageInfo error:', error); return { used: 0, total: 0, available: 0 }; } } /** * Build the storage key with prefix */ buildKey(key) { return `${LocalStorageDriver.KEY_PREFIX}_${key}`; } /** * Build the TTL key */ buildTtlKey(key) { return `${this.buildKey(key)}${LocalStorageDriver.TTL_SUFFIX}`; } /** * Compress data (simple base64 encoding for now) * In a real implementation, you might want to use actual compression algorithms */ compress(data) { try { // Simple compression using btoa (base64 encoding) // This doesn't actually compress but ensures data integrity return btoa(encodeURIComponent(data)); } catch { // If btoa fails, return data as-is return data; } } /** * Decompress data */ decompress(data) { try { // Decompress using atob (base64 decoding) return decodeURIComponent(atob(data)); } catch { // If decompression fails, assume data is not compressed return data; } } /** * Clean up expired items to free up space */ cleanupExpiredItems() { try { const now = Date.now(); const keysToRemove = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(LocalStorageDriver.KEY_PREFIX) && key.endsWith(LocalStorageDriver.TTL_SUFFIX)) { const expirationTime = localStorage.getItem(key); if (expirationTime && parseInt(expirationTime, 10) < now) { // This TTL has expired const originalKey = key .substring(LocalStorageDriver.KEY_PREFIX.length + 1) .replace(LocalStorageDriver.TTL_SUFFIX, ''); keysToRemove.push(originalKey); } } } // Remove expired items keysToRemove.forEach(key => this.remove(key)); if (keysToRemove.length > 0) { // Cleaned up expired items (debug logging removed) } } catch (error) { console.error('LocalStorageDriver::cleanupExpiredItems error:', error); } } } exports.LocalStorageDriver = LocalStorageDriver; LocalStorageDriver.TTL_SUFFIX = '_ttl'; LocalStorageDriver.KEY_PREFIX = 'omniconvert'; LocalStorageDriver.DEFAULT_TTL = 31536000; // One year in seconds //# sourceMappingURL=LocalStorageDriver.js.map