From 8b1eddbd7beada498d92e9b4da11bc7236317c25 Mon Sep 17 00:00:00 2001 From: VasN Date: Tue, 28 Jul 2026 12:10:58 +0200 Subject: [PATCH] feat: iubenda CMP with TCF consent for ads tracking Load the iubenda Cookie Solution (TCF mode) for GDPR-covered webapp users behind the iubenda_cmp flag, mirror its consent into the existing ilikecookies* cookies, populate the ad tracker consent macros from __tcfapi, send gdpr/gdpr_consent/addtl_consent on ad requests, attach the iubenda consent id to analytics events, and forward the stored TC string to boot via the x-gdpr-consent header. --- packages/shared/src/components/Feed.spec.tsx | 10 +- .../cards/ad/common/AdMeasurement.tsx | 10 +- .../features/monetization/adConsent.spec.ts | 45 ++++--- .../src/features/monetization/adConsent.ts | 36 ++--- .../features/monetization/measurementFrame.ts | 5 +- .../monetization/useAdMacroContext.ts | 9 +- .../src/features/monetization/useAdQuery.ts | 8 +- .../src/features/monetization/useFetchAd.ts | 5 +- .../features/monetization/useTcfConsent.ts | 12 ++ packages/shared/src/hooks/log/useLogQueue.ts | 2 + .../shared/src/hooks/log/useLogSharedProps.ts | 2 + .../shared/src/hooks/useCookieBanner.spec.tsx | 28 ++++ packages/shared/src/hooks/useCookieBanner.ts | 10 ++ packages/shared/src/hooks/useCookieConsent.ts | 12 +- packages/shared/src/lib/ads.spec.ts | 28 ++++ packages/shared/src/lib/ads.ts | 46 ++++++- packages/shared/src/lib/boot.ts | 5 + packages/shared/src/lib/featureManagement.ts | 4 + packages/shared/src/lib/iubenda.spec.ts | 12 +- packages/shared/src/lib/iubenda.ts | 15 ++- packages/shared/src/lib/tcf.spec.ts | 91 +++++++++++++ packages/shared/src/lib/tcf.ts | 88 ++++++++++++ packages/webapp/__tests__/BookmarksPage.tsx | 4 +- packages/webapp/__tests__/MyFeedPage.tsx | 4 +- packages/webapp/__tests__/SourcePage.tsx | 4 +- packages/webapp/__tests__/SquadFeedPage.tsx | 4 +- packages/webapp/__tests__/TagPage.tsx | 4 +- packages/webapp/components/Iubenda.tsx | 127 ++++++++++++++++++ packages/webapp/pages/_app.tsx | 2 + packages/webapp/pages/embed/mf.tsx | 8 +- packages/webapp/pages/settings/privacy.tsx | 32 ++++- 31 files changed, 597 insertions(+), 75 deletions(-) create mode 100644 packages/shared/src/features/monetization/useTcfConsent.ts create mode 100644 packages/shared/src/lib/tcf.spec.ts create mode 100644 packages/shared/src/lib/tcf.ts create mode 100644 packages/webapp/components/Iubenda.tsx diff --git a/packages/shared/src/components/Feed.spec.tsx b/packages/shared/src/components/Feed.spec.tsx index 38a9653a692..cf216b24fdb 100644 --- a/packages/shared/src/components/Feed.spec.tsx +++ b/packages/shared/src/components/Feed.spec.tsx @@ -240,7 +240,9 @@ function renderComponent( const resolvedUser = arguments.length < 2 ? defaultUser : user; mocks.forEach(mockGraphQL); - nock('http://localhost:3000').get('/v1/a?active=false').reply(200, [ad]); + nock('http://localhost:3000') + .get('/v1/a?active=false&gdpr=0') + .reply(200, [ad]); const settingsContext: SettingsContextData = { ...baseSettingsContext, setTheme: jest.fn(), @@ -1807,9 +1809,11 @@ const renderWithHighlightLayout = ({ mockGraphQL(createFeedMock(buildFeedPage(posts))); // First ad page uses active=false, subsequent pages use active=true. Mock // both up to a handful of refills so multi-ad scenarios don't run dry. - nock('http://localhost:3000').get('/v1/a?active=false').reply(200, [ad]); nock('http://localhost:3000') - .get('/v1/a?active=true') + .get('/v1/a?active=false&gdpr=0') + .reply(200, [ad]); + nock('http://localhost:3000') + .get('/v1/a?active=true&gdpr=0') .times(10) .reply(200, [ad]); diff --git a/packages/shared/src/components/cards/ad/common/AdMeasurement.tsx b/packages/shared/src/components/cards/ad/common/AdMeasurement.tsx index 5ff7ed2e986..d05594c18cd 100644 --- a/packages/shared/src/components/cards/ad/common/AdMeasurement.tsx +++ b/packages/shared/src/components/cards/ad/common/AdMeasurement.tsx @@ -57,6 +57,8 @@ interface FramedProps { tags: AdMeasurementTag[]; overlay: boolean; gdprApplies?: boolean; + consentString?: string; + addtlConsent?: string; } /** @@ -68,6 +70,8 @@ const FramedMeasurement = ({ tags, overlay, gdprApplies, + consentString, + addtlConsent, }: FramedProps): ReactElement => { const iframeRef = useRef(null); const [frameReady, setFrameReady] = useState(false); @@ -106,10 +110,12 @@ const FramedMeasurement = ({ type: 'init', tags, gdprApplies, + consentString, + addtlConsent, }, frameOrigin, ); - }, [frameReady, tags, gdprApplies, frameOrigin]); + }, [frameReady, tags, gdprApplies, consentString, addtlConsent, frameOrigin]); return (