import { useState, useCallback } from 'react'; import { toast } from 'sonner'; // Type definitions for Web Bluetooth and Web USB // This is to satisfy TypeScript if the global types are missing /* eslint-disable @typescript-eslint/no-explicit-any */ export type PrinterType = 'bluetooth' | 'usb' | null; export type PaperSize = '58' | '80'; interface PrinterState { device: any; // Using any to avoid complex type issues with experimental APIs type: PrinterType; isConnected: boolean; name: string | null; paperSize: PaperSize; } export function usePrinter() { const [state, setState] = useState(() => { return { device: null, type: null, isConnected: false, name: null, paperSize: '58', }; }); const [characteristic, setCharacteristic] = useState(null); const connectBluetooth = useCallback(async () => { try { const nav = navigator as any; if (!nav.bluetooth) { toast.error('Bluetooth tidak didukung di browser ini'); return; } const device = await nav.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: ['000018f0-0000-1000-8000-00805f9b34fb', '49535343-fe7d-41aa-83b1-d10935904914', '0000ff00-0000-1000-8000-00805f9b34fb'] }); const server = await device.gatt?.connect(); const services = await server.getPrimaryServices(); const writeChar = await findWriteCharacteristic(services); if (!writeChar) { throw new Error('Tidak dapat menemukan characteristic untuk menulis data'); } setCharacteristic(writeChar); setState(prev => ({ ...prev, device, type: 'bluetooth', isConnected: true, name: device.name || 'Bluetooth Printer', })); toast.success(`Terhubung ke ${device.name || 'Printer Bluetooth'}`); device.addEventListener('gattserverdisconnected', () => { setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null })); setCharacteristic(null); toast.error('Printer Bluetooth terputus'); }); } catch (error: any) { console.error('Bluetooth connection error:', error); if (error.name !== 'NotFoundError') { toast.error(error.message || 'Gagal menghubungkan printer bluetooth'); } } }, []); const connectUsb = useCallback(async () => { try { const nav = navigator as any; if (!nav.usb) { toast.error('USB tidak didukung di browser ini'); return; } const device = await nav.usb.requestDevice({ filters: [] }); await device.open(); if (device.configuration === null) { await device.selectConfiguration(1); } const iface = device.configuration.interfaces[0]; await device.claimInterface(iface.interfaceNumber); setState(prev => ({ ...prev, device, type: 'usb', isConnected: true, name: device.productName || 'USB Printer', })); toast.success(`Terhubung ke ${device.productName || 'Printer USB'}`); } catch (error: any) { console.error('USB connection error:', error); if (error.name !== 'NotFoundError') { toast.error(error.message || 'Gagal menghubungkan printer USB'); } } }, []); const setPaperSize = useCallback((size: PaperSize) => { setState(prev => ({ ...prev, paperSize: size })); }, []); const sendData = useCallback(async (data: Uint8Array) => { if (!state.isConnected || !state.device) { toast.error('Printer belum terhubung'); return; } try { if (state.type === 'bluetooth') { const device = state.device; // Try to reconnect if GATT is disconnected if (!device.gatt?.connected) { toast.loading('Menghubungkan kembali...', { id: 'printer-reconnect' }); const server = await device.gatt?.connect(); const services = await server.getPrimaryServices(); const writeChar = await findWriteCharacteristic(services); if (writeChar) { setCharacteristic(writeChar); toast.success('Printer terhubung kembali', { id: 'printer-reconnect' }); } else { toast.error('Gagal menghubungkan kembali printer', { id: 'printer-reconnect' }); return; } } if (!characteristic) throw new Error('Characteristic tidak ditemukan'); const chunkSize = 20; for (let i = 0; i < data.length; i += chunkSize) { const chunk = data.slice(i, i + chunkSize); // Use writeValueWithoutResponse for better stability if supported if (characteristic.writeValueWithoutResponse) { await characteristic.writeValueWithoutResponse(chunk); } else { await characteristic.writeValue(chunk); } } } else if (state.type === 'usb') { const device = state.device as any; const endpoint = device.configuration.interfaces[0].alternates[0].endpoints.find((e: any) => e.direction === 'out' && e.type === 'bulk'); if (!endpoint) throw new Error('Tidak dapat menemukan USB endpoint'); await device.transferOut(endpoint.endpointNumber, data); } } catch (error: any) { console.error('Print error:', error); toast.error('Gagal mengirim data ke printer: ' + (error.message || 'Error tidak diketahui')); } }, [state.isConnected, state.device, state.type, characteristic]); const disconnect = useCallback(async () => { if (state.type === 'bluetooth' && state.device) { if (state.device.gatt?.connected) { state.device.gatt.disconnect(); } } else if (state.type === 'usb' && state.device) { await state.device.close(); } setState(prev => ({ ...prev, device: null, type: null, isConnected: false, name: null })); setCharacteristic(null); toast.info('Printer terputus'); }, [state.device, state.type]); return { ...state, connectBluetooth, connectUsb, disconnect, sendData, setPaperSize, }; } async function findWriteCharacteristic(services: any[]) { for (const service of services) { const characteristics = await service.getCharacteristics(); for (const char of characteristics) { if (char.properties.write || char.properties.writeWithoutResponse) { return char; } } } return null; }