package com.rnthermalprinter.printing import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap /** * Unified Print Error System * Maps to TypeScript PrintErrorCode for consistency */ enum class PrintErrorCode(val code: String) { // Connection errors (1xxx) DEVICE_NOT_FOUND("E1001"), CONNECTION_TIMEOUT("E1002"), CONNECTION_REFUSED("E1003"), CONNECTION_FAILED("E1004"), // Bluetooth errors (2xxx) BLUETOOTH_DISABLED("E2001"), BLUETOOTH_NOT_PAIRED("E2002"), BLUETOOTH_PERMISSION("E2003"), BLUETOOTH_NOT_SUPPORTED("E2004"), // Network errors (3xxx) NETWORK_UNREACHABLE("E3001"), INVALID_IP_ADDRESS("E3002"), PORT_UNAVAILABLE("E3003"), INVALID_HOST("E3004"), // Print errors (4xxx) SEND_FAILED("E4001"), PRINTER_OFFLINE("E4002"), PAPER_OUT("E4003"), COVER_OPEN("E4004"), PRINT_HEAD_ERROR("E4005"), // Data errors (5xxx) INVALID_ADDRESS("E5001"), INVALID_DATA("E5002"), ENCODING_ERROR("E5003"), IMAGE_DECODE_ERROR("E5004"), // System errors (9xxx) NO_PRINTERS("E9001"), NO_DOCUMENTS("E9002"), NATIVE_MODULE_ERROR("E9003"), UNKNOWN_ERROR("E9999"); companion object { fun fromCode(code: String): PrintErrorCode? { return values().find { it.code == code } } } } /** * Step in print process where error occurred */ enum class ErrorStep { CONNECT, SEND, DISCONNECT, TEST, UNKNOWN } /** * Print Error with detailed information */ class PrintError( val errorCode: PrintErrorCode, override val message: String, val step: ErrorStep = ErrorStep.UNKNOWN, val retryable: Boolean = false, val suggestion: String? = null ) : Exception(message) { /** * Convert to WritableMap for React Native */ fun toWritableMap(): WritableMap { val map = Arguments.createMap() map.putString("code", errorCode.code) map.putString("message", message) map.putString("step", step.name.lowercase()) map.putBoolean("retryable", retryable) suggestion?.let { map.putString("suggestion", it) } return map } companion object { // Factory methods for common errors // Connection errors fun deviceNotFound(address: String, step: ErrorStep = ErrorStep.CONNECT) = PrintError( PrintErrorCode.DEVICE_NOT_FOUND, "Device not found: $address", step, retryable = false, suggestion = "Check if printer is turned on and in range" ) fun connectionTimeout(address: String, timeoutMs: Int, step: ErrorStep = ErrorStep.CONNECT) = PrintError( PrintErrorCode.CONNECTION_TIMEOUT, "Connection timeout after ${timeoutMs}ms: $address", step, retryable = true, suggestion = "Check if printer is ready and try again" ) fun connectionRefused(address: String, step: ErrorStep = ErrorStep.CONNECT) = PrintError( PrintErrorCode.CONNECTION_REFUSED, "Connection refused: $address", step, retryable = true, suggestion = "Ensure printer is ready to accept connections" ) fun connectionFailed(address: String, reason: String? = null, step: ErrorStep = ErrorStep.CONNECT) = PrintError( PrintErrorCode.CONNECTION_FAILED, "Connection failed: $address${reason?.let { " - $it" } ?: ""}", step, retryable = true ) // Bluetooth errors fun bluetoothDisabled() = PrintError( PrintErrorCode.BLUETOOTH_DISABLED, "Bluetooth is disabled on device", ErrorStep.CONNECT, retryable = false, suggestion = "Please enable Bluetooth in device settings" ) fun bluetoothNotPaired(address: String) = PrintError( PrintErrorCode.BLUETOOTH_NOT_PAIRED, "Device not paired: $address", ErrorStep.CONNECT, retryable = false, suggestion = "Pair the printer in Bluetooth settings first" ) fun bluetoothPermission() = PrintError( PrintErrorCode.BLUETOOTH_PERMISSION, "Missing Bluetooth permission", ErrorStep.CONNECT, retryable = false, suggestion = "Grant Bluetooth permission in app settings" ) // Network errors fun networkUnreachable(host: String) = PrintError( PrintErrorCode.NETWORK_UNREACHABLE, "Network unreachable: $host", ErrorStep.CONNECT, retryable = true, suggestion = "Check network connection and try again" ) fun invalidIpAddress(ip: String) = PrintError( PrintErrorCode.INVALID_IP_ADDRESS, "Invalid IP address: $ip", ErrorStep.CONNECT, retryable = false, suggestion = "Verify the IP address format (e.g., 192.168.1.100)" ) // Print errors fun sendFailed(address: String, reason: String? = null) = PrintError( PrintErrorCode.SEND_FAILED, "Failed to send data to $address${reason?.let { ": $it" } ?: ""}", ErrorStep.SEND, retryable = true ) fun printerOffline(address: String) = PrintError( PrintErrorCode.PRINTER_OFFLINE, "Printer offline: $address", ErrorStep.SEND, retryable = true, suggestion = "Turn on the printer and ensure it is ready" ) // Data errors fun invalidAddress(address: String) = PrintError( PrintErrorCode.INVALID_ADDRESS, "Invalid address format: $address. Use bt:MAC, lan:IP:PORT, or ble:UUID", ErrorStep.CONNECT, retryable = false ) fun invalidData(reason: String) = PrintError( PrintErrorCode.INVALID_DATA, "Invalid data: $reason", ErrorStep.SEND, retryable = false ) // System errors fun unknown(error: Throwable?) = PrintError( PrintErrorCode.UNKNOWN_ERROR, error?.message ?: "Unknown error occurred", ErrorStep.UNKNOWN, retryable = false ) /** * Parse native exception to PrintError */ fun fromException(e: Throwable, address: String? = null, step: ErrorStep = ErrorStep.UNKNOWN): PrintError { val message = e.message ?: e.toString() // Parse based on exception type and message return when { // Bluetooth errors e is SecurityException && message.contains("BLUETOOTH", ignoreCase = true) -> bluetoothPermission() message.contains("Bluetooth is disabled", ignoreCase = true) || message.contains("Bluetooth not enabled", ignoreCase = true) -> bluetoothDisabled() message.contains("not paired", ignoreCase = true) -> bluetoothNotPaired(address ?: "unknown") // Network errors e is java.net.UnknownHostException -> invalidIpAddress(address ?: "unknown") e is java.net.SocketTimeoutException -> connectionTimeout(address ?: "unknown", 10000, step) e is java.net.ConnectException && message.contains("refused", ignoreCase = true) -> connectionRefused(address ?: "unknown", step) message.contains("Network unreachable", ignoreCase = true) -> networkUnreachable(address ?: "unknown") // Connection errors message.contains("Device not found", ignoreCase = true) || message.contains("not found", ignoreCase = true) -> deviceNotFound(address ?: "unknown", step) message.contains("timeout", ignoreCase = true) -> connectionTimeout(address ?: "unknown", 10000, step) message.contains("Connection failed", ignoreCase = true) -> connectionFailed(address ?: "unknown", e.message, step) // Send errors message.contains("Failed to send", ignoreCase = true) -> sendFailed(address ?: "unknown", e.message) // Default else -> unknown(e) } } } }