Skip to content

Commit 5fb36d9

Browse files
feat: implement Google Consent Mode v2 with cookieless pings (#8987)
* 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 * fix: remove shouldLoad from useEffect dependency array to prevent re-initialization loop --------- Co-authored-by: Roo Code <[email protected]>
1 parent 5b753f5 commit 5fb36d9

File tree

1 file changed

+87
-26
lines changed

1 file changed

+87
-26
lines changed

apps/web-roo-code/src/components/providers/google-analytics-provider.tsx

Lines changed: 87 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,72 +8,133 @@ import { hasConsent, onConsentChange } from "@/lib/analytics/consent-manager"
88
const GTM_ID = "AW-17391954825"
99

1010
/**
11-
* Google Analytics Provider
12-
* Only loads Google Tag Manager after user gives consent
11+
* Google Analytics Provider with Consent Mode v2
12+
* Implements cookieless pings and advanced consent management
1313
*/
1414
export function GoogleAnalyticsProvider({ children }: { children: React.ReactNode }) {
1515
const [shouldLoad, setShouldLoad] = useState(false)
1616

1717
useEffect(() => {
18+
// Initialize consent defaults BEFORE loading gtag.js (required for Consent Mode v2)
19+
initializeConsentDefaults()
20+
1821
// Check initial consent status
1922
if (hasConsent()) {
2023
setShouldLoad(true)
21-
initializeGoogleAnalytics()
24+
updateConsentGranted()
2225
}
2326

2427
// Listen for consent changes
2528
const unsubscribe = onConsentChange((consented) => {
26-
if (consented && !shouldLoad) {
27-
setShouldLoad(true)
28-
initializeGoogleAnalytics()
29+
if (consented) {
30+
if (!shouldLoad) {
31+
setShouldLoad(true)
32+
}
33+
updateConsentGranted()
34+
} else {
35+
updateConsentDenied()
2936
}
3037
})
3138

3239
return unsubscribe
33-
}, [shouldLoad])
40+
// eslint-disable-next-line react-hooks/exhaustive-deps -- shouldLoad intentionally omitted to prevent re-initialization loop
41+
}, [])
3442

35-
const initializeGoogleAnalytics = () => {
36-
// Initialize the dataLayer and gtag function
43+
const initializeConsentDefaults = () => {
44+
// Set up consent defaults before gtag loads (Consent Mode v2 requirement)
3745
if (typeof window !== "undefined") {
3846
window.dataLayer = window.dataLayer || []
3947
window.gtag = function (...args: GtagArgs) {
4048
window.dataLayer.push(args)
4149
}
42-
window.gtag("js", new Date())
43-
window.gtag("config", GTM_ID)
50+
51+
// Set default consent state to 'denied' with cookieless pings enabled
52+
window.gtag("consent", "default", {
53+
ad_storage: "denied",
54+
ad_user_data: "denied",
55+
ad_personalization: "denied",
56+
analytics_storage: "denied",
57+
functionality_storage: "denied",
58+
personalization_storage: "denied",
59+
security_storage: "granted", // Always granted for security
60+
wait_for_update: 500, // Wait 500ms for consent before sending data
61+
})
62+
63+
// Enable cookieless pings for Google Ads
64+
window.gtag("set", "url_passthrough", true)
4465
}
4566
}
4667

47-
// Only render Google Analytics scripts if consent is given
48-
if (!shouldLoad) {
49-
return <>{children}</>
68+
const updateConsentGranted = () => {
69+
// User accepted cookies - update consent to granted
70+
if (typeof window !== "undefined" && window.gtag) {
71+
window.gtag("consent", "update", {
72+
ad_storage: "granted",
73+
ad_user_data: "granted",
74+
ad_personalization: "granted",
75+
analytics_storage: "granted",
76+
functionality_storage: "granted",
77+
personalization_storage: "granted",
78+
})
79+
}
5080
}
5181

82+
const updateConsentDenied = () => {
83+
// User declined cookies - keep consent denied (cookieless pings still work)
84+
if (typeof window !== "undefined" && window.gtag) {
85+
window.gtag("consent", "update", {
86+
ad_storage: "denied",
87+
ad_user_data: "denied",
88+
ad_personalization: "denied",
89+
analytics_storage: "denied",
90+
functionality_storage: "denied",
91+
personalization_storage: "denied",
92+
})
93+
}
94+
}
95+
96+
// Always render scripts (Consent Mode v2 needs gtag loaded even without consent)
97+
// Cookieless pings will work with denied consent
98+
5299
return (
53100
<>
54-
{/* Google tag (gtag.js) - Only loads after consent */}
101+
{/* Google tag (gtag.js) - Loads immediately for Consent Mode v2 */}
55102
<Script
56103
src={`https://www.googletagmanager.com/gtag/js?id=${GTM_ID}`}
57104
strategy="afterInteractive"
58105
onLoad={() => {
59-
console.log("Google Analytics loaded with consent")
106+
// Initialize gtag config after script loads
107+
if (typeof window !== "undefined" && window.gtag) {
108+
window.gtag("js", new Date())
109+
window.gtag("config", GTM_ID)
110+
}
60111
}}
61112
/>
62-
<Script id="google-analytics-init" strategy="afterInteractive">
63-
{`
64-
window.dataLayer = window.dataLayer || [];
65-
function gtag(){dataLayer.push(arguments);}
66-
gtag('js', new Date());
67-
gtag('config', '${GTM_ID}');
68-
`}
69-
</Script>
70113
{children}
71114
</>
72115
)
73116
}
74117

75-
// Type definitions for Google Analytics
76-
type GtagArgs = ["js", Date] | ["config", string, GtagConfig?] | ["event", string, GtagEventParameters?]
118+
// Type definitions for Google Analytics with Consent Mode v2
119+
type ConsentState = "granted" | "denied"
120+
121+
interface ConsentParams {
122+
ad_storage?: ConsentState
123+
ad_user_data?: ConsentState
124+
ad_personalization?: ConsentState
125+
analytics_storage?: ConsentState
126+
functionality_storage?: ConsentState
127+
personalization_storage?: ConsentState
128+
security_storage?: ConsentState
129+
wait_for_update?: number
130+
}
131+
132+
type GtagArgs =
133+
| ["js", Date]
134+
| ["config", string, GtagConfig?]
135+
| ["event", string, GtagEventParameters?]
136+
| ["consent", "default" | "update", ConsentParams]
137+
| ["set", string, unknown]
77138

78139
interface GtagConfig {
79140
[key: string]: unknown

0 commit comments

Comments
 (0)