From 04429ad0e025c44c14bc88de17290c53b69dff5f Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 3 Nov 2025 01:45:30 +0000 Subject: [PATCH 1/2] feat: implement Google Consent Mode v2 with cookieless pings - Add consent defaults before gtag.js loads (required for Consent Mode v2) - Enable cookieless pings with url_passthrough for Google Ads - Implement consent update logic for all consent categories - Support both granted and denied consent states - Maintain backward compatibility with existing consent manager --- .../providers/google-analytics-provider.tsx | 110 ++++++++++++++---- 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx b/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx index 7bbe26d5bfb2..b6e0c8fcf476 100644 --- a/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx +++ b/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx @@ -8,72 +8,132 @@ import { hasConsent, onConsentChange } from "@/lib/analytics/consent-manager" const GTM_ID = "AW-17391954825" /** - * Google Analytics Provider - * Only loads Google Tag Manager after user gives consent + * Google Analytics Provider with Consent Mode v2 + * Implements cookieless pings and advanced consent management */ export function GoogleAnalyticsProvider({ children }: { children: React.ReactNode }) { const [shouldLoad, setShouldLoad] = useState(false) useEffect(() => { + // Initialize consent defaults BEFORE loading gtag.js (required for Consent Mode v2) + initializeConsentDefaults() + // Check initial consent status if (hasConsent()) { setShouldLoad(true) - initializeGoogleAnalytics() + updateConsentGranted() } // Listen for consent changes const unsubscribe = onConsentChange((consented) => { - if (consented && !shouldLoad) { - setShouldLoad(true) - initializeGoogleAnalytics() + if (consented) { + if (!shouldLoad) { + setShouldLoad(true) + } + updateConsentGranted() + } else { + updateConsentDenied() } }) return unsubscribe }, [shouldLoad]) - const initializeGoogleAnalytics = () => { - // Initialize the dataLayer and gtag function + const initializeConsentDefaults = () => { + // Set up consent defaults before gtag loads (Consent Mode v2 requirement) if (typeof window !== "undefined") { window.dataLayer = window.dataLayer || [] window.gtag = function (...args: GtagArgs) { window.dataLayer.push(args) } - window.gtag("js", new Date()) - window.gtag("config", GTM_ID) + + // Set default consent state to 'denied' with cookieless pings enabled + window.gtag("consent", "default", { + ad_storage: "denied", + ad_user_data: "denied", + ad_personalization: "denied", + analytics_storage: "denied", + functionality_storage: "denied", + personalization_storage: "denied", + security_storage: "granted", // Always granted for security + wait_for_update: 500, // Wait 500ms for consent before sending data + }) + + // Enable cookieless pings for Google Ads + window.gtag("set", "url_passthrough", true) } } - // Only render Google Analytics scripts if consent is given - if (!shouldLoad) { - return <>{children} + const updateConsentGranted = () => { + // User accepted cookies - update consent to granted + if (typeof window !== "undefined" && window.gtag) { + window.gtag("consent", "update", { + ad_storage: "granted", + ad_user_data: "granted", + ad_personalization: "granted", + analytics_storage: "granted", + functionality_storage: "granted", + personalization_storage: "granted", + }) + } } + const updateConsentDenied = () => { + // User declined cookies - keep consent denied (cookieless pings still work) + if (typeof window !== "undefined" && window.gtag) { + window.gtag("consent", "update", { + ad_storage: "denied", + ad_user_data: "denied", + ad_personalization: "denied", + analytics_storage: "denied", + functionality_storage: "denied", + personalization_storage: "denied", + }) + } + } + + // Always render scripts (Consent Mode v2 needs gtag loaded even without consent) + // Cookieless pings will work with denied consent + return ( <> - {/* Google tag (gtag.js) - Only loads after consent */} + {/* Google tag (gtag.js) - Loads immediately for Consent Mode v2 */} {children} ) } -// Type definitions for Google Analytics -type GtagArgs = ["js", Date] | ["config", string, GtagConfig?] | ["event", string, GtagEventParameters?] +// Type definitions for Google Analytics with Consent Mode v2 +type ConsentState = "granted" | "denied" + +interface ConsentParams { + ad_storage?: ConsentState + ad_user_data?: ConsentState + ad_personalization?: ConsentState + analytics_storage?: ConsentState + functionality_storage?: ConsentState + personalization_storage?: ConsentState + security_storage?: ConsentState + wait_for_update?: number +} + +type GtagArgs = + | ["js", Date] + | ["config", string, GtagConfig?] + | ["event", string, GtagEventParameters?] + | ["consent", "default" | "update", ConsentParams] + | ["set", string, unknown] interface GtagConfig { [key: string]: unknown From b7d5bd3ff3b0ffbd1ef282a76ecb04bdb78b6aa9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 3 Nov 2025 03:27:29 +0000 Subject: [PATCH 2/2] fix: remove shouldLoad from useEffect dependency array to prevent re-initialization loop --- .../src/components/providers/google-analytics-provider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx b/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx index b6e0c8fcf476..3d274db5090f 100644 --- a/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx +++ b/apps/web-roo-code/src/components/providers/google-analytics-provider.tsx @@ -37,7 +37,8 @@ export function GoogleAnalyticsProvider({ children }: { children: React.ReactNod }) return unsubscribe - }, [shouldLoad]) + // eslint-disable-next-line react-hooks/exhaustive-deps -- shouldLoad intentionally omitted to prevent re-initialization loop + }, []) const initializeConsentDefaults = () => { // Set up consent defaults before gtag loads (Consent Mode v2 requirement)