Files
RecipesCam/docker/frontend/src/track.ts
T
3dtours 89beb5f160 web: put the traffic screen in the admin frame
installTracking() beacons one view per page load and one click per control that
carries a data-key, so every existing button is already counted. The new STATS
pane reads it back: a 7/30/90-day range, the three totals, an SVG timeline and
eight proportional bar lists (pages, clicked features, country, region, city,
browser, system, device).
2026-09-18 14:23:25 +07:00

49 lines
1.8 KiB
TypeScript

// The page counter's browser end. One beacon per page view, one per click on an
// element that carries a `data-key` — that attribute is already on nearly every
// control, so it doubles as the feature's name. Nothing here may break the app:
// every path is wrapped, a refused beacon is simply not counted, and a visitor
// who asked not to be tracked is left alone.
const ENDPOINT = '/api/events';
type Kind = 'view' | 'click';
function send(kind: Kind, path: string, target?: string): void {
try {
if (navigator.doNotTrack === '1') return;
const body = JSON.stringify({ kind, path, target });
const blob = new Blob([body], { type: 'application/json' });
// sendBeacon survives the page unload a click often triggers; the fetch is
// the fallback for a browser that has no beacon (or refuses this one).
if (navigator.sendBeacon?.(ENDPOINT, blob)) return;
void fetch(ENDPOINT, {
method: 'POST',
body,
keepalive: true,
headers: { 'content-type': 'application/json' },
}).catch(() => {});
} catch {
// Analytics is best-effort — a failure here must never reach the app.
}
}
export function track(kind: Kind, path: string, target?: string): void {
send(kind, path, target);
}
// Call once, before the first render. The view is recorded now; the click is
// recorded from one capturing listener on the document, so a control keeps its
// own handler and needs no tracking code of its own.
export function installTracking(): void {
send('view', window.location.pathname);
document.addEventListener(
'click',
(event) => {
const node = event.target;
const el = node instanceof Element ? node.closest('[data-key]') : null;
const target = el?.getAttribute('data-key');
if (target) send('click', window.location.pathname, target);
},
true,
);
}