/** * Request Context Manager * Provides correlation IDs and metadata for tracking requests across the system */ export interface RequestContext { requestId: string; userId?: string; timestamp: string; userAgent?: string; clientVersion?: string; traceId?: string; // For distributed tracing across multiple requests } export interface RequestMetadata { endpoint: string; method: string; statusCode?: number; duration?: number; error?: { type: string; message: string; }; } class RequestContextManager { private contexts = new Map(); private readonly MAX_CONTEXTS = 1000; // Prevent memory leaks create(userId?: string, traceId?: string): RequestContext { // Cleanup old contexts if limit reached if (this.contexts.size >= this.MAX_CONTEXTS) { const firstKey = this.contexts.keys().next().value; if (firstKey) this.contexts.delete(firstKey); } const context: RequestContext = { requestId: crypto.randomUUID(), userId, timestamp: new Date().toISOString(), userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined, clientVersion: import.meta.env.VITE_APP_VERSION || '1.0.0', traceId: traceId || crypto.randomUUID(), }; this.contexts.set(context.requestId, context); return context; } get(requestId: string): RequestContext | undefined { return this.contexts.get(requestId); } cleanup(requestId: string): void { this.contexts.delete(requestId); } // Get current context from thread-local storage pattern getCurrentContext(): RequestContext | undefined { // This is a simplified version - in production you'd use AsyncLocalStorage equivalent const keys = Array.from(this.contexts.keys()); return keys.length > 0 ? this.contexts.get(keys[keys.length - 1]) : undefined; } } export const requestContext = new RequestContextManager();