import { getGlobalThis } from '@wener/utils'; import { createStore } from 'zustand'; import { mutative } from 'zustand-mutative'; import type { MatomoTracker } from './types'; function setupMatomo({ global = getGlobalThis(), url, siteId, queue = [], }: { global?: any; siteId?: string; url?: string; queue?: any[]; } = {}) { if (!url || !siteId) { return; } const G = global; if (typeof G === 'undefined' || G['Matomo']?.initialized) { return; } const _paq = (G._paq = G._paq || queue); /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); const u = url; _paq.push(['setTrackerUrl', u + 'matomo.php']); _paq.push(['setSiteId', siteId]); return new Promise((resolve, reject) => { const doc = document; const ele$ = doc.createElement('script'); const parent$ = doc.getElementsByTagName('script')[0]; ele$.onload = () => { resolve(window.Matomo); }; ele$.onerror = reject; ele$.type = 'text/javascript'; ele$.async = true; ele$.defer = true; ele$.src = u + 'matomo.js'; parent$.parentNode?.insertBefore(ele$, parent$); }); } interface TrackerStoreState { tracker: MatomoTracker; init: (o: { baseUrl?: string; siteId?: string }) => void; } export const TrackerStore = createStore()( mutative((setState, getState, store) => { const g: any = getGlobalThis(); const queue: any[] = []; let pending; return { init: ({ baseUrl, siteId }) => { pending ||= setupMatomo({ url: baseUrl, siteId: siteId, }); }, tracker: createProxyTracker({ queue, invoke: ({ method, args }) => {}, }), } as TrackerStoreState; }), ); function createProxyTracker({ queue = [], invoke, }: { queue?: any[]; invoke?: (o: { method: string; args: any[]; queue: any[] }) => void | boolean; }): MatomoTracker { return new Proxy( { _paq: queue, }, { get(target, prop) { switch (prop) { case '_paq': return target._paq; } return (...args: any[]) => { switch (prop) { case 'push': queue.push(...args); return; default: if (typeof prop !== 'string') { throw new Error(`Invalid tracker method ${String(prop)}`); } } let skip = false; if (invoke) { skip = invoke?.({ method: prop, args, queue }) === true; } if (!skip) { queue.push([prop, ...args]); } // Limit queue size if (queue.length > 1000) { queue.splice(0, 1000 - queue.length); } }; }, }, ) as any; } export function getTracker(): MatomoTracker { return TrackerStore.getState().tracker; }