export const CONSENT_VERSION = 1 as const export const CONSENT_STORAGE_KEY = 'memento-consent-v1' export const CONSENT_COOKIE_NAME = 'memento-cookie-consent' const CONSENT_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 export type ConsentRecord = { version: typeof CONSENT_VERSION necessary: true analytics: boolean marketing: boolean updatedAt: string } export const CONSENT_CHANGE_EVENT = 'memento-consent-change' export const OPEN_COOKIE_PREFERENCES_EVENT = 'memento-open-cookie-preferences' function isBrowser(): boolean { return typeof window !== 'undefined' } function parseRecord(raw: unknown): ConsentRecord | null { if (!raw || typeof raw !== 'object') return null const o = raw as Partial if (o.version !== CONSENT_VERSION) return null if (o.necessary !== true) return null if (typeof o.analytics !== 'boolean' || typeof o.marketing !== 'boolean') return null if (typeof o.updatedAt !== 'string') return null return { version: CONSENT_VERSION, necessary: true, analytics: o.analytics, marketing: o.marketing, updatedAt: o.updatedAt, } } function readCookie(): ConsentRecord | null { if (!isBrowser()) return null const match = document.cookie .split(';') .map((s) => s.trim()) .find((s) => s.startsWith(`${CONSENT_COOKIE_NAME}=`)) if (!match) return null try { const encoded = match.slice(CONSENT_COOKIE_NAME.length + 1) const json = decodeURIComponent(atob(encoded)) return parseRecord(JSON.parse(json)) } catch { return null } } function writeCookie(record: ConsentRecord): void { if (!isBrowser()) return const encoded = btoa(encodeURIComponent(JSON.stringify(record))) document.cookie = `${CONSENT_COOKIE_NAME}=${encoded};path=/;max-age=${CONSENT_COOKIE_MAX_AGE};samesite=lax` } export function getConsent(): ConsentRecord | null { if (!isBrowser()) return null try { const fromStorage = localStorage.getItem(CONSENT_STORAGE_KEY) if (fromStorage) { const parsed = parseRecord(JSON.parse(fromStorage)) if (parsed) return parsed } } catch { // ignore corrupt storage } return readCookie() } export function hasConsentChoice(): boolean { return getConsent() !== null } export function hasAnalyticsConsent(): boolean { return getConsent()?.analytics === true } export function hasMarketingConsent(): boolean { return getConsent()?.marketing === true } export function setConsent(partial: Pick): ConsentRecord { const record: ConsentRecord = { version: CONSENT_VERSION, necessary: true, analytics: partial.analytics, marketing: partial.marketing, updatedAt: new Date().toISOString(), } if (isBrowser()) { localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(record)) writeCookie(record) window.dispatchEvent(new CustomEvent(CONSENT_CHANGE_EVENT, { detail: record })) } return record } export function acceptEssentialsOnly(): ConsentRecord { return setConsent({ analytics: false, marketing: false }) } export function acceptAllOptional(): ConsentRecord { return setConsent({ analytics: true, marketing: false }) } export function openCookiePreferences(): void { if (!isBrowser()) return window.dispatchEvent(new CustomEvent(OPEN_COOKIE_PREFERENCES_EVENT)) }