140 lines
3.9 KiB
TypeScript
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;
|