Files
fotospiel-app/resources/js/components/analytics/MatomoTracker.tsx

140 lines
3.9 KiB
TypeScript

import { useEffect } from 'react';
import { usePage } from '@inertiajs/react';
import { useConsent } from '@/contexts/consent';
export type MatomoConfig = {
enabled: boolean;
url?: string;
siteId?: string;
};
declare global {
interface Window {
_paq?: Array<[string, ...unknown[]]>;
__matomoInitialized?: boolean;
__CSP_NONCE?: string;
}
}
interface MatomoTrackerProps {
config: MatomoConfig | undefined;
}
const MatomoTracker: React.FC<MatomoTrackerProps> = ({ config }) => {
let scriptNonce: string | undefined;
let analyticsConsent = false;
let pageUrl = typeof window !== 'undefined' ? window.location.pathname + window.location.search : '';
try {
const page = usePage<{ security?: { csp?: { scriptNonce?: string } } }>();
scriptNonce = (page.props.security as { csp?: { scriptNonce?: string } } | undefined)?.csp?.scriptNonce;
pageUrl = page.url || pageUrl;
} catch {
// Not in Inertia context; fall back to window values/meta tags
const metaNonce = typeof document !== 'undefined'
? document.querySelector('meta[name="csp-nonce"]')?.getAttribute('content')
: undefined;
scriptNonce = metaNonce ?? undefined;
}
try {
const consent = useConsent();
analyticsConsent = consent.hasConsent('analytics');
} catch {
// No consent provider available; default to no analytics
analyticsConsent = false;
}
useEffect(() => {
if (!config?.enabled || !config.url || !config.siteId || typeof window === 'undefined') {
return;
}
const base = config.url.replace(/\/$/, '');
const scriptSelector = `script[data-matomo="${base}"]`;
if (!analyticsConsent) {
const existing = document.querySelector<HTMLScriptElement>(scriptSelector);
existing?.remove();
if (window._paq) {
window._paq.length = 0;
}
delete window.__matomoInitialized;
return;
}
window._paq = window._paq || [];
const { _paq } = window;
if (!window.__matomoInitialized) {
_paq.push(['setTrackerUrl', `${base}/matomo.php`]);
_paq.push(['setSiteId', config.siteId]);
_paq.push(['disableCookies']);
_paq.push(['enableLinkTracking']);
if (!document.querySelector(scriptSelector)) {
const script = document.createElement('script');
script.async = true;
script.src = `${base}/matomo.js`;
script.dataset.matomo = base;
if (scriptNonce) {
script.setAttribute('nonce', scriptNonce);
} else if (typeof window !== 'undefined' && window.__CSP_NONCE) {
script.setAttribute('nonce', window.__CSP_NONCE);
} else {
const metaNonce = document
.querySelector('meta[name="csp-nonce"]')
?.getAttribute('content');
if (metaNonce) {
script.setAttribute('nonce', metaNonce);
}
}
document.body.appendChild(script);
}
window.__matomoInitialized = true;
}
}, [config, analyticsConsent, scriptNonce]);
useEffect(() => {
if (
!config?.enabled ||
!config.url ||
!config.siteId ||
typeof window === 'undefined' ||
!analyticsConsent
) {
return;
}
window._paq = window._paq || [];
const { _paq } = window;
const currentUrl =
typeof window !== 'undefined' ? `${window.location.origin}${pageUrl}` : pageUrl;
_paq.push(['setCustomUrl', currentUrl]);
if (typeof document !== 'undefined') {
_paq.push(['setDocumentTitle', document.title]);
}
_paq.push(['trackPageView']);
}, [config, analyticsConsent, pageUrl]);
if (!config?.enabled || !config.url || !config.siteId || !analyticsConsent) {
return null;
}
const base = config.url.replace(/\/$/, '');
const noscriptSrc = `${base}/matomo.php?idsite=${encodeURIComponent(config.siteId)}&rec=1`;
return (
<noscript>
<p>
<img src={noscriptSrc} style={{ border: 0 }} alt="" />
</p>
</noscript>
);
};
export default MatomoTracker;