|
import localforage from 'localforage'; |
|
|
|
class CacheService { |
|
constructor() { |
|
this.storage = localforage.createInstance({ |
|
name: 'LinAppCache', |
|
storeName: 'authCache' |
|
}); |
|
this.cachePrefix = 'lin_cache_'; |
|
this.defaultTTL = 7 * 24 * 60 * 60 * 1000; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
_generateKey(key) { |
|
return `${this.cachePrefix}${key}`; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async set(key, data, ttl = this.defaultTTL) { |
|
const cacheKey = this._generateKey(key); |
|
const expiry = Date.now() + ttl; |
|
|
|
const cacheData = { |
|
data, |
|
expiry, |
|
metadata: { |
|
createdAt: Date.now(), |
|
ttl, |
|
key |
|
} |
|
}; |
|
|
|
try { |
|
await this.storage.setItem(cacheKey, cacheData); |
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log(`ποΈ [Cache] Set cache for key: ${key}`); |
|
} |
|
} catch (error) { |
|
console.error(`ποΈ [Cache] Error setting cache for key: ${key}`, error); |
|
throw error; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async get(key) { |
|
const cacheKey = this._generateKey(key); |
|
|
|
try { |
|
const cacheData = await this.storage.getItem(cacheKey); |
|
|
|
if (!cacheData) { |
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log(`ποΈ [Cache] Cache miss for key: ${key}`); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
if (Date.now() > cacheData.expiry) { |
|
await this.remove(key); |
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log(`ποΈ [Cache] Cache expired for key: ${key}`); |
|
} |
|
return null; |
|
} |
|
|
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log(`ποΈ [Cache] Cache hit for key: ${key}`); |
|
} |
|
|
|
return cacheData.data; |
|
} catch (error) { |
|
console.error(`ποΈ [Cache] Error getting cache for key: ${key}`, error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async remove(key) { |
|
const cacheKey = this._generateKey(key); |
|
|
|
try { |
|
await this.storage.removeItem(cacheKey); |
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log(`ποΈ [Cache] Removed cache for key: ${key}`); |
|
} |
|
} catch (error) { |
|
console.error(`ποΈ [Cache] Error removing cache for key: ${key}`, error); |
|
throw error; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async clear() { |
|
try { |
|
await this.storage.clear(); |
|
if (import.meta.env.VITE_NODE_ENV === 'development') { |
|
console.log('ποΈ [Cache] Cleared all cache'); |
|
} |
|
} catch (error) { |
|
console.error('ποΈ [Cache] Error clearing cache', error); |
|
throw error; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async exists(key) { |
|
const cacheKey = this._generateKey(key); |
|
|
|
try { |
|
const cacheData = await this.storage.getItem(cacheKey); |
|
|
|
if (!cacheData) { |
|
return false; |
|
} |
|
|
|
|
|
if (Date.now() > cacheData.expiry) { |
|
await this.remove(key); |
|
return false; |
|
} |
|
|
|
return true; |
|
} catch (error) { |
|
console.error(`ποΈ [Cache] Error checking cache existence for key: ${key}`, error); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async getMetadata(key) { |
|
const cacheKey = this._generateKey(key); |
|
|
|
try { |
|
const cacheData = await this.storage.getItem(cacheKey); |
|
|
|
if (!cacheData) { |
|
return null; |
|
} |
|
|
|
return cacheData.metadata || null; |
|
} catch (error) { |
|
console.error(`ποΈ [Cache] Error getting cache metadata for key: ${key}`, error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async setAuthCache(authData, rememberMe = false) { |
|
const ttl = rememberMe ? this.defaultTTL : 60 * 60 * 1000; |
|
|
|
await this.set('auth_token', authData.token, ttl); |
|
await this.set('user_data', authData.user, ttl); |
|
await this.set('remember_me', rememberMe, ttl); |
|
await this.set('auth_expiry', Date.now() + ttl, ttl); |
|
|
|
|
|
const deviceFingerprint = this.generateDeviceFingerprint(); |
|
await this.set('device_fingerprint', deviceFingerprint, ttl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async getAuthCache() { |
|
try { |
|
const [token, userData, rememberMe, expiry, deviceFingerprint] = await Promise.all([ |
|
this.get('auth_token'), |
|
this.get('user_data'), |
|
this.get('remember_me'), |
|
this.get('auth_expiry'), |
|
this.get('device_fingerprint') |
|
]); |
|
|
|
if (!token || !userData || !expiry) { |
|
return null; |
|
} |
|
|
|
|
|
if (Date.now() > expiry) { |
|
await this.clearAuthCache(); |
|
return null; |
|
} |
|
|
|
|
|
const currentFingerprint = this.generateDeviceFingerprint(); |
|
if (deviceFingerprint && deviceFingerprint !== currentFingerprint) { |
|
await this.clearAuthCache(); |
|
return null; |
|
} |
|
|
|
return { |
|
token, |
|
user: userData, |
|
rememberMe: rememberMe || false, |
|
expiresAt: expiry, |
|
deviceFingerprint |
|
}; |
|
} catch (error) { |
|
console.error('ποΈ [Cache] Error getting auth cache', error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async clearAuthCache() { |
|
const keys = ['auth_token', 'user_data', 'remember_me', 'auth_expiry', 'device_fingerprint']; |
|
|
|
await Promise.all(keys.map(key => this.remove(key))); |
|
|
|
|
|
localStorage.removeItem('token'); |
|
localStorage.removeItem('rememberMePreference'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
generateDeviceFingerprint() { |
|
const userAgent = navigator.userAgent; |
|
const screenResolution = `${screen.width}x${screen.height}`; |
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; |
|
const language = navigator.language; |
|
|
|
|
|
const fingerprint = `${userAgent}-${screenResolution}-${timezone}-${language}`; |
|
return btoa(fingerprint).replace(/[^a-zA-Z0-9]/g, '').substring(0, 32); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async setUserPreferences(preferences) { |
|
await this.set('user_preferences', preferences, 30 * 24 * 60 * 60 * 1000); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async getUserPreferences() { |
|
return await this.get('user_preferences'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async cacheApiResponse(endpoint, data, ttl = 5 * 60 * 1000) { |
|
await this.set(`api_${endpoint}`, data, ttl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async getCachedApiResponse(endpoint) { |
|
return await this.get(`api_${endpoint}`); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async setAuthToken(token, rememberMe = false) { |
|
const ttl = rememberMe ? this.defaultTTL : 60 * 60 * 1000; |
|
await this.set('auth_token', token, ttl); |
|
await this.set('remember_me', rememberMe, ttl); |
|
await this.set('auth_expiry', Date.now() + ttl, ttl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async getAuthToken() { |
|
try { |
|
const [token, rememberMe, expiry] = await Promise.all([ |
|
this.get('auth_token'), |
|
this.get('remember_me'), |
|
this.get('auth_expiry') |
|
]); |
|
|
|
if (!token || !expiry) { |
|
return null; |
|
} |
|
|
|
|
|
if (Date.now() > expiry) { |
|
await this.clearAuthToken(); |
|
return null; |
|
} |
|
|
|
return { |
|
token, |
|
rememberMe: rememberMe || false, |
|
expiresAt: expiry |
|
}; |
|
} catch (error) { |
|
console.error('ποΈ [Cache] Error getting auth token', error); |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async clearAuthToken() { |
|
const keys = ['auth_token', 'remember_me', 'auth_expiry']; |
|
await Promise.all(keys.map(key => this.remove(key))); |
|
localStorage.removeItem('token'); |
|
localStorage.removeItem('rememberMePreference'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async setUserData(userData, rememberMe = false) { |
|
const ttl = rememberMe ? this.defaultTTL : 60 * 60 * 1000; |
|
await this.set('user_data', userData, ttl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async getUserData() { |
|
return await this.get('user_data'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async setSessionInfo(sessionData) { |
|
const ttl = sessionData.rememberMe ? this.defaultTTL : 60 * 60 * 1000; |
|
await this.set('session_info', sessionData, ttl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async getSessionInfo() { |
|
return await this.get('session_info'); |
|
} |
|
} |
|
|
|
|
|
export default new CacheService(); |