// cache.js - Client-side caching utilities /** * Simple cache implementation with time-based expiration */ class Cache { constructor(options = {}) { this.storage = {}; this.options = { defaultTTL: 3600000, // 1 hour in milliseconds namespace: 'jvb_cache_', useLocalStorage: true, ...options }; // Load persisted data from localStorage if enabled if (this.options.useLocalStorage) { this.loadFromStorage(); } } /** * Set a cache item * @param {string} key - Cache key * @param {*} value - Value to store * @param {number} ttl - Time to live in milliseconds (optional) */ set(key, value, ttl = this.options.defaultTTL) { const cacheKey = this.formatKey(key); const expiry = Date.now() + ttl; this.storage[cacheKey] = { value, expiry }; // Persist to localStorage if enabled if (this.options.useLocalStorage) { this.saveToStorage(); } return value; } /** * Get a cache item * @param {string} key - Cache key * @param {*} defaultValue - Default value if item not found or expired * @returns {*} Cached value or default */ get(key, defaultValue = null) { const cacheKey = this.formatKey(key); const item = this.storage[cacheKey]; // Return default if item doesn't exist if (!item) { return defaultValue; } // Check if item has expired if (item.expiry < Date.now()) { this.remove(key); return defaultValue; } return item.value; } /** * Get or set a cache item * @param {string} key - Cache key * @param {Function} callback - Function to generate value if not cached * @param {number} ttl - Time to live in milliseconds (optional) * @returns {*} Cached or generated value */ getOrSet(key, callback, ttl = this.options.defaultTTL) { const value = this.get(key); if (value !== null) { return value; } // Generate new value const newValue = callback(); // Store in cache return this.set(key, newValue, ttl); } /** * Remove a cache item * @param {string} key - Cache key */ remove(key) { const cacheKey = this.formatKey(key); delete this.storage[cacheKey]; // Update localStorage if enabled if (this.options.useLocalStorage) { this.saveToStorage(); } } /** * Clear all cache items */ clear() { this.storage = {}; // Clear from localStorage if enabled if (this.options.useLocalStorage) { this.clearFromStorage(); } } /** * Check if a cache item exists and is not expired * @param {string} key - Cache key * @returns {boolean} True if item exists and is valid */ has(key) { const cacheKey = this.formatKey(key); const item = this.storage[cacheKey]; if (!item) { return false; } return item.expiry >= Date.now(); } /** * Format a key with namespace * @param {string} key - Original key * @returns {string} Namespaced key */ formatKey(key) { return `${this.options.namespace}${key}`; } /** * Save cache to localStorage */ saveToStorage() { try { localStorage.setItem('jvb_cache', JSON.stringify({ timestamp: Date.now(), data: this.storage })); } catch (e) { console.warn('Failed to save cache to localStorage:', e); } } /** * Load cache from localStorage */ loadFromStorage() { try { const stored = localStorage.getItem('jvb_cache'); if (stored) { const parsed = JSON.parse(stored); // Only use if not too old (24 hours max) const maxAge = 24 * 60 * 60 * 1000; // 24 hours if (parsed.timestamp && (Date.now() - parsed.timestamp) < maxAge) { this.storage = parsed.data || {}; // Clean expired items this.cleanExpired(); } } } catch (e) { console.warn('Failed to load cache from localStorage:', e); this.storage = {}; } } /** * Clear cache from localStorage */ clearFromStorage() { try { localStorage.removeItem('jvb_cache'); } catch (e) { console.warn('Failed to clear cache from localStorage:', e); } } /** * Remove expired items */ cleanExpired() { const now = Date.now(); Object.keys(this.storage).forEach(key => { const item = this.storage[key]; if (item.expiry < now) { delete this.storage[key]; } }); } } // Create and export a singleton instance const cache = new Cache(); export default cache; // Also export the class for custom instances export { Cache };