92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import { EncryptionUtils } from '@repo/utils';
|
|
import type { IStorageService } from './storage.interface';
|
|
|
|
export interface StorageOptions<TKey extends string> {
|
|
encryptedKeys?: Set<TKey>;
|
|
plainTextKeys?: Set<TKey>;
|
|
}
|
|
|
|
/**
|
|
* Enterprise-grade localStorage wrapper with optional AES encryption.
|
|
*/
|
|
export class LocalStorageService<TKey extends string> implements IStorageService<TKey> {
|
|
private readonly encryption: EncryptionUtils;
|
|
private readonly encryptedKeys: Set<TKey>;
|
|
private readonly plainTextKeys: Set<TKey>;
|
|
|
|
constructor(options?: StorageOptions<TKey>, encryptionUtils?: EncryptionUtils) {
|
|
this.encryption = encryptionUtils ?? EncryptionUtils.getInstance();
|
|
this.encryptedKeys = options?.encryptedKeys ?? new Set();
|
|
this.plainTextKeys = options?.plainTextKeys ?? new Set();
|
|
}
|
|
|
|
private validateKey(key: TKey): void {
|
|
if (!this.encryptedKeys.has(key) && !this.plainTextKeys.has(key)) {
|
|
throw new Error(`[Storage Engine] Security Exception: Key '${key}' is not registered and cannot be accessed.`);
|
|
}
|
|
}
|
|
|
|
private shouldEncrypt(key: TKey): boolean {
|
|
return this.encryptedKeys.has(key);
|
|
}
|
|
|
|
async setItem<T>(key: TKey, value: T): Promise<void> {
|
|
this.validateKey(key);
|
|
const serialized = JSON.stringify(value);
|
|
|
|
if (this.shouldEncrypt(key)) {
|
|
const encrypted = this.encryption.encrypt(serialized);
|
|
localStorage.setItem(key as string, encrypted);
|
|
} else {
|
|
localStorage.setItem(key as string, serialized);
|
|
}
|
|
}
|
|
|
|
async getItem<T>(key: TKey): Promise<T | null> {
|
|
this.validateKey(key);
|
|
const raw = localStorage.getItem(key as string);
|
|
if (raw === null) return null;
|
|
|
|
try {
|
|
if (this.shouldEncrypt(key)) {
|
|
const decrypted = this.encryption.decrypt(raw);
|
|
if (!decrypted) return null;
|
|
return JSON.parse(decrypted) as T;
|
|
}
|
|
return JSON.parse(raw) as T;
|
|
} catch {
|
|
console.warn(`[core-storage] Failed to parse key "${key}". Removing corrupt entry.`);
|
|
localStorage.removeItem(key as string);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async removeItem(key: TKey): Promise<void> {
|
|
this.validateKey(key);
|
|
localStorage.removeItem(key as string);
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
localStorage.clear();
|
|
}
|
|
|
|
async hasItem(key: TKey): Promise<boolean> {
|
|
return localStorage.getItem(key as string) !== null;
|
|
}
|
|
|
|
async keys(): Promise<TKey[]> {
|
|
const result: TKey[] = [];
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key !== null) result.push(key as TKey);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export function createLocalStorage<TKey extends string>(
|
|
options?: StorageOptions<TKey>
|
|
): IStorageService<TKey> {
|
|
return new LocalStorageService<TKey>(options);
|
|
}
|