spb/phi-conseil
Public
1/**2 * Consentement aux témoins (cookies) — Loi 25 (Québec) / RGPD :3 * refus par défaut pour tout ce qui n'est pas strictement nécessaire,4 * choix granulaire, révocable à tout moment, conservé 180 jours.5 *6 * Le site ne pose aujourd'hui AUCUN témoin non nécessaire : ce module est la7 * porte d'entrée obligatoire pour tout futur outil d'analyse ou de marketing8 * (`hasConsent("analytics")` avant de charger un script).9 */1011export const CONSENT_COOKIE = "phi_consent";12export const CONSENT_VERSION = 1;13export const CONSENT_MAX_AGE_S = 180 * 24 * 60 * 60;14export const CONSENT_EVENT = "phi-consent-change";15export const CONSENT_OPEN_EVENT = "phi-consent-open";1617export const OPTIONAL_CATEGORIES = ["analytics", "marketing"] as const;18export type OptionalCategory = (typeof OPTIONAL_CATEGORIES)[number];19export type Category = "necessary" | OptionalCategory;2021export type Consent = {22 version: number;23 date: string; // ISO24 necessary: true;25 analytics: boolean;26 marketing: boolean;27};2829export function allRejected(): Consent {30 return { version: CONSENT_VERSION, date: new Date().toISOString(), necessary: true, analytics: false, marketing: false };31}3233export function allAccepted(): Consent {34 return { version: CONSENT_VERSION, date: new Date().toISOString(), necessary: true, analytics: true, marketing: true };35}3637export function parseConsent(raw: string | null | undefined): Consent | null {38 if (!raw) return null;39 try {40 const v = JSON.parse(decodeURIComponent(raw)) as Partial<Consent>;41 if (v.version !== CONSENT_VERSION) return null; // nouvelle version = redemander42 if (typeof v.analytics !== "boolean" || typeof v.marketing !== "boolean") return null;43 return {44 version: CONSENT_VERSION,45 date: typeof v.date === "string" ? v.date : new Date(0).toISOString(),46 necessary: true,47 analytics: v.analytics,48 marketing: v.marketing,49 };50 } catch {51 return null;52 }53}5455export function serializeConsent(c: Consent): string {56 return encodeURIComponent(JSON.stringify(c));57}5859export function readConsentCookie(cookieHeader: string): Consent | null {60 const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${CONSENT_COOKIE}=([^;]*)`));61 return parseConsent(m?.[1]);62}6364export function hasConsent(consent: Consent | null, category: Category): boolean {65 if (category === "necessary") return true;66 return consent?.[category] === true;67}68