// A public-form notification settings save sent with no answer yet, per practice. It may have landed, and nothing on // the client can prove it no longer will: no clock does, and a lost connection does not cancel the write. So no form // offers another save until the request's own answer arrives; a read showing the settings it sent does not say which // request saved them. Kept for the browser session, so a reload is not taken as a failure. export interface FormNotificationSettings { enabled: boolean; recipients: string[]; } export interface UnconfirmedFormNotificationSave { attempt: string; sent: FormNotificationSettings; /** The server answered this very request, though nobody was left on screen to be told. */ answer?: 'saved' | 'refused'; } const STORAGE_KEY = 'akashic.unconfirmedFormNotificationSaves'; // Where session storage cannot be used (blocked, private mode), the tab's memory still holds the barrier. let fallback: Record = {}; const readAll = (): Record => { try { const raw = window.sessionStorage.getItem(STORAGE_KEY); return raw ? { ...fallback, ...JSON.parse(raw) } : { ...fallback }; } catch { return { ...fallback }; } }; const writeAll = (all: Record) => { fallback = all; try { window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(all)); } catch { // The in-memory copy above stands in. } }; export const unconfirmedFormNotificationSave = (organizationId: string | undefined) => organizationId ? readAll()[organizationId] : undefined; /** Recorded as the request is sent; the returned attempt is what its answer is noted against. */ export const recordUnconfirmedFormNotificationSave = (organizationId: string | undefined, sent: FormNotificationSettings): string => { const attempt = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; if (organizationId) writeAll({ ...readAll(), [organizationId]: { attempt, sent } }); return attempt; }; export const noteFormNotificationSaveAnswer = (organizationId: string | undefined, attempt: string, answer: 'saved' | 'refused'): void => { const all = readAll(); if (organizationId && all[organizationId]?.attempt === attempt) writeAll({ ...all, [organizationId]: { ...all[organizationId], answer } }); }; export const settleUnconfirmedFormNotificationSave = (organizationId: string | undefined): void => { if (!organizationId) return; const all = readAll(); delete all[organizationId]; writeAll(all); }; /** The same settings: the same switch and the same addresses, in any order and any letter case. */ export const sameFormNotificationSettings = (a: FormNotificationSettings, b: FormNotificationSettings): boolean => { const key = (list: string[]) => list.map((email) => email.toLowerCase()).sort().join('\n'); return a.enabled === b.enabled && key(a.recipients) === key(b.recipients); }; // Tests only: the barrier outlives a test the same way it outlives the form. export const forgetUnconfirmedFormNotificationSaves = (): void => { writeAll({}); };