import type { ViewEventData, EngagementEventData, } from '../types'; export interface BackendAnalyticsConfig { apiBaseUrl: string; appName: string; enableDebugLogging?: boolean; } /** * Backend analytics client for server-side event tracking * Uses fire-and-forget pattern to avoid blocking request processing */ export class BackendAnalyticsClient { private config: Required; constructor(config: BackendAnalyticsConfig) { this.config = { enableDebugLogging: false, ...config, }; } /** * Track a view event (fire-and-forget) */ trackView(data: Omit): void { this.sendEvent('view', { ...data, app: this.config.appName, }).catch((error) => { if (this.config.enableDebugLogging) { console.error('[Analytics] Failed to track view:', error); } }); } /** * Track an engagement event (fire-and-forget) */ trackEngagement(data: EngagementEventData): void { this.sendEvent('engagement', data).catch((error) => { if (this.config.enableDebugLogging) { console.error('[Analytics] Failed to track engagement:', error); } }); } /** * Track an API call event (specialized view event) */ trackApiCall(data: { endpoint: string; method: string; userId?: string; statusCode: number; duration?: number; ipAddress?: string; }): void { this.trackView({ contentId: `${data.method}:${data.endpoint}`, contentType: 'post', // Using 'post' as generic content type for API calls userId: data.userId, sessionId: this.generateSessionId(), duration: data.duration, ipAddress: data.ipAddress, deviceType: 'desktop', // Backend calls don't have device type }); } /** * Track a business event (specialized engagement event) */ trackBusinessEvent(data: { userId: string; eventType: 'message_sent' | 'message_received' | 'stream_started' | 'stream_ended' | 'payment_processed'; targetId: string; metadata?: Record; }): void { // Map business events to engagement metric types const metricTypeMap: Record = { message_sent: 'comment', // Using comment as proxy for communication message_received: 'comment', stream_started: 'subscribe', // Using subscribe as proxy for stream engagement stream_ended: 'subscribe', payment_processed: 'purchase', }; this.trackEngagement({ userId: data.userId, metricType: metricTypeMap[data.eventType], targetId: data.targetId, targetType: 'content', // Generic target type for business events metadata: { ...data.metadata, businessEventType: data.eventType, }, }); } private async sendEvent(type: 'view' | 'engagement', data: ViewEventData | EngagementEventData): Promise { const endpoint = type === 'view' ? '/analytics/track/view' : '/analytics/track/engagement'; try { const response = await fetch(`${this.config.apiBaseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), signal: AbortSignal.timeout(5000), // 5 second timeout }); if (!response.ok && this.config.enableDebugLogging) { console.warn(`[Analytics] Failed to track ${type}: ${response.status} ${response.statusText}`); } } catch (error) { // Silently fail in production, log in debug mode if (this.config.enableDebugLogging) { console.error(`[Analytics] Error tracking ${type}:`, error); } } } private generateSessionId(): string { return `backend-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; } }