{"version":3,"file":"ErrorReporter.mjs","sources":["../../../src/lib/monitoring/ErrorReporter.ts"],"sourcesContent":["/**\n * @file Error Reporter\n * @description Sends errors to external monitoring services (Sentry/Datadog/etc.)\n */\n\nimport type { AppError, ErrorContext, ErrorReport } from './errorTypes';\nimport { normalizeError } from './errorTypes';\nimport { isDebugModeEnabled, isDevelopmentEnv } from '../flags/debug-mode';\n\n/**\n * Error reporter configuration\n */\nexport interface ErrorReporterConfig {\n  enabled: boolean;\n  dsn?: string;\n  environment: string;\n  version: string;\n  sampleRate?: number;\n  beforeSend?: (report: ErrorReport) => ErrorReport | null;\n  ignoredErrors?: (string | RegExp)[];\n}\n\n/**\n * Default reporter configuration\n * Note: 'enabled' uses isDevelopmentEnv() inverse for production detection\n * since error reporting should always be on in production\n */\nconst defaultConfig: ErrorReporterConfig = {\n  enabled: !isDevelopmentEnv(),\n  environment:\n    process.env['NODE_ENV'] !== undefined &&\n    process.env['NODE_ENV'] !== null &&\n    process.env['NODE_ENV'] !== ''\n      ? process.env['NODE_ENV']\n      : 'development',\n  version: '1.0.0',\n  sampleRate: 1.0,\n  ignoredErrors: [\n    'ResizeObserver loop limit exceeded',\n    'Network request failed',\n    /^Loading chunk .* failed/,\n  ],\n};\n\n/**\n * Maximum number of errors to queue before dropping\n */\nconst MAX_ERROR_QUEUE_SIZE = 100;\n\n/**\n * Rate limiting configuration\n */\nconst RATE_LIMIT_WINDOW_MS = 60000; // 1 minute\nconst MAX_ERRORS_PER_WINDOW = 50;\n\n/**\n * Error reporter state\n */\nlet config: ErrorReporterConfig = defaultConfig;\nlet currentContext: ErrorContext = {};\nlet errorQueue: ErrorReport[] = [];\nlet isInitialized = false;\n\n/**\n * Rate limiting state\n */\nlet errorTimestamps: number[] = [];\n\n/**\n * Store references to event handlers for proper cleanup\n * This prevents event listener accumulation when initErrorReporter is called multiple times\n */\nlet globalErrorHandler: ((event: ErrorEvent) => void) | null = null;\nlet unhandledRejectionHandler: ((event: PromiseRejectionEvent) => void) | null = null;\n\n/**\n * Initialize the error reporter\n */\nexport function initErrorReporter(options: Partial<ErrorReporterConfig> = {}): void {\n  // Clean up existing handlers first to prevent accumulation\n  cleanupErrorHandlers();\n\n  config = { ...defaultConfig, ...options };\n  isInitialized = true;\n\n  // Set up global error handlers with stored references\n  if (typeof window !== 'undefined') {\n    globalErrorHandler = handleGlobalError;\n    unhandledRejectionHandler = handleUnhandledRejection;\n\n    window.addEventListener('error', globalErrorHandler);\n    window.addEventListener('unhandledrejection', unhandledRejectionHandler);\n  }\n\n  // Flush any queued errors\n  flushErrorQueue();\n}\n\n/**\n * Clean up error handlers\n * Call this when resetting the error reporter or before re-initialization\n */\nexport function cleanupErrorHandlers(): void {\n  if (typeof window !== 'undefined') {\n    if (globalErrorHandler !== null) {\n      window.removeEventListener('error', globalErrorHandler);\n      globalErrorHandler = null;\n    }\n    if (unhandledRejectionHandler !== null) {\n      window.removeEventListener('unhandledrejection', unhandledRejectionHandler);\n      unhandledRejectionHandler = null;\n    }\n  }\n}\n\n/**\n * Reset error reporter state\n * Useful for testing or when unmounting the application\n */\nexport function resetErrorReporter(): void {\n  cleanupErrorHandlers();\n  // Clone defaultConfig to avoid mutating the shared default object\n  config = { ...defaultConfig };\n  currentContext = {};\n  errorQueue = [];\n  errorTimestamps = [];\n  isInitialized = false;\n}\n\n/**\n * Check if we're within rate limits\n */\nfunction isWithinRateLimit(): boolean {\n  const now = Date.now();\n  // Clean up old timestamps\n  errorTimestamps = errorTimestamps.filter((timestamp) => now - timestamp < RATE_LIMIT_WINDOW_MS);\n  return errorTimestamps.length < MAX_ERRORS_PER_WINDOW;\n}\n\n/**\n * Record an error for rate limiting\n */\nfunction recordErrorForRateLimit(): void {\n  errorTimestamps.push(Date.now());\n}\n\n/**\n * Set the user context for error reports\n */\nexport function setUserContext(userId?: string, sessionId?: string): void {\n  currentContext = {\n    ...currentContext,\n    ...(userId !== undefined && { userId }),\n    ...(sessionId !== undefined && { sessionId }),\n  };\n}\n\n/**\n * Set additional context for error reports\n */\nexport function setErrorContext(context: Partial<ErrorContext>): void {\n  currentContext = { ...currentContext, ...context };\n}\n\n/**\n * Clear error context\n */\nexport function clearErrorContext(): void {\n  currentContext = {};\n}\n\n/**\n * Report an error\n */\nexport function reportError(error: unknown, context?: Partial<ErrorContext>): void {\n  const appError = normalizeError(error);\n\n  // Check if error should be ignored\n  if (shouldIgnoreError(appError)) {\n    return;\n  }\n\n  const report = createErrorReport(appError, context);\n\n  // Apply beforeSend hook\n  const finalReport = config.beforeSend ? config.beforeSend(report) : report;\n  if (!finalReport) {\n    return;\n  }\n\n  // Apply sample rate\n  if (config.sampleRate != null && config.sampleRate > 0 && Math.random() > config.sampleRate) {\n    return;\n  }\n\n  // Apply rate limiting\n  if (!isWithinRateLimit()) {\n    if (isDebugModeEnabled()) {\n      console.warn('[Error Reporter] Rate limit exceeded, dropping error');\n    }\n    return;\n  }\n\n  recordErrorForRateLimit();\n\n  if (isInitialized && config.enabled) {\n    void sendErrorReport(finalReport);\n  } else {\n    // Queue error for later with bounds checking\n    if (errorQueue.length < MAX_ERROR_QUEUE_SIZE) {\n      errorQueue.push(finalReport);\n    } else if (isDebugModeEnabled()) {\n      console.warn('[Error Reporter] Error queue full, dropping error');\n    }\n  }\n\n  // Log when debug mode is enabled\n  if (isDebugModeEnabled()) {\n    console.error('[Error Reporter]', appError.message, appError);\n  }\n}\n\n/**\n * Report a warning (lower severity)\n */\nexport function reportWarning(message: string, context?: Partial<ErrorContext>): void {\n  const appError: AppError = {\n    id: `warn_${Date.now()}`,\n    message,\n    category: 'client',\n    severity: 'low',\n    timestamp: new Date().toISOString(),\n  };\n\n  const report = createErrorReport(appError, context);\n\n  if (isDebugModeEnabled()) {\n    console.warn('[Warning]', message, context);\n  }\n\n  if (isInitialized && config.enabled) {\n    void sendErrorReport(report);\n  }\n}\n\n/**\n * Report an info event\n */\nexport function reportInfo(message: string, metadata?: Record<string, unknown>): void {\n  if (isDebugModeEnabled()) {\n    console.info('[Info]', message, metadata);\n  }\n\n  // Send as breadcrumb in production\n  addBreadcrumb('info', message, metadata);\n}\n\n/**\n * Add a breadcrumb for error context\n */\nexport function addBreadcrumb(type: string, message: string, data?: Record<string, unknown>): void {\n  // In production, this would add to Sentry/Datadog breadcrumbs\n  // Log to console when debug mode is enabled\n  if (isDebugModeEnabled()) {\n    // eslint-disable-next-line no-console\n    console.debug('[Breadcrumb]', type, message, data);\n  }\n}\n\n/**\n * Create error report from app error\n */\nfunction createErrorReport(appError: AppError, context?: Partial<ErrorContext>): ErrorReport {\n  const route = typeof window !== 'undefined' ? window.location.pathname : undefined;\n\n  return {\n    ...appError,\n    context: {\n      ...currentContext,\n      ...context,\n      ...(route !== undefined && { route }),\n    },\n    userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',\n    url: typeof window !== 'undefined' ? window.location.href : 'unknown',\n    environment: config.environment,\n    version: config.version,\n  };\n}\n\n/**\n * Send error report to external service\n *\n * Note: This function intentionally uses raw fetch() rather than apiClient because:\n * 1. Error reporting must be independent to avoid circular dependencies\n * 2. Reporting endpoints are typically third-party services (Sentry, Datadog)\n * 3. Must work even when apiClient itself encounters errors\n *\n * @see {@link @/lib/api/api-client} for application API calls\n */\nasync function sendErrorReport(report: ErrorReport): Promise<void> {\n  try {\n    // In a real implementation, this would send to Sentry/Datadog\n    // For now, we'll use a generic endpoint\n    if (config.dsn != null && config.dsn !== '') {\n      // Raw fetch is intentional - error reporting must be independent\n      await fetch(config.dsn, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(report),\n      });\n    }\n  } catch (error) {\n    // Fail silently to avoid infinite loops\n    console.error('[ErrorReporter] Failed to send error report', error);\n  }\n}\n\n/**\n * Handle global error events\n */\nfunction handleGlobalError(event: ErrorEvent): void {\n  const errorValue: unknown = event.error ?? event.message;\n  reportError(errorValue, {\n    action: 'global_error',\n    metadata: {\n      filename: event.filename,\n      lineno: event.lineno,\n      colno: event.colno,\n    },\n  });\n}\n\n/**\n * Handle unhandled promise rejections\n */\nfunction handleUnhandledRejection(event: PromiseRejectionEvent): void {\n  reportError(event.reason, {\n    action: 'unhandled_rejection',\n  });\n}\n\n/**\n * Check if error should be ignored\n */\nfunction shouldIgnoreError(error: AppError): boolean {\n  if (!config.ignoredErrors) return false;\n\n  return config.ignoredErrors.some((pattern) => {\n    if (typeof pattern === 'string') {\n      return error.message.includes(pattern);\n    }\n    return pattern.test(error.message);\n  });\n}\n\n/**\n * Flush queued errors\n */\nfunction flushErrorQueue(): void {\n  if (!config.enabled) return;\n\n  errorQueue.forEach((report) => {\n    void sendErrorReport(report);\n  });\n  errorQueue = [];\n}\n\n/**\n * Export error reporter as singleton object\n */\nexport const ErrorReporter = {\n  init: initErrorReporter,\n  cleanup: cleanupErrorHandlers,\n  reset: resetErrorReporter,\n  setUserContext,\n  setErrorContext,\n  clearErrorContext,\n  reportError,\n  reportWarning,\n  reportInfo,\n  addBreadcrumb,\n};\n"],"names":["defaultConfig","isDevelopmentEnv","MAX_ERROR_QUEUE_SIZE","RATE_LIMIT_WINDOW_MS","MAX_ERRORS_PER_WINDOW","config","currentContext","errorQueue","isInitialized","errorTimestamps","globalErrorHandler","unhandledRejectionHandler","initErrorReporter","options","cleanupErrorHandlers","handleGlobalError","handleUnhandledRejection","flushErrorQueue","resetErrorReporter","isWithinRateLimit","now","timestamp","recordErrorForRateLimit","setUserContext","userId","sessionId","setErrorContext","context","clearErrorContext","reportError","error","appError","normalizeError","shouldIgnoreError","report","createErrorReport","finalReport","isDebugModeEnabled","sendErrorReport","reportWarning","message","reportInfo","metadata","addBreadcrumb","type","data","route","event","errorValue","pattern","ErrorReporter"],"mappings":"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"}