Share the Django site's analytics consent instead of asking twice (same-origin tchatou-consent)
This commit is contained in:
parent
56ae05f56d
commit
900859acdf
3 changed files with 24 additions and 15 deletions
|
|
@ -4,9 +4,12 @@ import { getConfig } from '../core/config';
|
|||
import { getConsent, setConsent } from '../core/consent';
|
||||
import { initGa } from '../core/ga';
|
||||
|
||||
// First-visit analytics consent. Only shown when Google Analytics is configured
|
||||
// (config.analytics.gaId) and the visitor hasn't chosen yet. Accepting loads gtag;
|
||||
// rejecting means it never loads. Reject is as prominent as Accept (CNIL).
|
||||
// Analytics consent — a FALLBACK. The Django site (tchatou.fr) already asks and
|
||||
// stores the choice in the shared `tchatou-consent` entry (same origin), so anyone
|
||||
// arriving from there is never asked again. This only appears when GA is configured
|
||||
// AND no choice exists yet — i.e. a direct /app/ visit (installed PWA, bookmark) that
|
||||
// never hit the marketing site. Accepting loads gtag; rejecting means it never loads;
|
||||
// reject is as prominent as Accept (CNIL). The choice writes the same shared entry.
|
||||
export function ConsentBanner() {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(() => !!getConfig().analytics?.gaId && getConsent() === 'unset');
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
// Analytics consent (opt-in). Google Analytics sets cookies, so on an EU/French
|
||||
// site it must not load until the visitor agrees — gtag is gated on this. Stored in
|
||||
// localStorage; 'unset' means "not asked yet" (the banner shows). Withdrawable from
|
||||
// Settings. The first-party cookieless collector does NOT depend on this (no cookie).
|
||||
const KEY = 'tchatou:analytics-consent';
|
||||
// Analytics consent (opt-in) for Google Analytics. SHARED with the Django site:
|
||||
// tchatou.fr and tchatou.fr/app are the same origin, so we read/write the exact same
|
||||
// localStorage entry the marketing site's static/js/consent.js uses — key
|
||||
// `tchatou-consent`, value `{v:'granted'|'denied', t:epochMs}`, re-asked after ~6
|
||||
// months. A visitor who already chose on tchatou.fr is never asked again in the app;
|
||||
// the app's own banner only appears for someone who opens /app/ directly (e.g. the
|
||||
// installed PWA) and writes the same shared entry so the choice stays in sync.
|
||||
const KEY = 'tchatou-consent';
|
||||
const TTL = 1000 * 60 * 60 * 24 * 180; // re-ask after ~6 months (matches consent.js)
|
||||
export type Consent = 'granted' | 'denied' | 'unset';
|
||||
|
||||
export function getConsent(): Consent {
|
||||
try {
|
||||
const v = localStorage.getItem(KEY);
|
||||
return v === 'granted' || v === 'denied' ? v : 'unset';
|
||||
} catch {
|
||||
return 'unset';
|
||||
}
|
||||
const o = JSON.parse(localStorage.getItem(KEY) || 'null');
|
||||
if (o && o.t && Date.now() - o.t < TTL && (o.v === 'granted' || o.v === 'denied')) return o.v;
|
||||
} catch { /* bad JSON / no storage */ }
|
||||
return 'unset';
|
||||
}
|
||||
|
||||
export function setConsent(v: 'granted' | 'denied'): void {
|
||||
try { localStorage.setItem(KEY, v); } catch { /* private mode / quota */ }
|
||||
try { localStorage.setItem(KEY, JSON.stringify({ v, t: Date.now() })); } catch { /* private mode / quota */ }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ export function initGa(): void {
|
|||
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(id);
|
||||
document.head.appendChild(s);
|
||||
w.gtag('js', new Date());
|
||||
w.gtag('config', id);
|
||||
// Match the Django site's config: no Google Signals / ad-personalisation, so GA
|
||||
// sets only the first-party _ga cookies the consent banner declares (and doesn't
|
||||
// use the region1.analytics.google.com signals endpoint).
|
||||
w.gtag('config', id, { allow_google_signals: false, allow_ad_personalization_signals: false });
|
||||
bus.on('connected', () => w.gtag('event', 'login', { method: 'irc' }));
|
||||
bus.on('buffer.active', (name: unknown) => {
|
||||
const n = String(name ?? '');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue