7 changed files with 229 additions and 45 deletions
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
import React, { Component, ReactNode } from 'react' |
||||
import { AlertTriangle } from 'lucide-react' |
||||
|
||||
interface MediaErrorBoundaryProps { |
||||
children: ReactNode |
||||
fallback?: ReactNode |
||||
onError?: (error: Error) => void |
||||
} |
||||
|
||||
interface MediaErrorBoundaryState { |
||||
hasError: boolean |
||||
error?: Error |
||||
} |
||||
|
||||
export class MediaErrorBoundary extends Component<MediaErrorBoundaryProps, MediaErrorBoundaryState> { |
||||
constructor(props: MediaErrorBoundaryProps) { |
||||
super(props) |
||||
this.state = { hasError: false } |
||||
} |
||||
|
||||
static getDerivedStateFromError(error: Error): MediaErrorBoundaryState { |
||||
return { hasError: true, error } |
||||
} |
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { |
||||
// Don't log expected media errors
|
||||
if (error.name === 'AbortError' ||
|
||||
error.message.includes('play() request was interrupted') || |
||||
error.message.includes('The play() request was interrupted')) { |
||||
return |
||||
} |
||||
|
||||
// Log unexpected errors
|
||||
console.warn('Media error boundary caught error:', error, errorInfo) |
||||
this.props.onError?.(error) |
||||
} |
||||
|
||||
render() { |
||||
if (this.state.hasError) { |
||||
if (this.props.fallback) { |
||||
return this.props.fallback |
||||
} |
||||
|
||||
return ( |
||||
<div className="flex items-center justify-center p-4 bg-muted/50 rounded-lg border border-dashed"> |
||||
<div className="flex items-center gap-2 text-muted-foreground"> |
||||
<AlertTriangle className="w-4 h-4" /> |
||||
<span className="text-sm">Media unavailable</span> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return this.props.children |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/** |
||||
* Suppress expected console errors that are not actionable |
||||
* This helps reduce noise in the development console |
||||
*/ |
||||
|
||||
// Track suppressed errors to avoid spam
|
||||
const suppressedErrors = new Set<string>() |
||||
|
||||
export function suppressExpectedErrors() { |
||||
// Override console.error to filter out expected errors
|
||||
const originalConsoleError = console.error |
||||
|
||||
console.error = (...args: any[]) => { |
||||
const message = args.join(' ') |
||||
|
||||
// Suppress favicon 404 errors
|
||||
if (message.includes('favicon.ico') && message.includes('404')) { |
||||
return |
||||
} |
||||
|
||||
// Suppress CORS errors for external websites
|
||||
if (message.includes('CORS policy') && message.includes('Access-Control-Allow-Origin')) { |
||||
return |
||||
} |
||||
|
||||
// Suppress network errors for external websites
|
||||
if (message.includes('net::ERR_FAILED') && message.includes('200 (OK)')) { |
||||
return |
||||
} |
||||
|
||||
// Suppress YouTube API warnings
|
||||
if (message.includes('Unrecognized feature: \'web-share\'')) { |
||||
return |
||||
} |
||||
|
||||
// Suppress Canvas2D warnings
|
||||
if (message.includes('Canvas2D: Multiple readback operations')) { |
||||
return |
||||
} |
||||
|
||||
// Call original console.error for unexpected errors
|
||||
originalConsoleError.apply(console, args) |
||||
} |
||||
|
||||
// Override console.warn to filter out expected warnings
|
||||
const originalConsoleWarn = console.warn |
||||
|
||||
console.warn = (...args: any[]) => { |
||||
const message = args.join(' ') |
||||
|
||||
// Suppress React DevTools suggestion (only show once)
|
||||
if (message.includes('Download the React DevTools')) { |
||||
if (suppressedErrors.has('react-devtools')) { |
||||
return |
||||
} |
||||
suppressedErrors.add('react-devtools') |
||||
} |
||||
|
||||
// Call original console.warn for unexpected warnings
|
||||
originalConsoleWarn.apply(console, args) |
||||
} |
||||
} |
||||
|
||||
// Initialize error suppression
|
||||
if (typeof window !== 'undefined') { |
||||
suppressExpectedErrors() |
||||
} |
||||
Loading…
Reference in new issue