Files
Momento/memento-note/lib/consent/cookie-consent.ts
Antigravity bb75b2e763
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s
docs: add comprehensive Stripe billing guide
Covers architecture, configuration steps, user flows, API routes,
webhooks, pricing, testing with Stripe CLI, production checklist,
and troubleshooting.
2026-05-16 21:10:26 +00:00

113 lines
3.2 KiB
TypeScript

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<ConsentRecord>
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, 'analytics' | 'marketing'>): 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))
}