10s was unnecessarily long — any EEA user with Funding Choices properly loaded will have __tcfapi within 3s. Reducing the timeout means non-TCF visitors (Americas etc.) receive the granted fallback 7 seconds sooner. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
4.8 KiB
JavaScript
146 lines
4.8 KiB
JavaScript
/**
|
|
* ATT Consent — TCF v2.2 Bridge
|
|
*
|
|
* Used when cmp_mode = 'tcf_bridge'. A separate, IAB TCF v2.2 certified CMP
|
|
* (e.g. Google's "Privacy & messaging" / Funding Choices, Tarteaucitron.js)
|
|
* is expected to render the banner and emit gtag('consent', ...) calls.
|
|
*
|
|
* This script:
|
|
* 1. Waits for window.__tcfapi to appear.
|
|
* 2. Subscribes to the TCF event stream.
|
|
* 3. Maps TCF v2.2 purposes back to this plugin's three categories
|
|
* (functional / analytics / marketing).
|
|
* 4. Calls AttConsent.update() so custom scripts, the att_cc_consent
|
|
* cookie cache and the WP Consent API bridge all stay in sync.
|
|
* 5. Wires the floating widget click to the CMP's "re-open" entry point.
|
|
*
|
|
* If __tcfapi never appears (CMP not installed, or visitor is outside the
|
|
* TCF jurisdiction and the CMP loaded no stub) the plugin's default-denied
|
|
* state simply persists — no fail-open here.
|
|
*
|
|
* TCF purpose → category mapping:
|
|
* 1, 5, 6 → functional (device storage, personalised content)
|
|
* 7, 8, 9, 10 → analytics (ad/content measurement, market research, product dev)
|
|
* 2, 3, 4 → marketing (basic ads, ad profiling, personalised ads)
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
if (typeof window.AttConsent === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
var MAX_WAIT_MS = 3000;
|
|
var POLL_INTERVAL = 100;
|
|
var elapsed = 0;
|
|
var lastSignature = '';
|
|
|
|
function mapTcDataToCategories(tcData) {
|
|
// Outside the TCF scope (typically non-EEA/UK/CH): the CMP isn't
|
|
// asserting any restriction, so treat as full consent. The site
|
|
// operator remains responsible for any other applicable law (GPP/CCPA).
|
|
if (tcData && tcData.gdprApplies === false) {
|
|
return { functional: true, analytics: true, marketing: true };
|
|
}
|
|
|
|
var purposes = (tcData && tcData.purpose && tcData.purpose.consents) || {};
|
|
return {
|
|
functional: !!(purposes[1] || purposes[5] || purposes[6]),
|
|
analytics: !!(purposes[7] || purposes[8] || purposes[9] || purposes[10]),
|
|
marketing: !!(purposes[2] || purposes[3] || purposes[4])
|
|
};
|
|
}
|
|
|
|
function handleTcData(tcData, success) {
|
|
if (!success || !tcData) {
|
|
return;
|
|
}
|
|
// Only act on terminal states.
|
|
if (tcData.eventStatus !== 'tcloaded' && tcData.eventStatus !== 'useractioncomplete') {
|
|
return;
|
|
}
|
|
|
|
var cats = mapTcDataToCategories(tcData);
|
|
// Suppress repeat updates with identical state — TCF can fire
|
|
// multiple times per page (e.g. tcloaded then useractioncomplete).
|
|
var sig = cats.functional + '|' + cats.analytics + '|' + cats.marketing;
|
|
if (sig === lastSignature) {
|
|
return;
|
|
}
|
|
lastSignature = sig;
|
|
|
|
try {
|
|
window.AttConsent.update(cats);
|
|
} catch (e) {
|
|
// Swallow — don't break the page if consent-manager errored.
|
|
}
|
|
}
|
|
|
|
function attach() {
|
|
try {
|
|
window.__tcfapi('addEventListener', 2, handleTcData);
|
|
} catch (e) {
|
|
// Bad CMP stub. Nothing else to do.
|
|
}
|
|
}
|
|
|
|
function poll() {
|
|
if (typeof window.__tcfapi === 'function') {
|
|
attach();
|
|
return;
|
|
}
|
|
elapsed += POLL_INTERVAL;
|
|
if (elapsed >= MAX_WAIT_MS) {
|
|
// __tcfapi never appeared: visitor is outside TCF jurisdiction (e.g. Americas)
|
|
// or the CMP script was blocked. GDPR opt-in doesn't apply; grant full consent
|
|
// so these visitors aren't silently tracked as denied.
|
|
try {
|
|
window.AttConsent.update({ functional: true, analytics: true, marketing: true });
|
|
} catch (e) {}
|
|
return;
|
|
}
|
|
setTimeout(poll, POLL_INTERVAL);
|
|
}
|
|
|
|
// --- Floating widget: re-open the external CMP UI ---
|
|
|
|
var widget = document.getElementById('att-cc-widget');
|
|
if (widget) {
|
|
widget.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
|
|
// 1. Google Funding Choices: use its own re-prompt API.
|
|
// FC accepts but silently ignores the IAB 'displayConsentUi'
|
|
// command, so we must try the FC path FIRST when FC is detected.
|
|
if (window.googlefc && typeof window.googlefc.showRevocationMessage === 'function') {
|
|
try { window.googlefc.showRevocationMessage(); return; } catch (err) {}
|
|
}
|
|
if (window.googlefc && window.googlefc.callbackQueue) {
|
|
window.googlefc.callbackQueue.push({
|
|
CONSENT_DATA_READY: function () {
|
|
if (typeof window.googlefc.showRevocationMessage === 'function') {
|
|
window.googlefc.showRevocationMessage();
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 2. Generic IAB TCF v2.2 re-prompt — for non-FC CMPs that
|
|
// actually implement this (Sourcepoint, Didomi, etc.).
|
|
if (typeof window.__tcfapi === 'function') {
|
|
try { window.__tcfapi('displayConsentUi', 2, function () {}); return; } catch (err) {}
|
|
}
|
|
|
|
// 3. Last-ditch: scroll to a #privacy / #cookies anchor if the
|
|
// site has one in its footer. Fail silently otherwise.
|
|
var fallback = document.querySelector('a[href*="#cookie"], a[href*="#privacy"]');
|
|
if (fallback) {
|
|
fallback.click();
|
|
}
|
|
});
|
|
}
|
|
|
|
poll();
|
|
})();
|