feat(web): add platform-service telemetry client

This commit is contained in:
saravanakumardb1 2026-02-28 02:04:08 -08:00
parent 1fc1d6478a
commit 1713ce058b
2 changed files with 161 additions and 0 deletions

View File

@ -1,8 +1,14 @@
'use client';
import { useEffect } from 'react';
import { AuthProvider } from '@/lib/auth-context';
import { initTelemetry } from '@/lib/telemetry';
import type { ReactNode } from 'react';
export function Providers({ children }: { children: ReactNode }) {
useEffect(() => {
initTelemetry();
}, []);
return <AuthProvider>{children}</AuthProvider>;
}

155
web/src/lib/telemetry.ts Normal file
View File

@ -0,0 +1,155 @@
// ── Platform Telemetry Client ─────────────────────────────────
// Sends lightweight events to platform-service telemetry endpoint.
// Runs in the browser only. Privacy: no PII, only action names + timing.
import { PRODUCT_ID } from './platform-sync';
const PLATFORM = 'web';
const OS_FAMILY = 'other';
const CHANNEL = 'pwa';
const MAX_QUEUE = 50;
const FLUSH_INTERVAL_MS = 30_000;
interface TelemetryEvent {
id: string;
productId: string;
anonymousInstallId: string;
sessionId: string;
platform: string;
channel: string;
osFamily: string;
osVersion: string;
appVersion: string;
buildNumber: string;
releaseChannel: string;
eventType: string;
module: string;
eventName: string;
feature?: string;
message?: string;
tags?: Record<string, string>;
metrics?: Record<string, number>;
occurredAt: string;
}
let queue: TelemetryEvent[] = [];
let sessionId = '';
let installId = '';
let flushTimer: ReturnType<typeof setInterval> | null = null;
let baseUrl = '';
function uuid(): string {
if (typeof crypto?.randomUUID === 'function') return crypto.randomUUID();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
}
function getInstallId(): string {
if (installId) return installId;
try {
const stored = localStorage.getItem('chronomind_telemetry_install_id');
if (stored) {
installId = stored;
return installId;
}
installId = uuid();
localStorage.setItem('chronomind_telemetry_install_id', installId);
} catch {
installId = uuid();
}
return installId;
}
export function trackEvent(
eventType: 'debug' | 'info' | 'warn' | 'error',
module: string,
eventName: string,
options?: {
feature?: string;
message?: string;
tags?: Record<string, string>;
metrics?: Record<string, number>;
}
): void {
if (typeof window === 'undefined') return;
const event: TelemetryEvent = {
id: uuid(),
productId: PRODUCT_ID,
anonymousInstallId: getInstallId(),
sessionId,
platform: PLATFORM,
channel: CHANNEL,
osFamily: OS_FAMILY,
osVersion: navigator.userAgent.substring(0, 128),
appVersion: '0.1.0',
buildNumber: '1',
releaseChannel: 'beta',
eventType,
module,
eventName,
occurredAt: new Date().toISOString(),
...options,
};
queue.push(event);
if (queue.length > MAX_QUEUE) {
queue = queue.slice(-MAX_QUEUE);
}
if (queue.length >= 10) {
flush();
}
}
export function trackTimerEvent(eventName: string, tags?: Record<string, string>, metrics?: Record<string, number>): void {
trackEvent('info', 'timers', eventName, { tags, metrics });
}
export function trackPageView(path: string): void {
trackEvent('info', 'navigation', 'page_view', { tags: { path } });
}
export function flush(): void {
if (typeof window === 'undefined' || queue.length === 0 || !baseUrl) return;
const events = [...queue];
queue = [];
const body = JSON.stringify({ productId: PRODUCT_ID, events });
const url = `${baseUrl}/telemetry/events`;
try {
const sent = navigator.sendBeacon(url, body);
if (!sent) {
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
keepalive: true,
}).catch(() => {});
}
} catch {
// Best effort — telemetry is non-critical
}
}
export function initTelemetry(): void {
if (typeof window === 'undefined') return;
baseUrl = process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL ?? 'https://api.chronomind.app';
sessionId = uuid();
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
flush();
}
});
if (flushTimer) clearInterval(flushTimer);
flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
trackEvent('info', 'app_lifecycle', 'session_started');
}