diff --git a/packages/common/consent-state.ts b/packages/common/consent-state.ts index 9d024c9be07..d0b6ce8c4a1 100644 --- a/packages/common/consent-state.ts +++ b/packages/common/consent-state.ts @@ -4,6 +4,35 @@ import { proxy, snapshot, useSnapshot } from 'valtio' import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from './constants' +/** + * Check if the user previously accepted all consent services by reading + * the compressed ucData format that the GTM/Usercentrics integration writes. + * + * Context (FE-2648): After acceptAllServices(), the GTM script's Usercentrics + * integration replaces uc_settings with ucString/ucData. On the next page load, + * UC.init() can't read that format and treats the user as new. This function + * detects that prior consent so we can silently re-accept. + */ +function hasPreviousConsentInUcData(): boolean { + try { + const ucData = localStorage?.getItem('ucData') + if (!ucData) return false + + const data = JSON.parse(ucData) + const services = data?.consent?.services + if (!services || typeof services !== 'object') return false + + const serviceValues = Object.values(services) + if (serviceValues.length === 0) return false + + return serviceValues.every( + (s) => typeof s === 'object' && s !== null && (s as { consent: boolean }).consent === true + ) + } catch { + return false + } +} + export const consentState = proxy({ // Usercentrics state UC: null as Usercentrics | null, @@ -73,27 +102,59 @@ async function initUserCentrics() { return } - const { default: Usercentrics } = await import('@usercentrics/cmp-browser-sdk') + // Check for prior consent BEFORE UC.init(), which can't read the compressed + // ucData format written by the GTM/Usercentrics integration (FE-2648). + const previouslyAccepted = hasPreviousConsentInUcData() - const UC = new Usercentrics(process.env.NEXT_PUBLIC_USERCENTRICS_RULESET_ID!, { - rulesetId: process.env.NEXT_PUBLIC_USERCENTRICS_RULESET_ID, - useRulesetId: true, - }) + try { + const { default: Usercentrics } = await import('@usercentrics/cmp-browser-sdk') - const initialUIValues = await UC.init() + const UC = new Usercentrics(process.env.NEXT_PUBLIC_USERCENTRICS_RULESET_ID!, { + rulesetId: process.env.NEXT_PUBLIC_USERCENTRICS_RULESET_ID, + useRulesetId: true, + }) - consentState.UC = UC - const hasConsented = UC.areAllConsentsAccepted() + const initialUIValues = await UC.init() - // 0 = first layer, aka show consent toast - consentState.showConsentToast = initialUIValues.initialLayer === 0 - consentState.hasConsented = hasConsented - consentState.categories = UC.getCategoriesBaseInfo() + consentState.UC = UC + const hasConsented = UC.areAllConsentsAccepted() - // If the user has previously consented (before usercentrics), accept all services - if (!hasConsented && localStorage?.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) === 'true') { - consentState.acceptAll() - localStorage.removeItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) + // If the SDK wants to show the banner but the user previously accepted + // (ucData exists from a prior GTM-mediated accept), silently re-accept + // instead of showing the banner again. + if (initialUIValues.initialLayer === 0 && !hasConsented && previouslyAccepted) { + consentState.hasConsented = true + consentState.showConsentToast = false + consentState.categories = UC.getCategoriesBaseInfo() + localStorage?.removeItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) + UC.acceptAllServices() + .then(() => { + consentState.categories = UC.getCategoriesBaseInfo() + }) + .catch(() => { + // If re-accept fails, fall back to showing the banner + consentState.hasConsented = false + consentState.showConsentToast = true + }) + return + } + + // 0 = first layer, aka show consent toast + consentState.showConsentToast = initialUIValues.initialLayer === 0 + consentState.hasConsented = hasConsented + consentState.categories = UC.getCategoriesBaseInfo() + + // If the user has previously consented (before usercentrics), accept all services + if (!hasConsented && localStorage?.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) === 'true') { + consentState.acceptAll() + localStorage.removeItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) + } + } catch (error) { + console.error('Failed to initialize Usercentrics:', error) + // If SDK fails but user previously accepted, honor that + if (previouslyAccepted) { + consentState.hasConsented = true + } } }