From c6f548b8ff348d169d89645a92f0675d40f27092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 24 Apr 2026 15:05:09 +0400 Subject: [PATCH] Add Google Consent Mode v2 to cookie banner SDK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set GCM defaults to denied on load, then push consent updates via gtag('consent', 'update', ...) before activating scripts. The mapping from category slugs to GCM consent types comes from the banner config endpoint. Signed-off-by: Émile Ré --- packages/cookie-banner/src/client.ts | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/cookie-banner/src/client.ts b/packages/cookie-banner/src/client.ts index e3ffff7da..ef4ed2032 100644 --- a/packages/cookie-banner/src/client.ts +++ b/packages/cookie-banner/src/client.ts @@ -39,6 +39,7 @@ export interface Category { description: string; kind: string; cookies: CookieItem[]; + gcm_consent_types: string[]; } export interface BannerConfig { @@ -108,6 +109,7 @@ export class CookieBannerClient { const config = await fetchJSON(configUrl); this.bannerConfig = config; + this.setGoogleConsentDefaults(); this.startDetector(config); const cookie = getConsentCookie(); @@ -272,6 +274,8 @@ export class CookieBannerClient { } private activate(consentData: Record): void { + this.updateGoogleConsentMode(consentData); + const categoryCookies: Record = {}; const categoryLabels: Record = {}; for (const cat of this.config.categories) { @@ -289,6 +293,55 @@ export class CookieBannerClient { this.observer = observeAndActivate(consentData, categoryLabels, texts); } + private hasGCMMapping(): boolean { + return this.config.categories.some( + (cat) => cat.gcm_consent_types && cat.gcm_consent_types.length > 0, + ); + } + + private setGoogleConsentDefaults(): void { + if (!this.hasGCMMapping()) return; + + const w = window as unknown as Record; + const gtag = w.gtag as ((...args: unknown[]) => void) | undefined; + if (typeof gtag !== "function") return; + + const defaults: Record = {}; + for (const cat of this.config.categories) { + if (!cat.gcm_consent_types) continue; + for (const gcmType of cat.gcm_consent_types) { + defaults[gcmType] = "denied"; + } + } + + if (Object.keys(defaults).length > 0) { + gtag("consent", "default", defaults); + } + } + + private updateGoogleConsentMode( + consentData: Record, + ): void { + if (!this.hasGCMMapping()) return; + + const w = window as unknown as Record; + const gtag = w.gtag as ((...args: unknown[]) => void) | undefined; + if (typeof gtag !== "function") return; + + const update: Record = {}; + for (const cat of this.config.categories) { + if (!cat.gcm_consent_types) continue; + const granted = !!consentData[cat.slug]; + for (const gcmType of cat.gcm_consent_types) { + update[gcmType] = granted ? "granted" : "denied"; + } + } + + if (Object.keys(update).length > 0) { + gtag("consent", "update", update); + } + } + private startDetector(config: BannerConfig): void { const knownNames = new Set(); knownNames.add(COOKIE_NAME);