advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
539 lines (482 loc) • 14.5 kB
text/typescript
/**
* 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();