veza/apps/web/src/utils/safeStorage.ts
senke eb253af174 edge-cases: implement Edge 5.1 and 5.2 - browser compatibility fallbacks
- Edge 5.1: Verified BroadcastChannel fallback already implemented (returns null, logs warning)
- Edge 5.2: Created safe storage utility for localStorage/sessionStorage
- safeLocalStorage and safeSessionStorage automatically fall back to in-memory storage
- Handles private browsing mode and quota exceeded errors gracefully
- Tests availability before use
- Logs warnings when fallback is used
- Provides utility functions to check support
- Improves reliability in all browser environments
2026-01-16 14:21:03 +01:00

159 lines
4.1 KiB
TypeScript

/**
* Edge 5.2: Safe Storage Utility
* Provides safe wrappers for localStorage and sessionStorage with fallback support
* for private browsing mode and unsupported browsers.
*
* In private browsing mode, localStorage may throw errors when accessed.
* This utility handles those errors gracefully by falling back to in-memory storage.
*/
import { logger } from './logger';
/**
* In-memory storage fallback for when localStorage/sessionStorage is unavailable
*/
class MemoryStorage implements Storage {
private data: Map<string, string> = new Map();
get length(): number {
return this.data.size;
}
clear(): void {
this.data.clear();
}
getItem(key: string): string | null {
return this.data.get(key) || null;
}
key(index: number): string | null {
const keys = Array.from(this.data.keys());
return keys[index] || null;
}
removeItem(key: string): void {
this.data.delete(key);
}
setItem(key: string, value: string): void {
this.data.set(key, value);
}
}
/**
* Edge 5.2: Test if localStorage is available and working
* Returns true if localStorage can be used, false otherwise
*/
function isLocalStorageAvailable(): boolean {
if (typeof window === 'undefined') {
return false; // SSR environment
}
try {
const testKey = '__veza_storage_test__';
localStorage.setItem(testKey, 'test');
localStorage.removeItem(testKey);
return true;
} catch {
// localStorage is not available (private browsing, quota exceeded, etc.)
return false;
}
}
/**
* Edge 5.2: Test if sessionStorage is available and working
* Returns true if sessionStorage can be used, false otherwise
*/
function isSessionStorageAvailable(): boolean {
if (typeof window === 'undefined') {
return false; // SSR environment
}
try {
const testKey = '__veza_session_storage_test__';
sessionStorage.setItem(testKey, 'test');
sessionStorage.removeItem(testKey);
return true;
} catch {
// sessionStorage is not available (private browsing, quota exceeded, etc.)
return false;
}
}
// Create fallback storage instances
const memoryLocalStorage = new MemoryStorage();
const memorySessionStorage = new MemoryStorage();
// Determine which storage to use
const localStorageAvailable = isLocalStorageAvailable();
const sessionStorageAvailable = isSessionStorageAvailable();
// Log warnings if fallback is being used
if (typeof window !== 'undefined' && !localStorageAvailable) {
logger.warn(
'[SafeStorage] localStorage not available, using in-memory fallback',
{
reason: 'private_browsing_or_quota_exceeded',
},
);
}
if (typeof window !== 'undefined' && !sessionStorageAvailable) {
logger.warn(
'[SafeStorage] sessionStorage not available, using in-memory fallback',
{
reason: 'private_browsing_or_quota_exceeded',
},
);
}
/**
* Edge 5.2: Safe localStorage wrapper
* Automatically falls back to in-memory storage if localStorage is unavailable
*
* @example
* ```typescript
* import { safeLocalStorage } from '@/utils/safeStorage';
*
* // Use like regular localStorage
* safeLocalStorage.setItem('key', 'value');
* const value = safeLocalStorage.getItem('key');
* safeLocalStorage.removeItem('key');
* ```
*/
export const safeLocalStorage: Storage = localStorageAvailable
? window.localStorage
: memoryLocalStorage;
/**
* Edge 5.2: Safe sessionStorage wrapper
* Automatically falls back to in-memory storage if sessionStorage is unavailable
*
* @example
* ```typescript
* import { safeSessionStorage } from '@/utils/safeStorage';
*
* // Use like regular sessionStorage
* safeSessionStorage.setItem('key', 'value');
* const value = safeSessionStorage.getItem('key');
* safeSessionStorage.removeItem('key');
* ```
*/
export const safeSessionStorage: Storage = sessionStorageAvailable
? window.sessionStorage
: memorySessionStorage;
/**
* Edge 5.2: Check if localStorage is available (not using fallback)
*/
export function isLocalStorageSupported(): boolean {
return localStorageAvailable;
}
/**
* Edge 5.2: Check if sessionStorage is available (not using fallback)
*/
export function isSessionStorageSupported(): boolean {
return sessionStorageAvailable;
}