UNPKG

json-secure-store

Version:

Typed JSON storage wrapper for localStorage/sessionStorage with optional encryption.

100 lines (99 loc) 3.45 kB
import { StorageType } from './types'; import { encryptData, decryptData } from './utils/crypto'; export class JsonStore { constructor(options) { this.listeners = []; this.memoryCache = new Map(); const { storageType = StorageType.Local, encrypt = false, encryptionKey, cache = false, namespace, } = options || {}; const defaults = { storageType: StorageType.Local, encrypt: false, encryptionKey: '', cache: false, namespace: '', defaultTTL: 0, }; this.options = Object.assign({}, defaults, options); this.storage = window[this.options.storageType]; if (this.options.encrypt && !this.options.encryptionKey) { throw new Error('Encryption key is required when encryption is enabled.'); } this.storage = window[storageType]; this.encrypt = encrypt; this.encryptionKey = encryptionKey; this.cache = cache; this.namespace = namespace; if (this.encrypt && !this.encryptionKey) { throw new Error('Encryption key is required when encryption is enabled.'); } } buildKey(key) { return this.namespace ? `${this.namespace}:${key}` : key; } notifyChange(key, value) { this.listeners.forEach((cb) => cb(key, value)); } onChange(callback) { this.listeners.push(callback); } setItem(key, value) { const fullKey = this.buildKey(key); const expiresAt = this.options.defaultTTL ? Date.now() + this.options.defaultTTL : undefined; const wrapper = { data: value, expiresAt }; let data = JSON.stringify(wrapper); if (this.encrypt && this.encryptionKey) { data = encryptData(data, this.encryptionKey); } this.storage.setItem(fullKey, data); if (this.cache) { this.memoryCache.set(fullKey, value); } } getItem(key) { const fullKey = this.buildKey(key); if (this.cache && this.memoryCache.has(fullKey)) { return this.memoryCache.get(fullKey); } const raw = this.storage.getItem(fullKey); if (!raw) return null; try { const data = this.encrypt && this.encryptionKey ? decryptData(raw, this.encryptionKey) : raw; const parsed = JSON.parse(data); if (parsed.expiresAt && Date.now() > parsed.expiresAt) { this.removeItem(key); return null; } if (this.cache) { this.memoryCache.set(fullKey, parsed); } return parsed.data; } catch (err) { console.error('Failed to decrypt or parse item:', err); return null; } } removeItem(key) { const fullKey = this.buildKey(key); this.storage.removeItem(fullKey); if (this.cache) { this.memoryCache.delete(fullKey); } } clear() { const keysToRemove = Object.keys(this.storage).filter((key) => this.namespace ? key.startsWith(this.namespace + ':') : true); keysToRemove.forEach((key) => this.storage.removeItem(key)); if (this.cache) { this.memoryCache.clear(); } } raw() { return this.storage; } } export { StorageType };