206 lines
7.4 KiB
TypeScript
206 lines
7.4 KiB
TypeScript
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<PrinterState>(() => {
|
|
return {
|
|
device: null,
|
|
type: null,
|
|
isConnected: false,
|
|
name: null,
|
|
paperSize: '58',
|
|
};
|
|
});
|
|
|
|
const [characteristic, setCharacteristic] = useState<any>(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;
|
|
}
|