Explorar o código

web: Attempt at compliant, cookie-less anonymous tracking for the website (#8957)

Bruno Bergher hai 2 meses
pai
achega
eb68c65296

+ 3 - 3
apps/web-roo-code/src/components/CookieConsentWrapper.tsx

@@ -5,7 +5,7 @@ import ReactCookieConsent from "react-cookie-consent"
 import { Cookie } from "lucide-react"
 import { getDomain } from "tldts"
 import { CONSENT_COOKIE_NAME } from "@roo-code/types"
-import { dispatchConsentEvent } from "@/lib/analytics/consent-manager"
+import { handleConsentAccept, handleConsentReject } from "@/lib/analytics/consent-manager"
 
 /**
  * GDPR-compliant cookie consent banner component
@@ -23,11 +23,11 @@ export function CookieConsentWrapper() {
 	}, [])
 
 	const handleAccept = () => {
-		dispatchConsentEvent(true)
+		handleConsentAccept()
 	}
 
 	const handleDecline = () => {
-		dispatchConsentEvent(false)
+		handleConsentReject()
 	}
 
 	const extraCookieOptions = cookieDomain

+ 24 - 42
apps/web-roo-code/src/components/providers/posthog-provider.tsx

@@ -3,8 +3,8 @@
 import { usePathname, useSearchParams } from "next/navigation"
 import posthog from "posthog-js"
 import { PostHogProvider as OriginalPostHogProvider } from "posthog-js/react"
-import { useEffect, Suspense, useState } from "react"
-import { hasConsent, onConsentChange } from "@/lib/analytics/consent-manager"
+import { useEffect, Suspense } from "react"
+import { hasConsent } from "@/lib/analytics/consent-manager"
 
 function PageViewTracker() {
 	const pathname = usePathname()
@@ -28,11 +28,9 @@ function PageViewTracker() {
 }
 
 export function PostHogProvider({ children }: { children: React.ReactNode }) {
-	const [isInitialized, setIsInitialized] = useState(false)
-
 	useEffect(() => {
-		// Initialize PostHog only on the client side AND when consent is given
-		if (typeof window !== "undefined") {
+		// Initialize PostHog immediately on the client side
+		if (typeof window !== "undefined" && !posthog.__loaded) {
 			const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
 			const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST
 
@@ -52,48 +50,32 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
 				)
 			}
 
-			const initializePosthog = () => {
-				if (!isInitialized) {
-					posthog.init(posthogKey, {
-						api_host: posthogHost || "https://us.i.posthog.com",
-						capture_pageview: false,
-						loaded: (posthogInstance) => {
-							if (process.env.NODE_ENV === "development") {
-								posthogInstance.debug()
-							}
-						},
-						respect_dnt: true, // Respect Do Not Track
-					})
-					setIsInitialized(true)
-				}
-			}
-
-			// Check initial consent status
-			if (hasConsent()) {
-				initializePosthog()
-			}
+			// Check if user has already consented to cookies
+			const userHasConsented = hasConsent()
 
-			// Listen for consent changes
-			const unsubscribe = onConsentChange((consented) => {
-				if (consented && !isInitialized) {
-					initializePosthog()
-				}
+			// Initialize PostHog with appropriate persistence based on consent
+			posthog.init(posthogKey, {
+				api_host: posthogHost || "https://us.i.posthog.com",
+				capture_pageview: false, // We handle pageview tracking manually
+				loaded: (posthogInstance) => {
+					if (process.env.NODE_ENV === "development") {
+						posthogInstance.debug()
+					}
+				},
+				save_referrer: true, // Save referrer information
+				save_campaign_params: true, // Save UTM parameters
+				respect_dnt: true, // Respect Do Not Track
+				persistence: userHasConsented ? "localStorage+cookie" : "memory", // Use localStorage if consented, otherwise memory-only
+				opt_out_capturing_by_default: false, // Start tracking immediately
 			})
-
-			return () => {
-				unsubscribe()
-			}
 		}
-	}, [isInitialized])
+	}, [])
 
-	// Only provide PostHog context if it's initialized
 	return (
 		<OriginalPostHogProvider client={posthog}>
-			{isInitialized && (
-				<Suspense fallback={null}>
-					<PageViewTracker />
-				</Suspense>
-			)}
+			<Suspense fallback={null}>
+				<PageViewTracker />
+			</Suspense>
 			{children}
 		</OriginalPostHogProvider>
 	)

+ 25 - 0
apps/web-roo-code/src/lib/analytics/consent-manager.ts

@@ -5,6 +5,7 @@
 
 import { getCookieConsentValue } from "react-cookie-consent"
 import { CONSENT_COOKIE_NAME } from "@roo-code/types"
+import posthog from "posthog-js"
 
 export const CONSENT_EVENT = "cookieConsentChanged"
 
@@ -45,3 +46,27 @@ export function onConsentChange(callback: (consented: boolean) => void): () => v
 	window.addEventListener(CONSENT_EVENT, handler)
 	return () => window.removeEventListener(CONSENT_EVENT, handler)
 }
+
+/**
+ * Handle user accepting cookies
+ * Opts PostHog back into cookie-based tracking
+ */
+export function handleConsentAccept(): void {
+	if (typeof window !== "undefined" && posthog.__loaded) {
+		// User accepted - ensure localStorage+cookie persistence is enabled
+		posthog.opt_in_capturing()
+		posthog.set_config({
+			persistence: "localStorage+cookie",
+		})
+	}
+	dispatchConsentEvent(true)
+}
+
+/**
+ * Handle user rejecting cookies
+ * Switches PostHog to cookieless (memory-only) mode
+ */
+export function handleConsentReject(): void {
+	// User rejected - stick to cookieless mode
+	dispatchConsentEvent(false)
+}