/** * Per-client `localStorage` cache for the in-flight content-generation job id * and the last terminal status snapshot. * * Used by the Content Generator page so the progress UI survives page reloads * and cross-page navigation. The cached id is treated as authoritative only * after `getActiveContentGenJob` confirms Redis still holds the lock; the * cached snapshot is the fallback when the lock is gone (terminal cleanup * already wiped Redis) so the UI can still show the "Job Complete" card * after a refresh. See `src/pages/ContentGenerator.tsx`. */ import type { ContentGeneratorStatusResponse } from './content-generator.interface'; const KEY_PREFIX = 'recomaze:content-gen:job-id'; const SNAPSHOT_PREFIX = 'recomaze:content-gen:snapshot'; // 7 days. Beyond that, surfacing the previous completion on refresh is more // confusing than helpful (the user has likely moved on). const SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1000; const buildKey = (clientId: string): string => `${KEY_PREFIX}:${clientId}`; const buildSnapshotKey = (clientId: string): string => `${SNAPSHOT_PREFIX}:${clientId}`; export const loadCachedJobId = (clientId: string): string | null => { if (typeof window === 'undefined' || !clientId) return null; try { return window.localStorage.getItem(buildKey(clientId)); } catch { return null; } }; export const saveCachedJobId = (clientId: string, jobId: string): void => { if (typeof window === 'undefined' || !clientId) return; try { window.localStorage.setItem(buildKey(clientId), jobId); } catch { /* quota / private mode - non-fatal */ } }; export const clearCachedJobId = (clientId: string): void => { if (typeof window === 'undefined' || !clientId) return; try { window.localStorage.removeItem(buildKey(clientId)); } catch { /* non-fatal */ } }; interface CachedSnapshotPayload { status: ContentGeneratorStatusResponse; savedAt: number; } export const saveCachedJobSnapshot = ( clientId: string, status: ContentGeneratorStatusResponse ): void => { if (typeof window === 'undefined' || !clientId) return; try { const payload: CachedSnapshotPayload = { status, savedAt: Date.now() }; window.localStorage.setItem( buildSnapshotKey(clientId), JSON.stringify(payload) ); } catch { /* non-fatal */ } }; export const loadCachedJobSnapshot = ( clientId: string ): ContentGeneratorStatusResponse | null => { if (typeof window === 'undefined' || !clientId) return null; try { const raw = window.localStorage.getItem(buildSnapshotKey(clientId)); if (!raw) return null; const payload = JSON.parse(raw) as CachedSnapshotPayload | null; if (!payload?.status) return null; if (Date.now() - payload.savedAt > SNAPSHOT_TTL_MS) { window.localStorage.removeItem(buildSnapshotKey(clientId)); return null; } return payload.status; } catch { return null; } }; export const clearCachedJobSnapshot = (clientId: string): void => { if (typeof window === 'undefined' || !clientId) return; try { window.localStorage.removeItem(buildSnapshotKey(clientId)); } catch { /* non-fatal */ } };