/** * Unified Error System for Thermal Printer * Standardized error codes and messages across all platforms */ // Error codes với format Exxx để dễ filter trong logs export enum PrintErrorCode { // Connection errors (1xxx) DEVICE_NOT_FOUND = 'E1001', // Device/printer not found CONNECTION_TIMEOUT = 'E1002', // Connection attempt timed out CONNECTION_REFUSED = 'E1003', // Connection refused by printer CONNECTION_FAILED = 'E1004', // Connection failed (other reasons) // Bluetooth errors (2xxx) BLUETOOTH_DISABLED = 'E2001', // Bluetooth is disabled BLUETOOTH_NOT_PAIRED = 'E2002', // Device not paired BLUETOOTH_PERMISSION = 'E2003', // Missing Bluetooth permission BLUETOOTH_NOT_SUPPORTED = 'E2004', // Bluetooth not supported on device // Network errors (3xxx) NETWORK_UNREACHABLE = 'E3001', // Network is unreachable INVALID_IP_ADDRESS = 'E3002', // Invalid IP address format PORT_UNAVAILABLE = 'E3003', // Port is not available INVALID_HOST = 'E3004', // Cannot resolve hostname // Print errors (4xxx) SEND_FAILED = 'E4001', // Failed to send data to printer PRINTER_OFFLINE = 'E4002', // Printer is offline PAPER_OUT = 'E4003', // Printer is out of paper COVER_OPEN = 'E4004', // Printer cover is open PRINT_HEAD_ERROR = 'E4005', // Print head error // Data errors (5xxx) INVALID_ADDRESS = 'E5001', // Invalid address format INVALID_DATA = 'E5002', // Invalid data format ENCODING_ERROR = 'E5003', // Text encoding error IMAGE_DECODE_ERROR = 'E5004', // Failed to decode image // System errors (9xxx) NO_PRINTERS = 'E9001', // No printers specified NO_DOCUMENTS = 'E9002', // No documents to print NATIVE_MODULE_ERROR = 'E9003', // Native module error UNKNOWN_ERROR = 'E9999', // Unknown error } // Error messages mapping - clear English for debugging export const ErrorMessages: Record = { // Connection errors [PrintErrorCode.DEVICE_NOT_FOUND]: 'Printer device not found at specified address', [PrintErrorCode.CONNECTION_TIMEOUT]: 'Connection attempt timed out after waiting', [PrintErrorCode.CONNECTION_REFUSED]: 'Connection refused by printer', [PrintErrorCode.CONNECTION_FAILED]: 'Failed to establish connection with printer', // Bluetooth errors [PrintErrorCode.BLUETOOTH_DISABLED]: 'Bluetooth is disabled on device', [PrintErrorCode.BLUETOOTH_NOT_PAIRED]: 'Bluetooth device is not paired', [PrintErrorCode.BLUETOOTH_PERMISSION]: 'Missing Bluetooth permission', [PrintErrorCode.BLUETOOTH_NOT_SUPPORTED]: 'Bluetooth is not supported on this device', // Network errors [PrintErrorCode.NETWORK_UNREACHABLE]: 'Network is unreachable', [PrintErrorCode.INVALID_IP_ADDRESS]: 'Invalid IP address format', [PrintErrorCode.PORT_UNAVAILABLE]: 'Port is not available or already in use', [PrintErrorCode.INVALID_HOST]: 'Cannot resolve hostname', // Print errors [PrintErrorCode.SEND_FAILED]: 'Failed to send data to printer', [PrintErrorCode.PRINTER_OFFLINE]: 'Printer is offline or not responding', [PrintErrorCode.PAPER_OUT]: 'Printer is out of paper', [PrintErrorCode.COVER_OPEN]: 'Printer cover is open', [PrintErrorCode.PRINT_HEAD_ERROR]: 'Printer head error detected', // Data errors [PrintErrorCode.INVALID_ADDRESS]: 'Invalid printer address format', [PrintErrorCode.INVALID_DATA]: 'Invalid data format for printing', [PrintErrorCode.ENCODING_ERROR]: 'Text encoding error occurred', [PrintErrorCode.IMAGE_DECODE_ERROR]: 'Failed to decode image data', // System errors [PrintErrorCode.NO_PRINTERS]: 'No printers specified in print job', [PrintErrorCode.NO_DOCUMENTS]: 'No documents specified to print', [PrintErrorCode.NATIVE_MODULE_ERROR]: 'Native module operation failed', [PrintErrorCode.UNKNOWN_ERROR]: 'An unknown error occurred', } // Step where error occurred for better debugging export type ErrorStep = 'connect' | 'send' | 'disconnect' | 'test' | 'unknown' // Extended error class with more details export class PrintError extends Error { public readonly code: PrintErrorCode public readonly step?: ErrorStep public readonly retryable: boolean public readonly suggestion?: string public readonly originalError?: Error | unknown constructor( code: PrintErrorCode, message?: string, options?: { step?: ErrorStep retryable?: boolean suggestion?: string originalError?: Error | unknown }, ) { const finalMessage = message || ErrorMessages[code] || 'Unknown error' super(finalMessage) this.name = 'PrintError' this.code = code this.step = options?.step this.retryable = options?.retryable ?? this.isRetryable(code) this.suggestion = options?.suggestion ?? this.getSuggestion(code) this.originalError = options?.originalError if (Error.captureStackTrace) { Error.captureStackTrace(this, PrintError) } } // Determine if error is retryable based on code private isRetryable(code: PrintErrorCode): boolean { const retryableCodes = [ PrintErrorCode.CONNECTION_TIMEOUT, PrintErrorCode.CONNECTION_REFUSED, PrintErrorCode.NETWORK_UNREACHABLE, PrintErrorCode.SEND_FAILED, PrintErrorCode.PRINTER_OFFLINE, ] return retryableCodes.includes(code) } // Get suggestion for user based on error code private getSuggestion(code: PrintErrorCode): string | undefined { const suggestions: Partial> = { [PrintErrorCode.DEVICE_NOT_FOUND]: 'Check if printer is turned on and in range', [PrintErrorCode.CONNECTION_TIMEOUT]: 'Check if printer is ready and try again', [PrintErrorCode.BLUETOOTH_DISABLED]: 'Please enable Bluetooth in device settings', [PrintErrorCode.BLUETOOTH_NOT_PAIRED]: 'Pair the printer in Bluetooth settings first', [PrintErrorCode.NETWORK_UNREACHABLE]: 'Check network connection and try again', [PrintErrorCode.INVALID_IP_ADDRESS]: 'Verify the IP address format (e.g., 192.168.1.100)', [PrintErrorCode.PAPER_OUT]: 'Please add paper to the printer', [PrintErrorCode.COVER_OPEN]: 'Please close the printer cover', [PrintErrorCode.PRINTER_OFFLINE]: 'Turn on the printer and ensure it is ready', } return suggestions[code] } // Convert to plain object for serialization toJSON() { return { code: this.code, message: this.message, step: this.step, retryable: this.retryable, suggestion: this.suggestion, } } } // Factory functions for common errors export const PrintErrors = { // Helper to create error from native result fromNativeResult: (result: {success: boolean; error?: any}) => { if (result.error) { return new PrintError(result.error.code || PrintErrorCode.UNKNOWN_ERROR, result.error.message, { step: result.error.step, retryable: result.error.retryable, suggestion: result.error.suggestion, }) } return new PrintError(PrintErrorCode.UNKNOWN_ERROR, 'Unknown error') }, // Connection errors deviceNotFound: (address: string, step?: ErrorStep) => new PrintError(PrintErrorCode.DEVICE_NOT_FOUND, `Device not found: ${address}`, { step }), connectionTimeout: (address: string, timeoutMs: number, step?: ErrorStep) => new PrintError(PrintErrorCode.CONNECTION_TIMEOUT, `Connection timeout after ${timeoutMs}ms: ${address}`, { step, }), connectionRefused: (address: string, step?: ErrorStep) => new PrintError(PrintErrorCode.CONNECTION_REFUSED, `Connection refused: ${address}`, { step }), connectionFailed: (address: string, reason?: string, step?: ErrorStep) => new PrintError(PrintErrorCode.CONNECTION_FAILED, `Connection failed: ${address}${reason ? ` - ${reason}` : ''}`, { step, }), // Bluetooth errors bluetoothDisabled: () => new PrintError(PrintErrorCode.BLUETOOTH_DISABLED), bluetoothNotPaired: (address: string) => new PrintError(PrintErrorCode.BLUETOOTH_NOT_PAIRED, `Device not paired: ${address}`), bluetoothPermission: () => new PrintError(PrintErrorCode.BLUETOOTH_PERMISSION), // Network errors networkUnreachable: (host: string) => new PrintError(PrintErrorCode.NETWORK_UNREACHABLE, `Network unreachable: ${host}`), invalidIpAddress: (ip: string) => new PrintError(PrintErrorCode.INVALID_IP_ADDRESS, `Invalid IP address: ${ip}`), // Print errors sendFailed: (address: string, reason?: string) => new PrintError(PrintErrorCode.SEND_FAILED, `Failed to send data to ${address}${reason ? `: ${reason}` : ''}`, { step: 'send', }), printerOffline: (address: string) => new PrintError(PrintErrorCode.PRINTER_OFFLINE, `Printer offline: ${address}`, { step: 'send' }), paperOut: (address: string) => new PrintError(PrintErrorCode.PAPER_OUT, `Printer out of paper: ${address}`, { step: 'send' }), // Data errors invalidAddress: (address: string) => new PrintError( PrintErrorCode.INVALID_ADDRESS, `Invalid address format: ${address}. Use bt:MAC, lan:IP:PORT, or ble:UUID`, ), invalidData: (reason: string) => new PrintError(PrintErrorCode.INVALID_DATA, `Invalid data: ${reason}`), invalidConfig: (reason: string) => new PrintError(PrintErrorCode.INVALID_DATA, `Invalid configuration: ${reason}`), // System errors noPrinters: () => new PrintError(PrintErrorCode.NO_PRINTERS), noDocuments: () => new PrintError(PrintErrorCode.NO_DOCUMENTS), nativeError: (operation: string, error?: Error) => new PrintError(PrintErrorCode.NATIVE_MODULE_ERROR, `Native module error in ${operation}`, { originalError: error, }), unknown: (error: unknown) => new PrintError(PrintErrorCode.UNKNOWN_ERROR, undefined, { originalError: error }), } // Parse error from native module export function parseNativeError(nativeError: any, address?: string): PrintError { // If already a PrintError, return as-is if (nativeError instanceof PrintError) return nativeError // Parse error code from native const code = nativeError?.code || nativeError?.errorCode if (code && Object.values(PrintErrorCode).includes(code)) { return new PrintError(code as PrintErrorCode, nativeError.message, { step: nativeError.step, originalError: nativeError, }) } // Parse error message for known patterns const message = nativeError?.message || String(nativeError) // Connection errors if (message.includes('not found') || message.includes('Device not found')) { return PrintErrors.deviceNotFound(address || 'unknown') } if (message.includes('timeout') || message.includes('timed out')) { return PrintErrors.connectionTimeout(address || 'unknown', 10000) } if (message.includes('refused') || message.includes('Connection refused')) { return PrintErrors.connectionRefused(address || 'unknown') } // Bluetooth errors if (message.includes('Bluetooth') && message.includes('disabled')) { return PrintErrors.bluetoothDisabled() } if (message.includes('not paired') || message.includes('Not paired')) { return PrintErrors.bluetoothNotPaired(address || 'unknown') } if (message.includes('permission') || message.includes('Permission')) { return PrintErrors.bluetoothPermission() } // Network errors if (message.includes('Network unreachable') || message.includes('unreachable')) { return PrintErrors.networkUnreachable(address || 'unknown') } if (message.includes('Invalid IP') || message.includes('invalid address')) { return PrintErrors.invalidIpAddress(address || 'unknown') } // Default to unknown error return PrintErrors.unknown(nativeError) } export { parseNativeError as normalizeError }