mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51:13 -05:00
177 lines
3.8 KiB
TypeScript
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;
|
|
}
|