/** * Error Handling Module * * Centralized error handling for the DailyNotification plugin * Maps native exceptions to canonical errors with user-friendly messages * * @author Matthew Raymer * @version 1.0.0 */ import { ErrorCode, type ErrorInfo } from './bridge' export class ErrorHandler { /** * Map native error to canonical error */ mapNativeError(error: unknown): ErrorInfo { const errorMessage = (error as { message?: string })?.message || (error as { toString?: () => string })?.toString?.() || 'Unknown error' // Map common error patterns if (errorMessage.includes('Permission denied')) { return { code: ErrorCode.PERMISSION_DENIED, message: 'Required permission not granted', hint: 'Request permission in settings' } } if (errorMessage.includes('Channel disabled')) { return { code: ErrorCode.CHANNEL_DISABLED, message: 'Notification channel is disabled', hint: 'Enable notifications in settings' } } if (errorMessage.includes('Exact alarm')) { return { code: ErrorCode.EXACT_ALARM_DENIED, message: 'Exact alarm permission denied', hint: 'Grant exact alarm permission in settings' } } if (errorMessage.includes('Doze')) { return { code: ErrorCode.DOZE_LIMIT, message: 'Device in Doze mode', hint: 'Expect delays; fallback taken' } } if (errorMessage.includes('Invalid time')) { return { code: ErrorCode.INVALID_TIME, message: 'Invalid time format', hint: 'Use 24-hour HH:mm format' } } if (errorMessage.includes('Title too long')) { return { code: ErrorCode.TITLE_TOO_LONG, message: 'Title exceeds 100 characters', hint: 'Trim title to 100 characters or less' } } if (errorMessage.includes('Body too long')) { return { code: ErrorCode.BODY_TOO_LONG, message: 'Body exceeds 500 characters', hint: 'Trim body to 500 characters or less' } } if (errorMessage.includes('Response too large')) { return { code: ErrorCode.RESPONSE_TOO_LARGE, message: 'Response size exceeds limit', hint: 'Response is too large to process' } } if (errorMessage.includes('Insecure URL')) { return { code: ErrorCode.INSECURE_URL, message: 'Only HTTPS URLs allowed', hint: 'Use secure HTTPS URLs only' } } if (errorMessage.includes('Schedule blocked')) { return { code: ErrorCode.SCHEDULE_BLOCKED, message: 'Cannot schedule now', hint: 'Check prerequisites and try again' } } // Default error return { code: 'E_UNKNOWN', message: errorMessage, hint: 'Check logs for more details' } } /** * Create user-friendly error message */ createUserMessage(error: ErrorInfo): string { let message = error.message if (error.hint) { message += ` (${error.hint})` } return message } /** * Log error with context */ logError(error: unknown, context = 'DailyNotification'): void { console.error(`[${context}] Error:`, error) if ((error as { stack?: string })?.stack) { console.error(`[${context}] Stack:`, (error as { stack: string }).stack) } } /** * Handle plugin method error */ handlePluginError(error: unknown, method: string): ErrorInfo { this.logError(error, `Plugin.${method}`) return this.mapNativeError(error) } } // Singleton instance export const errorHandler = new ErrorHandler() // Utility functions export function mapNativeError(error: unknown): ErrorInfo { return errorHandler.mapNativeError(error) } export function createUserMessage(error: ErrorInfo): string { return errorHandler.createUserMessage(error) } export function logError(error: unknown, context?: string): void { errorHandler.logError(error, context) } export function handlePluginError(error: unknown, method: string): ErrorInfo { return errorHandler.handlePluginError(error, method) }