UNPKG

advanced-games-library

Version:

Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes

539 lines (482 loc) 14.5 kB
/** * Advanced error handling and recovery system for the games library */ export enum ErrorSeverity { LOW = 'low', MEDIUM = 'medium', HIGH = 'high', CRITICAL = 'critical' } export enum ErrorCategory { INITIALIZATION = 'initialization', GAME_LOGIC = 'game_logic', STORAGE = 'storage', NETWORK = 'network', PERFORMANCE = 'performance', USER_INPUT = 'user_input', RENDERING = 'rendering', MEMORY = 'memory', UNKNOWN = 'unknown' } export interface GameError { id: string; message: string; category: ErrorCategory; severity: ErrorSeverity; gameId?: string; playerId?: string; timestamp: Date; stack?: string; context?: Record<string, any>; recoverable: boolean; recoveryAttempts: number; resolved: boolean; } export interface ErrorRecoveryStrategy { category: ErrorCategory; severity: ErrorSeverity; maxAttempts: number; backoffMs: number; recoveryFunction: (error: GameError) => Promise<boolean>; } /** * Error Handler class for managing and recovering from errors */ export class GameErrorHandler { private static instance: GameErrorHandler; private errors: Map<string, GameError> = new Map(); private recoveryStrategies: Map<string, ErrorRecoveryStrategy> = new Map(); private errorQueue: GameError[] = []; private isProcessingQueue: boolean = false; private errorListeners: Set<(error: GameError) => void> = new Set(); private constructor() { this.setupDefaultRecoveryStrategies(); this.setupGlobalErrorHandlers(); } static getInstance(): GameErrorHandler { if (!GameErrorHandler.instance) { GameErrorHandler.instance = new GameErrorHandler(); } return GameErrorHandler.instance; } /** * Handle a new error */ async handleError( error: Error | string, category: ErrorCategory = ErrorCategory.UNKNOWN, severity: ErrorSeverity = ErrorSeverity.MEDIUM, context?: { gameId?: string; playerId?: string; additionalInfo?: Record<string, any>; } ): Promise<void> { const gameError: GameError = { id: this.generateErrorId(), message: typeof error === 'string' ? error : error.message, category, severity, gameId: context?.gameId, playerId: context?.playerId, timestamp: new Date(), stack: typeof error === 'object' ? error.stack : undefined, context: context?.additionalInfo, recoverable: this.isRecoverable(category, severity), recoveryAttempts: 0, resolved: false }; // Store error this.errors.set(gameError.id, gameError); // Notify listeners this.errorListeners.forEach(listener => { try { listener(gameError); } catch (listenerError) { console.warn('Error in error listener:', listenerError); } }); // Add to recovery queue if recoverable if (gameError.recoverable) { this.errorQueue.push(gameError); this.processErrorQueue(); } // Log error this.logError(gameError); } /** * Register error listener */ onError(callback: (error: GameError) => void): () => void { this.errorListeners.add(callback); return () => this.errorListeners.delete(callback); } /** * Get all errors */ getErrors(filters?: { category?: ErrorCategory; severity?: ErrorSeverity; gameId?: string; resolved?: boolean; }): GameError[] { let errors = Array.from(this.errors.values()); if (filters) { if (filters.category) { errors = errors.filter(e => e.category === filters.category); } if (filters.severity) { errors = errors.filter(e => e.severity === filters.severity); } if (filters.gameId) { errors = errors.filter(e => e.gameId === filters.gameId); } if (filters.resolved !== undefined) { errors = errors.filter(e => e.resolved === filters.resolved); } } return errors.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } /** * Get error statistics */ getErrorStats(): { total: number; byCategory: Record<ErrorCategory, number>; bySeverity: Record<ErrorSeverity, number>; resolvedCount: number; averageResolutionTime: number; } { const errors = Array.from(this.errors.values()); const stats = { total: errors.length, byCategory: {} as Record<ErrorCategory, number>, bySeverity: {} as Record<ErrorSeverity, number>, resolvedCount: 0, averageResolutionTime: 0 }; // Initialize counters Object.values(ErrorCategory).forEach(category => { stats.byCategory[category] = 0; }); Object.values(ErrorSeverity).forEach(severity => { stats.bySeverity[severity] = 0; }); let totalResolutionTime = 0; let resolvedErrors = 0; errors.forEach(error => { stats.byCategory[error.category]++; stats.bySeverity[error.severity]++; if (error.resolved) { stats.resolvedCount++; resolvedErrors++; // Calculate resolution time (simplified) totalResolutionTime += error.recoveryAttempts * 1000; // Rough estimate } }); stats.averageResolutionTime = resolvedErrors > 0 ? totalResolutionTime / resolvedErrors : 0; return stats; } /** * Clear resolved errors */ clearResolvedErrors(): void { const unresolvedErrors = new Map(); this.errors.forEach((error, id) => { if (!error.resolved) { unresolvedErrors.set(id, error); } }); this.errors = unresolvedErrors; } /** * Process error recovery queue */ private async processErrorQueue(): Promise<void> { if (this.isProcessingQueue || this.errorQueue.length === 0) { return; } this.isProcessingQueue = true; while (this.errorQueue.length > 0) { const error = this.errorQueue.shift()!; await this.attemptRecovery(error); } this.isProcessingQueue = false; } /** * Attempt to recover from an error */ private async attemptRecovery(error: GameError): Promise<void> { const strategyKey = `${error.category}_${error.severity}`; const strategy = this.recoveryStrategies.get(strategyKey); if (!strategy || error.recoveryAttempts >= strategy.maxAttempts) { console.warn(`No recovery strategy or max attempts reached for error: ${error.id}`); return; } error.recoveryAttempts++; try { // Wait for backoff period if (error.recoveryAttempts > 1) { await new Promise(resolve => setTimeout(resolve, strategy.backoffMs * error.recoveryAttempts) ); } const recovered = await strategy.recoveryFunction(error); if (recovered) { error.resolved = true; console.log(`Successfully recovered from error: ${error.id}`); } else if (error.recoveryAttempts < strategy.maxAttempts) { // Re-queue for another attempt this.errorQueue.push(error); } } catch (recoveryError) { console.error(`Recovery attempt failed for error ${error.id}:`, recoveryError); if (error.recoveryAttempts < strategy.maxAttempts) { this.errorQueue.push(error); } } } /** * Setup default recovery strategies */ private setupDefaultRecoveryStrategies(): void { // Storage errors - retry with exponential backoff this.recoveryStrategies.set(`${ErrorCategory.STORAGE}_${ErrorSeverity.MEDIUM}`, { category: ErrorCategory.STORAGE, severity: ErrorSeverity.MEDIUM, maxAttempts: 3, backoffMs: 1000, recoveryFunction: async (error) => { // Try to reinitialize storage try { // This would call storage reinitialization return true; } catch { return false; } } }); // Network errors - retry with backoff this.recoveryStrategies.set(`${ErrorCategory.NETWORK}_${ErrorSeverity.MEDIUM}`, { category: ErrorCategory.NETWORK, severity: ErrorSeverity.MEDIUM, maxAttempts: 5, backoffMs: 2000, recoveryFunction: async (error) => { // Check network connectivity and retry return new Promise(resolve => { // Simulate network check setTimeout(() => resolve(Math.random() > 0.3), 1000); }); } }); // Game logic errors - attempt restart this.recoveryStrategies.set(`${ErrorCategory.GAME_LOGIC}_${ErrorSeverity.HIGH}`, { category: ErrorCategory.GAME_LOGIC, severity: ErrorSeverity.HIGH, maxAttempts: 2, backoffMs: 500, recoveryFunction: async (error) => { // Try to restart the game try { if (error.gameId) { // This would trigger game restart console.log(`Attempting to restart game: ${error.gameId}`); return true; } return false; } catch { return false; } } }); // Memory errors - attempt garbage collection this.recoveryStrategies.set(`${ErrorCategory.MEMORY}_${ErrorSeverity.HIGH}`, { category: ErrorCategory.MEMORY, severity: ErrorSeverity.HIGH, maxAttempts: 2, backoffMs: 100, recoveryFunction: async (error) => { try { // Force garbage collection if available if (global.gc) { global.gc(); } return true; } catch { return false; } } }); } /** * Setup global error handlers */ private setupGlobalErrorHandlers(): void { // React Native global error handler if (typeof ErrorUtils !== 'undefined' && ErrorUtils.setGlobalHandler) { ErrorUtils.setGlobalHandler((error, isFatal) => { this.handleError( error, ErrorCategory.UNKNOWN, isFatal ? ErrorSeverity.CRITICAL : ErrorSeverity.HIGH ); }); } // Handle window errors (for web environments) if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') { window.addEventListener('error', (event: ErrorEvent) => { this.handleError( new Error(event.message), ErrorCategory.UNKNOWN, ErrorSeverity.HIGH, { additionalInfo: { filename: event.filename, lineno: event.lineno, colno: event.colno } } ); }); window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { this.handleError( new Error(`Unhandled promise rejection: ${String(event.reason)}`), ErrorCategory.UNKNOWN, ErrorSeverity.HIGH ); }); } } /** * Check if error is recoverable */ private isRecoverable(category: ErrorCategory, severity: ErrorSeverity): boolean { // Critical errors are generally not recoverable automatically if (severity === ErrorSeverity.CRITICAL) { return false; } // Some categories are more likely to be recoverable const recoverableCategories = [ ErrorCategory.NETWORK, ErrorCategory.STORAGE, ErrorCategory.PERFORMANCE, ErrorCategory.MEMORY ]; return recoverableCategories.includes(category); } /** * Generate unique error ID */ private generateErrorId(): string { return `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Log error to console and external services */ private logError(error: GameError): void { const logLevel = this.getLogLevel(error.severity); const message = `[${error.category.toUpperCase()}] ${error.message}`; switch (logLevel) { case 'error': console.error(message, error); break; case 'warn': console.warn(message, error); break; case 'info': console.info(message, error); break; default: console.log(message, error); } // Send to external error tracking service (placeholder) this.sendToErrorTracking(error); } /** * Get appropriate log level for error severity */ private getLogLevel(severity: ErrorSeverity): string { switch (severity) { case ErrorSeverity.CRITICAL: case ErrorSeverity.HIGH: return 'error'; case ErrorSeverity.MEDIUM: return 'warn'; case ErrorSeverity.LOW: return 'info'; default: return 'log'; } } /** * Send error to external tracking service */ private sendToErrorTracking(error: GameError): void { // This would integrate with services like Sentry, Bugsnag, etc. // For now, just a placeholder if (__DEV__) { console.log('Would send to error tracking:', error); } } } /** * Error boundary decorator for methods */ export function errorBoundary( category: ErrorCategory = ErrorCategory.UNKNOWN, severity: ErrorSeverity = ErrorSeverity.MEDIUM ) { return function (target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = async function (...args: any[]) { try { return await method.apply(this, args); } catch (error) { const errorHandler = GameErrorHandler.getInstance(); await errorHandler.handleError( error instanceof Error ? error : String(error), category, severity, { gameId: typeof this === 'object' && this && 'gameId' in this ? (this as any).gameId : undefined, additionalInfo: { method: propertyName, arguments: args } } ); // Re-throw for handling at higher level throw error; } }; return descriptor; }; } /** * Safe async wrapper */ export async function safeAsync<T>( operation: () => Promise<T>, fallback: T, context?: { category?: ErrorCategory; severity?: ErrorSeverity; gameId?: string; } ): Promise<T> { try { return await operation(); } catch (error) { const errorHandler = GameErrorHandler.getInstance(); await errorHandler.handleError( error instanceof Error ? error : String(error), context?.category || ErrorCategory.UNKNOWN, context?.severity || ErrorSeverity.MEDIUM, { gameId: context?.gameId } ); return fallback; } } // Export singleton instance export const gameErrorHandler = GameErrorHandler.getInstance();