158 lines
3.8 KiB
TypeScript
158 lines
3.8 KiB
TypeScript
import type { FunnelAnswers } from "./types";
|
|
|
|
/**
|
|
* Структура данных для хранения состояния воронки
|
|
*/
|
|
export interface FunnelRuntimeState {
|
|
answers: FunnelAnswers;
|
|
history: string[];
|
|
version: number;
|
|
timestamp?: number; // Для отслеживания времени последнего обновления
|
|
}
|
|
|
|
/**
|
|
* Префикс для ключей в localStorage
|
|
*/
|
|
const STORAGE_PREFIX = "funnel_state_";
|
|
|
|
/**
|
|
* Время жизни данных в localStorage (7 дней)
|
|
*/
|
|
const STORAGE_TTL = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
/**
|
|
* Проверяет доступность localStorage
|
|
*/
|
|
function isLocalStorageAvailable(): boolean {
|
|
try {
|
|
const testKey = "__storage_test__";
|
|
localStorage.setItem(testKey, "test");
|
|
localStorage.removeItem(testKey);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Получает ключ для localStorage по ID воронки
|
|
*/
|
|
function getStorageKey(funnelId: string): string {
|
|
return `${STORAGE_PREFIX}${funnelId}`;
|
|
}
|
|
|
|
/**
|
|
* Сохраняет состояние воронки в localStorage
|
|
*/
|
|
export function saveFunnelState(
|
|
funnelId: string,
|
|
state: FunnelRuntimeState
|
|
): void {
|
|
if (!isLocalStorageAvailable()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const dataToSave: FunnelRuntimeState = {
|
|
...state,
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
localStorage.setItem(getStorageKey(funnelId), JSON.stringify(dataToSave));
|
|
} catch (error) {
|
|
console.warn("Failed to save funnel state to localStorage:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Загружает состояние воронки из localStorage
|
|
*/
|
|
export function loadFunnelState(funnelId: string): FunnelRuntimeState | null {
|
|
if (!isLocalStorageAvailable()) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const stored = localStorage.getItem(getStorageKey(funnelId));
|
|
if (!stored) {
|
|
return null;
|
|
}
|
|
|
|
const data = JSON.parse(stored) as FunnelRuntimeState;
|
|
|
|
// Проверяем TTL
|
|
if (data.timestamp) {
|
|
const age = Date.now() - data.timestamp;
|
|
if (age > STORAGE_TTL) {
|
|
// Данные устарели, удаляем
|
|
clearFunnelState(funnelId);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.warn("Failed to load funnel state from localStorage:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Очищает состояние воронки из localStorage
|
|
*/
|
|
export function clearFunnelState(funnelId: string): void {
|
|
if (!isLocalStorageAvailable()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
localStorage.removeItem(getStorageKey(funnelId));
|
|
} catch (error) {
|
|
console.warn("Failed to clear funnel state from localStorage:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Очищает все устаревшие состояния воронок
|
|
*/
|
|
export function clearExpiredFunnelStates(): void {
|
|
if (!isLocalStorageAvailable()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const now = Date.now();
|
|
const keysToRemove: string[] = [];
|
|
|
|
// Проходим по всем ключам в localStorage
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (!key?.startsWith(STORAGE_PREFIX)) {
|
|
continue;
|
|
}
|
|
|
|
const value = localStorage.getItem(key);
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const data = JSON.parse(value) as FunnelRuntimeState;
|
|
if (data.timestamp && now - data.timestamp > STORAGE_TTL) {
|
|
keysToRemove.push(key);
|
|
}
|
|
} catch {
|
|
// Если не удалось распарсить - удаляем
|
|
keysToRemove.push(key);
|
|
}
|
|
}
|
|
|
|
// Удаляем устаревшие ключи
|
|
for (const key of keysToRemove) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
} catch (error) {
|
|
console.warn("Failed to clear expired funnel states:", error);
|
|
}
|
|
}
|