58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { apiUrl } from '../lib/api';
|
|
|
|
/**
|
|
* Sends a telemetry beacon every 30 seconds with session duration
|
|
* and the number of active filters (parsed from the URL `f` param).
|
|
* On the first beacon, also sends the entry path and referrer domain.
|
|
*/
|
|
export function useTelemetry() {
|
|
const startTime = useRef(Date.now());
|
|
const entryPath = useRef(window.location.pathname);
|
|
const referrer = useRef(extractReferrerDomain());
|
|
const sentEntry = useRef(false);
|
|
|
|
useEffect(() => {
|
|
const send = () => {
|
|
const sessionSeconds = Math.round((Date.now() - startTime.current) / 1000);
|
|
|
|
// Count active filters from URL (filters are encoded as `f=name:min:max;;name:val`)
|
|
const params = new URLSearchParams(window.location.search);
|
|
const filterStr = params.get('f') || '';
|
|
const filterCount = filterStr ? filterStr.split(';;').length : 0;
|
|
|
|
const payload: Record<string, unknown> = {
|
|
session_seconds: sessionSeconds,
|
|
filter_count: filterCount,
|
|
};
|
|
|
|
// Include entrypoint info on first beacon only
|
|
if (!sentEntry.current) {
|
|
payload.entry_path = entryPath.current;
|
|
payload.referrer = referrer.current;
|
|
sentEntry.current = true;
|
|
}
|
|
|
|
navigator.sendBeacon(
|
|
apiUrl('telemetry'),
|
|
new Blob([JSON.stringify(payload)], { type: 'application/json' })
|
|
);
|
|
};
|
|
|
|
const interval = setInterval(send, 30_000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
}
|
|
|
|
/** Extract the referrer domain, or "direct" if none / same-origin. */
|
|
function extractReferrerDomain(): string {
|
|
if (!document.referrer) return 'direct';
|
|
try {
|
|
const url = new URL(document.referrer);
|
|
// Same-origin navigation isn't a real external referrer
|
|
if (url.origin === window.location.origin) return 'direct';
|
|
return url.hostname;
|
|
} catch {
|
|
return 'direct';
|
|
}
|
|
}
|