Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 32x 32x 38x 38x 40x 20x 20x 20x 2x 2x 81x 81x 81x 40x 6x 34x 41x | import { Component, ErrorInfo, ReactNode } from 'react';
import i18next from 'i18next';
import { Alert, Button, Box } from '@mui/material';
/**
* Sanitize error message to prevent XSS attacks
* Removes HTML tags and limits message length
* @param message - Raw error message
* @returns Sanitized error message safe for display
*/
function sanitizeErrorMessage(message: string): string {
// Remove HTML tags
const withoutTags = message.replace(/<[^>]*>/g, '');
// Limit length to prevent DoS via large error messages
return withoutTags.slice(0, 500);
}
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: (props: { error: Error; resetError: () => void }) => ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
/**
* ErrorBoundary Component
* Catches React component errors and displays fallback UI
*
* @example
* <ErrorBoundary>
* <MyComponent />
* </ErrorBoundary>
*/
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// Update state so the next render will show the fallback UI
return {
hasError: true,
error,
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
// Log error details to console
try {
const correlationId = (error as Error & { correlationId?: string }).correlationId;
console.error('ErrorBoundary caught an error:', {
message: error.message,
stack: error.stack,
correlationId,
componentStack: errorInfo.componentStack,
});
// TODO: Integrate with Sentry or other error tracking service
// if (window.Sentry) {
// window.Sentry.captureException(error, {
// contexts: {
// react: {
// componentStack: errorInfo.componentStack,
// },
// },
// tags: {
// correlationId,
// },
// });
// }
} catch (loggingError) {
// Prevent logging errors from crashing the error boundary
console.warn('Failed to log error:', loggingError);
}
}
resetError = (): void => {
this.setState({
hasError: false,
error: null,
});
};
render(): ReactNode {
const { hasError, error } = this.state;
const { children, fallback } = this.props;
if (hasError && error) {
// If custom fallback provided, use it
if (fallback) {
return fallback({ error, resetError: this.resetError });
}
// Default fallback UI
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '400px',
padding: 3,
}}
>
<Alert
severity="error"
role="alert"
aria-live="assertive"
sx={{
maxWidth: '600px',
width: '100%',
marginBottom: 2,
}}
>
<Box>
<strong>{i18next.t('common:errors.somethingWentWrong')}</strong>
{error.message && (
<Box sx={{ marginTop: 1 }}>{sanitizeErrorMessage(error.message)}</Box>
)}
</Box>
</Alert>
<Button
variant="contained"
onClick={this.resetError}
aria-label={i18next.t('common:errors.tryAgain')}
>
{i18next.t('common:errors.tryAgain')}
</Button>
</Box>
);
}
return children;
}
}
export default ErrorBoundary;
|