/** * Voice settings that live in the browser only. Every access sits in * try/catch: private windows and blocked site data make localStorage throw, * and the default (no consent, notice not seen) is the safe answer then. */ const CLOUD_CONSENT_KEY = "apicore.voice.cloud-consent"; const NOTICE_SEEN_KEY = "apicore.voice.notice-seen"; function readFlag(key: string): boolean { try { return window.localStorage.getItem(key) === "1"; } catch { return false; } } function writeFlag(key: string, value: boolean): void { try { if (value) { window.localStorage.setItem(key, "1"); } else { window.localStorage.removeItem(key); } } catch { // Storage is a convenience here; without it the flag simply stays at its default. } } export function getCloudRecognitionConsent(): boolean { return readFlag(CLOUD_CONSENT_KEY); } const consentListeners = new Set<() => void>(); /** For useSyncExternalStore: re-reads after this tab or another tab changed the consent. */ export function subscribeCloudRecognitionConsent(listener: () => void): () => void { consentListeners.add(listener); const onStorage = (event: StorageEvent) => { if (event.key === CLOUD_CONSENT_KEY || event.key === null) { listener(); } }; window.addEventListener("storage", onStorage); return () => { consentListeners.delete(listener); window.removeEventListener("storage", onStorage); }; } export function setCloudRecognitionConsent(value: boolean): void { writeFlag(CLOUD_CONSENT_KEY, value); consentListeners.forEach((listener) => listener()); } export function hasSeenRecordingNotice(): boolean { return readFlag(NOTICE_SEEN_KEY); } export function markRecordingNoticeSeen(): void { writeFlag(NOTICE_SEEN_KEY, true); }