177 lines
3.8 KiB
TypeScript
Executable file
177 lines
3.8 KiB
TypeScript
Executable file
/**
|
|
* Wizard Storage
|
|
*
|
|
* SSR-safe localStorage wrapper with versioning support.
|
|
* Handles persistence of wizard state across page reloads.
|
|
*/
|
|
|
|
import type { WizardStorageData } from './types';
|
|
|
|
/**
|
|
* Check if localStorage is available
|
|
*/
|
|
function isLocalStorageAvailable(): boolean {
|
|
if (typeof window === 'undefined') return false;
|
|
|
|
try {
|
|
const testKey = '__wizard_storage_test__';
|
|
window.localStorage.setItem(testKey, testKey);
|
|
window.localStorage.removeItem(testKey);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wizard Storage API
|
|
*/
|
|
export const wizardStorage = {
|
|
/**
|
|
* Check if storage is available
|
|
*/
|
|
isAvailable: isLocalStorageAvailable,
|
|
|
|
/**
|
|
* Save wizard state to localStorage
|
|
*/
|
|
save<TData = Record<string, unknown>>(
|
|
key: string,
|
|
data: WizardStorageData<TData>
|
|
): boolean {
|
|
if (!isLocalStorageAvailable()) return false;
|
|
|
|
try {
|
|
const serialized = JSON.stringify({
|
|
...data,
|
|
lastSavedAt: Date.now(),
|
|
});
|
|
window.localStorage.setItem(key, serialized);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('[WizardStorage] Failed to save:', error);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load wizard state from localStorage
|
|
*/
|
|
load<TData = Record<string, unknown>>(
|
|
key: string
|
|
): WizardStorageData<TData> | null {
|
|
if (!isLocalStorageAvailable()) return null;
|
|
|
|
try {
|
|
const serialized = window.localStorage.getItem(key);
|
|
if (!serialized) return null;
|
|
|
|
const data = JSON.parse(serialized) as WizardStorageData<TData>;
|
|
return data;
|
|
} catch (error) {
|
|
console.warn('[WizardStorage] Failed to load:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear wizard state from localStorage
|
|
*/
|
|
clear(key: string): boolean {
|
|
if (!isLocalStorageAvailable()) return false;
|
|
|
|
try {
|
|
window.localStorage.removeItem(key);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('[WizardStorage] Failed to clear:', error);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if saved state exists
|
|
*/
|
|
exists(key: string): boolean {
|
|
if (!isLocalStorageAvailable()) return false;
|
|
|
|
try {
|
|
return window.localStorage.getItem(key) !== null;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get all wizard storage keys
|
|
*/
|
|
getAllKeys(prefix: string = 'wizard'): string[] {
|
|
if (!isLocalStorageAvailable()) return [];
|
|
|
|
try {
|
|
const keys: string[] = [];
|
|
for (let i = 0; i < window.localStorage.length; i++) {
|
|
const key = window.localStorage.key(i);
|
|
if (key?.startsWith(prefix)) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
return keys;
|
|
} catch {
|
|
return [];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear all wizard storage
|
|
*/
|
|
clearAll(prefix: string = 'wizard'): number {
|
|
const keys = this.getAllKeys(prefix);
|
|
let cleared = 0;
|
|
|
|
for (const key of keys) {
|
|
if (this.clear(key)) {
|
|
cleared++;
|
|
}
|
|
}
|
|
|
|
return cleared;
|
|
},
|
|
|
|
/**
|
|
* Check if saved state is expired
|
|
*/
|
|
isExpired(key: string, maxAgeMs: number): boolean {
|
|
const data = this.load(key);
|
|
if (!data) return true;
|
|
|
|
const age = Date.now() - data.lastSavedAt;
|
|
return age > maxAgeMs;
|
|
},
|
|
|
|
/**
|
|
* Migrate storage data if version mismatch
|
|
*/
|
|
migrate<TData = Record<string, unknown>>(
|
|
key: string,
|
|
currentVersion: number,
|
|
migrator: (data: WizardStorageData<TData>, fromVersion: number) => WizardStorageData<TData>
|
|
): WizardStorageData<TData> | null {
|
|
const data = this.load<TData>(key);
|
|
if (!data) return null;
|
|
|
|
if (data.version === currentVersion) {
|
|
return data;
|
|
}
|
|
|
|
try {
|
|
const migrated = migrator(data, data.version);
|
|
migrated.version = currentVersion;
|
|
this.save(key, migrated);
|
|
return migrated;
|
|
} catch (error) {
|
|
console.warn('[WizardStorage] Migration failed:', error);
|
|
return null;
|
|
}
|
|
},
|
|
};
|