UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

869 lines (769 loc) 22.7 kB
/** * Real User Monitoring (RUM) Implementation * Comprehensive real user performance and experience monitoring */ import { EventEmitter } from "events"; import { Logger } from "../utils/logger"; interface RUMConfig { enabled: boolean; apiKey: string; endpoint: string; samplingRate: number; // 0-1 (percentage of users to monitor) sessionTimeout: number; // minutes features: { performanceMetrics: boolean; errorTracking: boolean; userJourney: boolean; customMetrics: boolean; heatmaps: boolean; sessionRecording: boolean; }; privacy: { maskSensitiveData: boolean; excludeFields: string[]; ipAnonymization: boolean; }; } interface RUMMetrics { // Core Web Vitals lcp?: number; // Largest Contentful Paint fid?: number; // First Input Delay cls?: number; // Cumulative Layout Shift fcp?: number; // First Contentful Paint ttfb?: number; // Time to First Byte // Navigation Timing domContentLoaded?: number; loadComplete?: number; // Custom Performance Metrics timeToInteractive?: number; speedIndex?: number; totalBlockingTime?: number; // Resource Timing resources?: ResourceTiming[]; // User Experience userAgent: string; viewport: { width: number; height: number }; connectionType?: string; deviceMemory?: number; // Page Information url: string; referrer: string; timestamp: number; } interface ResourceTiming { name: string; type: string; duration: number; size: number; cached: boolean; } interface RUMError { message: string; stack?: string; filename?: string; lineno?: number; colno?: number; timestamp: number; url: string; userAgent: string; userId?: string; sessionId: string; } interface UserJourney { sessionId: string; userId?: string; startTime: number; endTime?: number; pageViews: PageView[]; interactions: UserInteraction[]; conversions: Conversion[]; errors: RUMError[]; } interface PageView { url: string; title: string; timestamp: number; duration?: number; exitType?: "navigation" | "reload" | "close"; } interface UserInteraction { type: "click" | "scroll" | "input" | "hover" | "resize"; element?: string; timestamp: number; coordinates?: { x: number; y: number }; value?: string; } interface Conversion { type: string; value: number; timestamp: number; metadata?: Record<string, any>; } export class RealUserMonitor extends EventEmitter { private logger: Logger; private config: RUMConfig; private sessionId: string; private userId?: string; private currentJourney: UserJourney; private metricsBuffer: RUMMetrics[] = []; private errorsBuffer: RUMError[] = []; private isInitialized: boolean = false; private observers: Map<string, PerformanceObserver> = new Map(); constructor(config: RUMConfig) { super(); this.config = config; this.logger = new Logger("RealUserMonitor"); this.sessionId = this.generateSessionId(); this.currentJourney = this.initializeJourney(); } /** * Initialize RUM monitoring */ async initialize(): Promise<void> { if (!this.config.enabled || this.isInitialized) { return; } try { // Check if we should monitor this user (sampling) if (Math.random() > this.config.samplingRate) { this.logger.debug( "User excluded from RUM monitoring due to sampling rate", ); return; } // Initialize performance monitoring if (this.config.features.performanceMetrics) { await this.initializePerformanceMonitoring(); } // Initialize error tracking if (this.config.features.errorTracking) { this.initializeErrorTracking(); } // Initialize user journey tracking if (this.config.features.userJourney) { this.initializeUserJourneyTracking(); } // Initialize custom metrics if (this.config.features.customMetrics) { this.initializeCustomMetrics(); } // Start data collection this.startDataCollection(); this.isInitialized = true; this.logger.info("RUM monitoring initialized"); } catch (error) { this.logger.error("Failed to initialize RUM monitoring:", error); throw error; } } /** * Initialize performance monitoring */ private async initializePerformanceMonitoring(): Promise<void> { if (typeof window === "undefined" || !window.PerformanceObserver) { this.logger.warn("PerformanceObserver not available"); return; } // Observe Navigation Timing try { const navObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { this.collectNavigationMetrics(entry as PerformanceNavigationTiming); } }); navObserver.observe({ type: "navigation", buffered: true }); this.observers.set("navigation", navObserver); } catch (error) { this.logger.warn("Navigation timing observer failed:", error); } // Observe Resource Timing try { const resourceObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { this.collectResourceMetrics(entry as PerformanceResourceTiming); } }); resourceObserver.observe({ type: "resource", buffered: true }); this.observers.set("resource", resourceObserver); } catch (error) { this.logger.warn("Resource timing observer failed:", error); } // Observe Paint Timing try { const paintObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { this.collectPaintMetrics(entry as PerformancePaintTiming); } }); paintObserver.observe({ type: "paint", buffered: true }); this.observers.set("paint", paintObserver); } catch (error) { this.logger.warn("Paint timing observer failed:", error); } // Observe Largest Contentful Paint try { const lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1] as any; if (lastEntry) { this.collectLCPMetric(lastEntry.startTime); } }); lcpObserver.observe({ type: "largest-contentful-paint", buffered: true }); this.observers.set("lcp", lcpObserver); } catch (error) { this.logger.warn("LCP observer failed:", error); } // Observe First Input Delay try { const fidObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { this.collectFIDMetric( (entry as any).processingStart - entry.startTime, ); } }); fidObserver.observe({ type: "first-input", buffered: true }); this.observers.set("fid", fidObserver); } catch (error) { this.logger.warn("FID observer failed:", error); } // Collect Layout Shift metrics try { const clsObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); for (const entry of entries) { if (!(entry as any).hadRecentInput) { this.collectCLSMetric((entry as any).value); } } }); clsObserver.observe({ type: "layout-shift", buffered: true }); this.observers.set("cls", clsObserver); } catch (error) { this.logger.warn("CLS observer failed:", error); } } /** * Initialize error tracking */ private initializeErrorTracking(): void { if (typeof window === "undefined") return; // Global error handler window.addEventListener("error", (event) => { const error: RUMError = { message: event.message, stack: event.error?.stack, filename: event.filename, lineno: event.lineno, colno: event.colno, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent, sessionId: this.sessionId, userId: this.userId, }; this.errorsBuffer.push(error); this.currentJourney.errors.push(error); this.emit("error", error); }); // Unhandled promise rejection handler window.addEventListener("unhandledrejection", (event) => { const error: RUMError = { message: `Unhandled Promise Rejection: ${event.reason}`, stack: event.reason?.stack, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent, sessionId: this.sessionId, userId: this.userId, }; this.errorsBuffer.push(error); this.currentJourney.errors.push(error); this.emit("error", error); }); } /** * Initialize user journey tracking */ private initializeUserJourneyTracking(): void { if (typeof window === "undefined") return; // Track page views this.trackPageView(); // Track navigation changes (SPA) const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = (...args) => { originalPushState.apply(history, args); this.trackPageView(); }; history.replaceState = (...args) => { originalReplaceState.apply(history, args); this.trackPageView(); }; window.addEventListener("popstate", () => { this.trackPageView(); }); // Track user interactions this.trackUserInteractions(); // Track page visibility changes document.addEventListener("visibilitychange", () => { if (document.hidden) { this.onPageHidden(); } else { this.onPageVisible(); } }); // Track page unload window.addEventListener("beforeunload", () => { this.finalizeSession(); }); } /** * Initialize custom metrics collection */ private initializeCustomMetrics(): void { // Create global API for custom metrics if (typeof window !== "undefined") { (window as any).geminiFlowRUM = { track: ( name: string, value: number, metadata?: Record<string, any>, ) => { this.trackCustomMetric(name, value, metadata); }, trackConversion: ( type: string, value: number, metadata?: Record<string, any>, ) => { this.trackConversion(type, value, metadata); }, setUserId: (userId: string) => { this.setUserId(userId); }, }; } } /** * Start periodic data collection and transmission */ private startDataCollection(): void { // Send data every 30 seconds setInterval(() => { this.sendBufferedData(); }, 30000); // Send data on page visibility change if (typeof document !== "undefined") { document.addEventListener("visibilitychange", () => { if (document.hidden) { this.sendBufferedData(); } }); } } /** * Collect navigation metrics */ private collectNavigationMetrics(entry: PerformanceNavigationTiming): void { const metrics: RUMMetrics = { ttfb: entry.responseStart - entry.requestStart, domContentLoaded: entry.domContentLoadedEventEnd - entry.domContentLoadedEventStart, loadComplete: entry.loadEventEnd - entry.loadEventStart, url: window.location.href, referrer: document.referrer, timestamp: Date.now(), userAgent: navigator.userAgent, viewport: { width: window.innerWidth, height: window.innerHeight, }, connectionType: (navigator as any).connection?.effectiveType, deviceMemory: (navigator as any).deviceMemory, }; this.metricsBuffer.push(metrics); this.emit("metrics", metrics); } /** * Collect resource metrics */ private collectResourceMetrics(entry: PerformanceResourceTiming): void { const resource: ResourceTiming = { name: entry.name, type: this.getResourceType(entry.name), duration: entry.duration, size: entry.transferSize || 0, cached: entry.transferSize === 0 && entry.decodedBodySize > 0, }; // Add to current metrics const currentMetrics = this.metricsBuffer[this.metricsBuffer.length - 1]; if (currentMetrics) { if (!currentMetrics.resources) { currentMetrics.resources = []; } currentMetrics.resources.push(resource); } } /** * Collect paint metrics */ private collectPaintMetrics(entry: PerformancePaintTiming): void { const currentMetrics = this.metricsBuffer[this.metricsBuffer.length - 1]; if (currentMetrics) { if (entry.name === "first-contentful-paint") { currentMetrics.fcp = entry.startTime; } } } /** * Collect LCP metric */ private collectLCPMetric(value: number): void { const currentMetrics = this.metricsBuffer[this.metricsBuffer.length - 1]; if (currentMetrics) { currentMetrics.lcp = value; } } /** * Collect FID metric */ private collectFIDMetric(value: number): void { const currentMetrics = this.metricsBuffer[this.metricsBuffer.length - 1]; if (currentMetrics) { currentMetrics.fid = value; } } /** * Collect CLS metric */ private collectCLSMetric(value: number): void { const currentMetrics = this.metricsBuffer[this.metricsBuffer.length - 1]; if (currentMetrics) { if (!currentMetrics.cls) { currentMetrics.cls = 0; } currentMetrics.cls += value; } } /** * Track page view */ private trackPageView(): void { const pageView: PageView = { url: window.location.href, title: document.title, timestamp: Date.now(), }; // Set duration for previous page view const lastPageView = this.currentJourney.pageViews[this.currentJourney.pageViews.length - 1]; if (lastPageView && !lastPageView.duration) { lastPageView.duration = Date.now() - lastPageView.timestamp; } this.currentJourney.pageViews.push(pageView); this.emit("pageView", pageView); } /** * Track user interactions */ private trackUserInteractions(): void { if (typeof document === "undefined") return; // Track clicks document.addEventListener( "click", (event) => { const interaction: UserInteraction = { type: "click", element: this.getElementSelector(event.target as Element), timestamp: Date.now(), coordinates: { x: event.clientX, y: event.clientY }, }; this.currentJourney.interactions.push(interaction); }, { passive: true }, ); // Track scrolling let scrollTimeout: NodeJS.Timeout; document.addEventListener( "scroll", () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { const interaction: UserInteraction = { type: "scroll", timestamp: Date.now(), coordinates: { x: window.scrollX, y: window.scrollY }, }; this.currentJourney.interactions.push(interaction); }, 100); }, { passive: true }, ); // Track input changes document.addEventListener( "input", (event) => { const target = event.target as HTMLInputElement; // Mask sensitive data if configured let value = target.value; if (this.config.privacy.maskSensitiveData) { if ( target.type === "password" || target.type === "email" || this.config.privacy.excludeFields.includes(target.name) ) { value = "[MASKED]"; } } const interaction: UserInteraction = { type: "input", element: this.getElementSelector(target), timestamp: Date.now(), value, }; this.currentJourney.interactions.push(interaction); }, { passive: true }, ); } /** * Track custom metric */ public trackCustomMetric( name: string, value: number, metadata?: Record<string, any>, ): void { const metric = { name, value, metadata, timestamp: Date.now(), sessionId: this.sessionId, userId: this.userId, url: typeof window !== "undefined" ? window.location.href : "", }; this.emit("customMetric", metric); this.sendCustomMetric(metric); } /** * Track conversion */ public trackConversion( type: string, value: number, metadata?: Record<string, any>, ): void { const conversion: Conversion = { type, value, timestamp: Date.now(), metadata, }; this.currentJourney.conversions.push(conversion); this.emit("conversion", conversion); } /** * Set user ID */ public setUserId(userId: string): void { this.userId = userId; this.currentJourney.userId = userId; } /** * Handle page hidden */ private onPageHidden(): void { this.sendBufferedData(); } /** * Handle page visible */ private onPageVisible(): void { // Resume tracking } /** * Finalize session */ private finalizeSession(): void { this.currentJourney.endTime = Date.now(); // Set duration for last page view const lastPageView = this.currentJourney.pageViews[this.currentJourney.pageViews.length - 1]; if (lastPageView && !lastPageView.duration) { lastPageView.duration = Date.now() - lastPageView.timestamp; lastPageView.exitType = "close"; } this.sendBufferedData(); this.sendUserJourney(); } /** * Send buffered data */ private async sendBufferedData(): Promise<void> { if (this.metricsBuffer.length === 0 && this.errorsBuffer.length === 0) { return; } try { const payload = { sessionId: this.sessionId, userId: this.userId, metrics: this.metricsBuffer.splice(0), errors: this.errorsBuffer.splice(0), timestamp: Date.now(), }; await this.sendToEndpoint("metrics", payload); } catch (error) { this.logger.error("Failed to send RUM data:", error); } } /** * Send user journey data */ private async sendUserJourney(): Promise<void> { try { await this.sendToEndpoint("journey", this.currentJourney); } catch (error) { this.logger.error("Failed to send user journey:", error); } } /** * Send custom metric */ private async sendCustomMetric(metric: any): Promise<void> { try { await this.sendToEndpoint("custom", metric); } catch (error) { this.logger.error("Failed to send custom metric:", error); } } /** * Send data to endpoint */ private async sendToEndpoint(endpoint: string, data: any): Promise<void> { const url = `${this.config.endpoint}/${endpoint}`; // Use sendBeacon if available for reliability if (typeof navigator !== "undefined" && navigator.sendBeacon) { const blob = new Blob([JSON.stringify(data)], { type: "application/json", }); navigator.sendBeacon(url, blob); } else { // Fallback to fetch fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.config.apiKey}`, }, body: JSON.stringify(data), keepalive: true, }).catch(() => { // Ignore fetch errors for RUM data }); } } /** * Utility functions */ private generateSessionId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } private initializeJourney(): UserJourney { return { sessionId: this.sessionId, userId: this.userId, startTime: Date.now(), pageViews: [], interactions: [], conversions: [], errors: [], }; } private getResourceType(name: string): string { if (name.match(/\.(js|jsx|ts|tsx)$/)) return "script"; if (name.match(/\.(css|scss|sass)$/)) return "stylesheet"; if (name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return "image"; if (name.match(/\.(woff|woff2|ttf|eot)$/)) return "font"; if (name.match(/\.(mp4|webm|ogg|avi)$/)) return "video"; if (name.match(/\.(mp3|wav|ogg|m4a)$/)) return "audio"; return "other"; } private getElementSelector(element: Element): string { if (!element) return ""; // Use data-testid if available const testId = element.getAttribute("data-testid"); if (testId) return `[data-testid="${testId}"]`; // Use ID if available if (element.id) return `#${element.id}`; // Use class names if (element.className) { const classes = element.className.split(" ").filter((c) => c.trim()); if (classes.length > 0) { return `.${classes.join(".")}`; } } // Use tag name as fallback return element.tagName.toLowerCase(); } /** * Cleanup resources */ public destroy(): void { // Stop all observers for (const [name, observer] of this.observers) { observer.disconnect(); this.logger.debug(`Stopped ${name} observer`); } this.observers.clear(); // Send final data this.finalizeSession(); this.isInitialized = false; this.logger.info("RUM monitoring destroyed"); } /** * Get current session statistics */ public getSessionStats(): any { return { sessionId: this.sessionId, userId: this.userId, startTime: this.currentJourney.startTime, duration: Date.now() - this.currentJourney.startTime, pageViews: this.currentJourney.pageViews.length, interactions: this.currentJourney.interactions.length, conversions: this.currentJourney.conversions.length, errors: this.currentJourney.errors.length, metricsCollected: this.metricsBuffer.length, }; } } // Default configuration export const DEFAULT_RUM_CONFIG: RUMConfig = { enabled: true, apiKey: process.env.RUM_API_KEY || "", endpoint: process.env.RUM_ENDPOINT || "https://rum.gemini-flow.com/api", samplingRate: 0.1, // Monitor 10% of users sessionTimeout: 30, // 30 minutes features: { performanceMetrics: true, errorTracking: true, userJourney: true, customMetrics: true, heatmaps: false, sessionRecording: false, }, privacy: { maskSensitiveData: true, excludeFields: ["password", "email", "ssn", "credit_card"], ipAnonymization: true, }, }; // Export types export type { RUMConfig, RUMMetrics, RUMError, UserJourney };