/** * Persistent event log for the GWT AAF event-logger view. * * Why: the host iframe slot can be torn down and re-created (e.g. when the * agent navigates away and comes back, or the FSM restarts the widget). Each * remount runs `useEffect` again and resets React state, so events received * during a previous mount disappear from the UI even though the SDK delivered * them. Persisting to `sessionStorage` keeps the log alive for the lifetime of * the browser tab without crossing tabs/sessions. * * Capacity is intentionally bounded so a chatty event (e.g. `pageVisibilityChange`) * cannot fill storage. Quota errors are caught and silenced — the in-memory copy * is always authoritative. */ import { safeJson, toPrintableJson } from "./safeJson"; const KEY_PREFIX = "gwt-aaf-event-logger:"; const MAX_ENTRIES = 500; export type StoredEntry = T; function key(scope: string): string { return `${KEY_PREFIX}${scope}`; } function read(scope: string): T[] { if (typeof window === "undefined" || !window.sessionStorage) return []; try { const raw = window.sessionStorage.getItem(key(scope)); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? (parsed as T[]) : []; } catch { return []; } } function write(scope: string, items: T[]): void { if (typeof window === "undefined" || !window.sessionStorage) return; try { const trimmed = items.slice(0, MAX_ENTRIES); window.sessionStorage.setItem(key(scope), JSON.stringify(trimmed)); } catch { // Quota exhausted, sessionStorage disabled in private mode, etc. — fall // back to the in-memory list (caller still has it via React state). } } export const eventStore = { loadEvents(): T[] { return read("events"); }, saveEvents(items: T[]): void { write("events", items); }, loadErrors(): T[] { return read("errors"); }, saveErrors(items: T[]): void { write("errors", items); }, loadCounter(): number { if (typeof window === "undefined" || !window.sessionStorage) return 0; const raw = window.sessionStorage.getItem(key("counter")); if (!raw) return 0; const n = Number.parseInt(raw, 10); return Number.isFinite(n) ? n : 0; }, saveCounter(value: number): void { if (typeof window === "undefined" || !window.sessionStorage) return; try { window.sessionStorage.setItem(key("counter"), String(value)); } catch { // ignore quota } }, clear(): void { if (typeof window === "undefined" || !window.sessionStorage) return; window.sessionStorage.removeItem(key("events")); window.sessionStorage.removeItem(key("errors")); window.sessionStorage.removeItem(key("counter")); } }; /** * Make payload safe for JSON.stringify before persisting (BigInt, circular refs, * Map/Set, typed arrays). Reuses the same normaliser as the UI display path so * the persisted shape matches what the user sees in the Events tab. */ export function makePersistable(value: unknown): unknown { return toPrintableJson(value); } export { safeJson };