51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
/**
|
|
* Свой учёт посетителей портала (spec 2026-07-13). Работает параллельно с Метрикой,
|
|
* её не заменяет и не трогает. Пишем только ИМЯ экрана — без содержимого (ПДн не уходят).
|
|
*/
|
|
const sentOnce = new Set<string>();
|
|
|
|
function send(event: string, extra: Record<string, unknown> = {}): void {
|
|
void fetch('/api/track', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
keepalive: true,
|
|
body: JSON.stringify({
|
|
event,
|
|
host: location.hostname,
|
|
path: location.pathname,
|
|
referrer: document.referrer || null,
|
|
...extra,
|
|
}),
|
|
}).catch(() => undefined);
|
|
}
|
|
|
|
/** Первый заход в портал + «человек шевельнулся». Идемпотентно. */
|
|
export function trackVisit(): void {
|
|
if (sentOnce.has('view')) return;
|
|
sentOnce.add('view');
|
|
send('view');
|
|
|
|
const events = ['scroll', 'touchstart', 'mousemove', 'keydown'];
|
|
const onFirst = () => {
|
|
if (!sentOnce.has('alive')) {
|
|
sentOnce.add('alive');
|
|
send('alive');
|
|
}
|
|
events.forEach((e) => window.removeEventListener(e, onFirst));
|
|
};
|
|
events.forEach((e) => window.addEventListener(e, onFirst, { once: true, passive: true }));
|
|
}
|
|
|
|
/** Открыт экран кабинета — только имя маршрута. */
|
|
export function trackScreen(screen: string): void {
|
|
send('portal_screen', { screen });
|
|
}
|
|
|
|
/** Открыт экран регистрации. */
|
|
export function trackRegisterOpen(): void {
|
|
if (sentOnce.has('register_open')) return;
|
|
sentOnce.add('register_open');
|
|
send('register_open');
|
|
}
|