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
JavaScript
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;
}
};
}