Files
thrilltrack-explorer/src-old/lib/localStorage.ts

177 lines
3.8 KiB
TypeScript

/**
* Safe localStorage wrapper with proper error handling and validation
*
* Handles:
* - Private browsing mode (localStorage unavailable)
* - Storage quota exceeded
* - JSON parse errors
* - Cross-origin restrictions
*/
import { logger } from './logger';
export class LocalStorageError extends Error {
constructor(message: string, public readonly cause?: Error) {
super(message);
this.name = 'LocalStorageError';
}
}
/**
* Check if localStorage is available
*/
export function isLocalStorageAvailable(): boolean {
try {
const testKey = '__localStorage_test__';
localStorage.setItem(testKey, 'test');
localStorage.removeItem(testKey);
return true;
} catch {
return false;
}
}
/**
* Safely get an item from localStorage
*/
export function getItem(key: string): string | null {
try {
if (!isLocalStorageAvailable()) {
return null;
}
return localStorage.getItem(key);
} catch (error) {
logger.warn(`Failed to get localStorage item: ${key}`, { error });
return null;
}
}
/**
* Safely set an item in localStorage
*/
export function setItem(key: string, value: string): boolean {
try {
if (!isLocalStorageAvailable()) {
return false;
}
localStorage.setItem(key, value);
return true;
} catch (error) {
if (error instanceof Error && error.name === 'QuotaExceededError') {
logger.warn('localStorage quota exceeded', { key });
} else {
logger.warn(`Failed to set localStorage item: ${key}`, { error });
}
return false;
}
}
/**
* Safely remove an item from localStorage
*/
export function removeItem(key: string): boolean {
try {
if (!isLocalStorageAvailable()) {
return false;
}
localStorage.removeItem(key);
return true;
} catch (error) {
logger.warn(`Failed to remove localStorage item: ${key}`, { error });
return false;
}
}
/**
* Safely clear all localStorage
*/
export function clear(): boolean {
try {
if (!isLocalStorageAvailable()) {
return false;
}
localStorage.clear();
return true;
} catch (error) {
logger.warn('Failed to clear localStorage', { error });
return false;
}
}
/**
* Get and parse a JSON object from localStorage
*/
export function getJSON<T>(key: string, defaultValue: T): T {
try {
const item = getItem(key);
if (!item) {
return defaultValue;
}
const parsed = JSON.parse(item);
return parsed as T;
} catch (error) {
logger.warn(`Failed to parse localStorage JSON for key: ${key}`, { error });
// Remove corrupted data
removeItem(key);
return defaultValue;
}
}
/**
* Stringify and set a JSON object in localStorage
*/
export function setJSON<T>(key: string, value: T): boolean {
try {
const serialized = JSON.stringify(value);
return setItem(key, serialized);
} catch (error) {
logger.warn(`Failed to stringify localStorage JSON for key: ${key}`, { error });
return false;
}
}
/**
* Check if a key exists in localStorage
*/
export function hasItem(key: string): boolean {
return getItem(key) !== null;
}
/**
* Get multiple items at once
*/
export function getItems(keys: string[]): Record<string, string | null> {
const result: Record<string, string | null> = {};
for (const key of keys) {
result[key] = getItem(key);
}
return result;
}
/**
* Set multiple items at once
*/
export function setItems(items: Record<string, string>): boolean {
let allSuccessful = true;
for (const [key, value] of Object.entries(items)) {
if (!setItem(key, value)) {
allSuccessful = false;
}
}
return allSuccessful;
}
/**
* Remove multiple items at once
*/
export function removeItems(keys: string[]): boolean {
let allSuccessful = true;
for (const key of keys) {
if (!removeItem(key)) {
allSuccessful = false;
}
}
return allSuccessful;
}