import type { ConsentCategory, ConsentGrantMeta } from "./consent-types"; import type { IConsent } from "./consent.interface"; /** Cookie name written by the anonymous consent banner. */ export const CONSENT_COOKIE_NAME = "cc_consent"; /** * Parses a raw Cookie header string and returns the consent categories the * anonymous visitor granted via the banner cookie. Returns null when the * consent cookie is absent or empty. * * Expected cookie value format: comma-separated category names, * e.g. "necessary,analytics,marketing". */ export function extractAnonymousConsent( cookieHeader: string, ): ConsentCategory[] | null { const cookies = parseCookieHeader(cookieHeader); const raw = cookies.get(CONSENT_COOKIE_NAME); if (!raw) return null; const categories = raw .split(",") .map((c) => c.trim()) .filter(Boolean) as ConsentCategory[]; return categories.length > 0 ? categories : null; } /** * Migrates anonymous consent categories into an authenticated user's consent * record. Calls IConsent.grant for each category with method "signup-migration" * so the migration is traceable in the audit log. No-op when cookieState is * null (visitor had no consent cookie). */ export async function migrateAnonymousConsent(opts: { consent: IConsent; cookieState: ConsentCategory[] | null; bannerVersion?: string; policyVersion?: string; }): Promise { const { consent, cookieState, bannerVersion, policyVersion } = opts; if (!cookieState) return; const meta: ConsentGrantMeta = { method: "signup-migration" }; if (bannerVersion !== undefined) meta.bannerVersion = bannerVersion; if (policyVersion !== undefined) meta.policyVersion = policyVersion; for (const category of cookieState) { await consent.grant(category, meta); } } function parseCookieHeader(cookieHeader: string): Map { const map = new Map(); for (const part of cookieHeader.split(";")) { const eqIdx = part.indexOf("="); if (eqIdx === -1) continue; const name = part.slice(0, eqIdx).trim(); const value = part.slice(eqIdx + 1).trim(); if (name) map.set(name, value); } return map; }