/** * Native Bridge * Interface to native thermal printer modules with new simplified API */ import {NativeModules} from 'react-native' import {TestConnectionResult} from '../connection' import { ImageOptions, NativeImagePrintOptions, NativePrintOptions, NativePrintResult, PrinterOptions, } from '../printing/printer' import {paperToImageWidth, parseNativeError, PrintErrors} from '../printing/shared' import type {INativeModule} from './types' const RNThermalPrinter = NativeModules.RNThermalPrinter as INativeModule if (!RNThermalPrinter) { throw PrintErrors.nativeError('Module not available') } /** * Native printer interface - New simplified API */ export class NativePrinter { ////////////////////////////////////////////////// //////////////////// Discovery /////////////////// ////////////////////////////////////////////////// /** * Scan for Bluetooth devices * @returns Scan result with device list */ static async scanBluetoothDevices(): Promise { console.log('[JS] NativePrinter.scanBluetoothDevices - START') try { const result = await RNThermalPrinter.scanBluetoothDevices() console.log('[JS] NativePrinter.scanBluetoothDevices - SUCCESS:', { pairedCount: result.paired?.length || 0, foundCount: result.found?.length || 0, }) return result } catch (error) { console.error('[JS] NativePrinter.scanBluetoothDevices - ERROR:', error) throw parseNativeError(error) } } /** * Stop scanning for devices * @returns Success status */ static async stopScanDevices(): Promise { console.log('[JS] NativePrinter.stopScanDevices - START') try { const result = await RNThermalPrinter.stopScanDevices() console.log('[JS] NativePrinter.stopScanDevices - SUCCESS:', result) return result } catch (error) { console.error('[JS] NativePrinter.stopScanDevices - ERROR:', error) return false } } ////////////////////////////////////////////////// //////////////////// Connection ////////////////// ////////////////////////////////////////////////// /** * Test printer connection * @param address Printer address (bt:MAC, lan:IP:PORT, ble:UUID) * @returns Test result with device info or error */ static async testConnection(address: string): Promise { console.log('[JS] NativePrinter.testConnection - START') console.log('[JS] NativePrinter.testConnection - PARAMS:', address) try { const result = await RNThermalPrinter.testConnection(address) console.log('[JS] NativePrinter.testConnection - SUCCESS:', result) return result } catch (error) { console.error('[JS] NativePrinter.testConnection - ERROR:', error) // If native throws, convert to result format const errorResult = { success: false, error: parseNativeError(error, address).toJSON(), } console.log('[JS] NativePrinter.testConnection - END: Failed') return errorResult } } /** * Disconnect printer(s) * @param address Printer address or undefined for all */ static async disconnect(address?: string): Promise { console.log('[JS] NativePrinter.disconnect - START') console.log('[JS] NativePrinter.disconnect - PARAMS:', address || 'all') try { await RNThermalPrinter.disconnect(address) console.log('[JS] NativePrinter.disconnect - SUCCESS') } catch (error) { console.error('[JS] NativePrinter.disconnect - ERROR:', error) // Ignore disconnect errors } } ////////////////////////////////////////////////// //////////////////// Printing //////////////////// ////////////////////////////////////////////////// /** * Print raw data with automatic connection management * @param address Printer address * @param data Raw ESC/POS commands as byte array * @param options Print options (keepAlive, timeout) * @returns Print result with success status */ static async printRaw( address: string, data: number[], options: NativePrintOptions = {}, ): Promise { console.log('[JS] NativePrinter.printRaw - START') console.log('[JS] NativePrinter.printRaw - PARAMS:', {address, dataSize: data.length, options}) // Log first few bytes for debugging if (data.length > 0) { const firstBytes = data.slice(0, Math.min(32, data.length)) const hexString = firstBytes.map((b) => b.toString(16).padStart(2, '0').toUpperCase()).join(' ') console.log('[JS] NativePrinter.printRaw - DEBUG: First bytes hex:', hexString) } // Ensure options have default values to avoid undefined const nativeOptions: NativePrintOptions = { keepAlive: options.keepAlive || false, timeout: options.timeout || 10000, } try { console.log('[JS] NativePrinter.printRaw - PROCESS: Calling native module') const result = await RNThermalPrinter.printRaw(address, data, nativeOptions) console.log('[JS] NativePrinter.printRaw - SUCCESS:', result) return result } catch (error) { console.error('[JS] NativePrinter.printRaw - ERROR:', error) // If native throws, convert to result format const errorResult = { success: false, error: parseNativeError(error, address).toJSON(), } console.log('[JS] NativePrinter.printRaw - END: Failed') return errorResult } } /** * Print image from file path * @param address Printer address * @param imagePath Path to image file * @param imageOptions Image print options * @param printerOptions Printer settings for paper width * @returns Print result with success status */ static async printImage( address: string, imagePath: string, imageOptions: ImageOptions = {}, printerOptions: PrinterOptions = {}, ): Promise { console.log('[JS] NativePrinter.printImage - START') console.log('[JS] NativePrinter.printImage - PARAMS: addr=' + address + ', path=' + imagePath) // Validate input if (!imagePath || imagePath.trim() === '') { console.error('[JS] NativePrinter.printImage - ERROR: Empty image path') return { success: false, error: { code: 'INVALID_PATH', message: 'Image path is empty', step: 'unknown', }, } } // Auto-calculate image width from paper width if not specified let widthPx = imageOptions.widthPx if (!widthPx && printerOptions.paperWidthMm) { widthPx = paperToImageWidth(printerOptions.paperWidthMm) } // Convert align string to number for native let alignNum = 0 // 0=left, 1=center, 2=right if (imageOptions.align === 'center') alignNum = 1 else if (imageOptions.align === 'right') alignNum = 2 const options: NativeImagePrintOptions = { widthPx: widthPx || 384, // Default to 384px (58mm) paperWidthMm: printerOptions.paperWidthMm || 58, // Pass paper width for margin calculation keepAlive: imageOptions.keepAlive || false, timeout: imageOptions.timeout || 10000, isCutPaper: imageOptions.isCutPaper === true, // default false marginMm: imageOptions.marginMm !== undefined ? imageOptions.marginMm : 0, alignNum: alignNum, } try { console.log('[JS] NativePrinter.printImage - PROCESS: Calling native with options:', options) const result = await RNThermalPrinter.printImage(address, imagePath, options) console.log('[JS] NativePrinter.printImage - SUCCESS:', result) return result } catch (error) { console.error('[JS] NativePrinter.printImage - ERROR:', error) const errorResult = { success: false, error: parseNativeError(error, address).toJSON(), } console.log('[JS] NativePrinter.printImage - END: Failed') return errorResult } } }