UNPKG

docusaurus-openai-search

Version:

AI-powered search plugin for Docusaurus - extends Algolia search with intelligent keyword generation and RAG-based answers

185 lines (184 loc) 9.4 kB
import React, { Component } from 'react'; import { createLogger } from '../utils'; /** * P3-001: ErrorBoundary component for comprehensive error handling * Wraps search components with error recovery mechanisms */ export class ErrorBoundary extends Component { constructor(props) { super(props); this.retryTimer = null; this.logger = null; this.handleRetry = () => { const { maxRetries = 3 } = this.props; if (this.state.retryCount < maxRetries) { console.log(`[ErrorBoundary] Retrying... (${this.state.retryCount + 1}/${maxRetries})`); this.setState(prevState => ({ hasError: false, error: null, errorInfo: null, retryCount: prevState.retryCount + 1, showDetails: false })); } else { console.warn(`[ErrorBoundary] Maximum retries (${maxRetries}) reached`); } }; this.handleAutoRetry = () => { const { maxRetries = 3 } = this.props; if (this.state.retryCount < maxRetries) { console.log(`[ErrorBoundary] Auto-retrying in 3 seconds... (${this.state.retryCount + 1}/${maxRetries})`); this.retryTimer = setTimeout(() => { this.handleRetry(); }, 3000); } }; this.toggleDetails = () => { this.setState(prevState => ({ showDetails: !prevState.showDetails })); }; this.state = { hasError: false, error: null, errorInfo: null, retryCount: 0, showDetails: false }; // Initialize logger if enabled if (props.enableLogging) { this.logger = createLogger(true); } } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI return { hasError: true, error }; } componentDidCatch(error, errorInfo) { const { onError, componentName = 'Unknown' } = this.props; // Log error details const errorDetails = { component: componentName, error: error.message, stack: error.stack, errorInfo: errorInfo, timestamp: new Date().toISOString(), userAgent: typeof window !== 'undefined' ? window.navigator.userAgent : 'Unknown', url: typeof window !== 'undefined' ? window.location.href : 'Unknown' }; console.error(`[ErrorBoundary] Error caught in ${componentName}:`, errorDetails); if (this.logger) { this.logger.error('Component Error Boundary triggered', errorDetails); } // Update state with error details this.setState({ error, errorInfo, }); // Call external error handler if provided if (onError) { try { onError(error, errorInfo); } catch (callbackError) { console.error('[ErrorBoundary] Error in onError callback:', callbackError); } } } componentDidUpdate(prevProps, prevState) { // Clear retry timer if component recovers if (prevState.hasError && !this.state.hasError && this.retryTimer) { clearTimeout(this.retryTimer); this.retryTimer = null; } } componentWillUnmount() { // Clean up retry timer if (this.retryTimer) { clearTimeout(this.retryTimer); this.retryTimer = null; } } render() { const { hasError, error, errorInfo, retryCount, showDetails } = this.state; const { children, fallback, maxRetries = 3, componentName = 'Component' } = this.props; if (hasError) { // Custom fallback UI provided if (fallback) { return fallback; } // Default error UI return (React.createElement("div", { className: "error-boundary" }, React.createElement("div", { className: "error-boundary-content" }, React.createElement("div", { className: "error-boundary-header" }, React.createElement("h3", { className: "error-boundary-title" }, React.createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "error-boundary-icon" }, React.createElement("path", { d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.728-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" })), "Something went wrong"), React.createElement("p", { className: "error-boundary-description" }, "The ", componentName, " encountered an error and couldn't continue.")), React.createElement("div", { className: "error-boundary-actions" }, retryCount < maxRetries ? (React.createElement("button", { className: "error-boundary-button error-boundary-button--primary", onClick: this.handleRetry }, React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, React.createElement("path", { d: "M21 2v6h-6" }), React.createElement("path", { d: "M3 12a9 9 0 0 1 15-6.7L21 8" }), React.createElement("path", { d: "M3 22v-6h6" }), React.createElement("path", { d: "M21 12a9 9 0 0 1-15 6.7L3 16" })), "Try Again (", retryCount + 1, "/", maxRetries, ")")) : (React.createElement("div", { className: "error-boundary-max-retries" }, React.createElement("p", null, "Maximum retry attempts reached. Please refresh the page."), React.createElement("button", { className: "error-boundary-button error-boundary-button--secondary", onClick: () => window.location.reload() }, "Refresh Page"))), retryCount < maxRetries && (React.createElement("button", { className: "error-boundary-button error-boundary-button--secondary", onClick: this.handleAutoRetry }, "Auto-retry in 3s"))), error && (React.createElement("div", { className: "error-boundary-details" }, React.createElement("button", { className: "error-boundary-details-toggle", onClick: this.toggleDetails }, showDetails ? 'Hide' : 'Show', " Error Details"), showDetails && (React.createElement("div", { className: "error-boundary-details-content" }, React.createElement("div", { className: "error-boundary-error-info" }, React.createElement("h4", null, "Error Message:"), React.createElement("pre", { className: "error-boundary-error-message" }, error.message), error.stack && (React.createElement(React.Fragment, null, React.createElement("h4", null, "Stack Trace:"), React.createElement("pre", { className: "error-boundary-stack-trace" }, error.stack))), errorInfo && errorInfo.componentStack && (React.createElement(React.Fragment, null, React.createElement("h4", null, "Component Stack:"), React.createElement("pre", { className: "error-boundary-component-stack" }, errorInfo.componentStack)))))))), React.createElement("div", { className: "error-boundary-footer" }, React.createElement("p", { className: "error-boundary-help-text" }, "If this problem persists, please report it to the development team."))))); } return children; } } /** * P3-001: Higher-order component for wrapping components with error boundary */ export function withErrorBoundary(Component, errorBoundaryProps) { const WrappedComponent = (props) => (React.createElement(ErrorBoundary, { ...errorBoundaryProps }, React.createElement(Component, { ...props }))); WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`; return WrappedComponent; } /** * P3-001: Hook for error boundary management */ export function useErrorBoundary() { return { triggerErrorBoundary: (error) => { // This will trigger the error boundary throw error; }, createErrorHandler: (componentName) => (error, errorInfo) => { console.error(`[${componentName}] Error:`, error); if (errorInfo) { console.error(`[${componentName}] Error Info:`, errorInfo); } throw error; } }; }