Skip to content

Commit a5504f3

Browse files
committed
fix: gate GA behind analytics consent
1 parent 97d38c3 commit a5504f3

4 files changed

Lines changed: 56 additions & 8 deletions

File tree

LYNX/server/server.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ const injectGoogleConsentDefaults = (html) => {
377377
'personalization_storage': 'denied',
378378
'wait_for_update': 2000
379379
});
380-
gtag('js', new Date());
381380
window.__lynxGcmDefaultConsentSet = true;
382381
</script>`;
383382

LYNX/server/server.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ describe('API Endpoints', () => {
241241
expect(response.text).toContain("'analytics_storage': 'denied'");
242242
expect(response.text).toContain("'ad_user_data': 'denied'");
243243
expect(response.text).toContain("'ad_personalization': 'denied'");
244+
expect(response.text).not.toContain("gtag('js'");
244245
expect(response.text.indexOf('id="lynx-gcm-default-consent"')).toBeLessThan(
245246
response.text.indexOf('<script type="module"')
246247
);

LYNX/src/lib/consent-manager.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class ConsentManager {
117117
private consent: ConsentRecord | null = null;
118118
private initialized = false;
119119
private listeners: Set<ConsentChangeListener> = new Set();
120+
private externalConsent: Partial<Record<ConsentCategory, boolean>> | null = null;
120121
/**
121122
* Scripts waiting for a specific category's consent.
122123
* Keyed by category; values are loader functions to call when consent is granted.
@@ -187,7 +188,6 @@ class ConsentManager {
187188
personalization_storage: 'denied',
188189
wait_for_update: 2000,
189190
});
190-
win.gtag('js', new Date());
191191
win.__lynxGcmDefaultConsentSet = true;
192192
}
193193

@@ -202,6 +202,9 @@ class ConsentManager {
202202
*/
203203
isGranted(category: ConsentCategory): boolean {
204204
if (ALWAYS_ACTIVE.includes(category)) return true;
205+
if (this.config?.mode === 'builder') {
206+
return this._isExternalCategoryGranted(category);
207+
}
205208
if (!this.consent) return false;
206209
if (!this._isConsentFresh()) return false;
207210
const cfg = this.config?.hardcoded;
@@ -480,6 +483,26 @@ class ConsentManager {
480483
}
481484
}
482485

486+
private _isExternalCategoryGranted(category: ConsentCategory): boolean {
487+
if (!this.config?.enabled || this.config.mode !== 'builder') return false;
488+
489+
const cookiebotConsent = (window as any).Cookiebot?.consent;
490+
if (cookiebotConsent && this.config.builder?.provider === 'cookiebot') {
491+
if (category === 'preferences') return cookiebotConsent.preferences === true;
492+
if (category === 'analytics') return cookiebotConsent.statistics === true;
493+
if (category === 'marketing') return cookiebotConsent.marketing === true;
494+
}
495+
496+
return this.externalConsent?.[category] === true;
497+
}
498+
499+
private _setExternalConsent(categories: Record<ConsentCategory, boolean>): void {
500+
this.externalConsent = categories;
501+
this._dispatchGcmUpdate(categories);
502+
this._dispatchPendingScripts();
503+
this._notifyListeners();
504+
}
505+
483506
private _injectHtmlSnippet(target: HTMLElement, html: string, markerId?: string): boolean {
484507
const template = document.createElement('template');
485508
template.innerHTML = html;
@@ -544,6 +567,7 @@ _iub.csConfiguration = {
544567
console.warn('[LynxConsent] Cookiebot: missing scriptId (cbid)');
545568
return;
546569
}
570+
this._wireCookiebotConsentEvents();
547571
const script = document.createElement('script');
548572
script.id = 'lynx-cmp-script';
549573
script.setAttribute('data-cbid', cfg.scriptId);
@@ -553,6 +577,29 @@ _iub.csConfiguration = {
553577
document.head.appendChild(script);
554578
}
555579

580+
private _wireCookiebotConsentEvents(): void {
581+
const win = window as any;
582+
if (win.__lynxCookiebotConsentEventsWired) return;
583+
win.__lynxCookiebotConsentEventsWired = true;
584+
585+
const syncCookiebotConsent = () => {
586+
const consent = win.Cookiebot?.consent;
587+
if (!consent) return;
588+
this._setExternalConsent({
589+
necessary: true,
590+
preferences: consent.preferences === true,
591+
analytics: consent.statistics === true,
592+
marketing: consent.marketing === true,
593+
});
594+
};
595+
596+
window.addEventListener('CookiebotOnConsentReady', syncCookiebotConsent);
597+
window.addEventListener('CookiebotOnAccept', syncCookiebotConsent);
598+
window.addEventListener('CookiebotOnDecline', syncCookiebotConsent);
599+
window.addEventListener('CookiebotOnLoad', syncCookiebotConsent);
600+
syncCookiebotConsent();
601+
}
602+
556603
private _injectCookieYes(cfg: BuilderConfig['providerConfig']): void {
557604
if (!cfg.scriptId) {
558605
console.warn('[LynxConsent] CookieYes: missing scriptId');

LYNX/src/pages/Index.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,24 @@ const Index = () => {
116116
// This is idempotent and runs only when native or external consent is enabled.
117117
consentManager.ensureGoogleConsentDefaults();
118118

119-
if (typeof win.gtag !== 'function') {
120-
win.dataLayer = win.dataLayer || [];
121-
win.gtag = function () { win.dataLayer.push(arguments); };
122-
win.gtag('js', new Date());
123-
}
124-
125119
// Load gtag.js and call config ONLY after analytics consent is granted.
126120
// No GA network request of any kind is made before this callback fires.
127121
const gaId = googleAnalyticsId;
128122
consentManager.registerConsentDependentScript('analytics', () => {
123+
if (!consentManager.isGranted('analytics')) return;
124+
consentManager.ensureGoogleConsentDefaults();
125+
if (typeof win.gtag !== 'function') {
126+
win.dataLayer = win.dataLayer || [];
127+
win.gtag = function () { win.dataLayer.push(arguments); };
128+
}
129129
if (!document.getElementById('lynx-ga-script')) {
130130
const script = document.createElement('script');
131131
script.id = 'lynx-ga-script';
132132
script.async = true;
133133
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}`;
134134
document.head.appendChild(script);
135135
}
136+
win.gtag?.('js', new Date());
136137
win.gtag?.('config', gaId);
137138
});
138139
}

0 commit comments

Comments
 (0)