ERP-node/frontend/lib/zebraBluetooth.ts

119 lines
4.1 KiB
TypeScript

/**
* Zebra 프린터 Web Bluetooth LE 연동
* Chrome/Edge (Chromium) 에서만 지원. BLE로 ZPL 전송 (512바이트 청크)
* 참고: https://developer.zebra.com/content/printing-webapp-using-webbluetooth
*/
const ZEBRA_BLE_SERVICE_UUID = "38eb4a80-c570-11e3-9507-0002a5d5c51b";
const ZEBRA_BLE_CHAR_UUID = "38eb4a82-c570-11e3-9507-0002a5d5c51b";
const CHUNK_SIZE = 512;
const CHUNK_DELAY_MS = 20;
export function isWebBluetoothSupported(): boolean {
if (typeof window === "undefined") return false;
return !!(navigator.bluetooth && navigator.bluetooth.requestDevice);
}
/** 지원 브라우저 안내 문구 */
export function getUnsupportedMessage(): string {
if (!isWebBluetoothSupported()) {
return "이 브라우저는 Web Bluetooth를 지원하지 않습니다. Chrome 또는 Edge(Chromium)에서 열어주세요. HTTPS 또는 localhost 필요.";
}
return "";
}
export interface ZebraPrintResult {
success: boolean;
message: string;
}
/**
* Zebra 프린터를 BLE로 선택·연결 후 ZPL 데이터 전송
* - 사용자에게 블루투스 기기 선택 창이 뜸 (Zebra 프린터 BLE 선택)
* - ZPL을 512바이트 단위로 나누어 순차 전송
*/
export async function printZPLToZebraBLE(zpl: string): Promise<ZebraPrintResult> {
if (!isWebBluetoothSupported()) {
return {
success: false,
message: "Web Bluetooth를 지원하지 않는 브라우저입니다. Chrome 또는 Edge에서 시도해주세요.",
};
}
let device: BluetoothDevice | null = null;
let server: BluetoothRemoteGATTServer | null = null;
try {
// 1) 서비스 UUID로만 필터 시 Android에서 Zebra가 광고하지 않으면 목록에 안 나옴.
// 2) acceptAllDevices + optionalServices 로 모든 BLE 기기 표시 후, 연결해 Zebra 서비스 사용.
const useAcceptAll =
typeof navigator !== "undefined" &&
/Android/i.test(navigator.userAgent);
if (useAcceptAll) {
device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [ZEBRA_BLE_SERVICE_UUID],
});
} else {
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [ZEBRA_BLE_SERVICE_UUID] }],
optionalServices: [ZEBRA_BLE_SERVICE_UUID],
});
}
if (!device) {
return { success: false, message: "프린터를 선택하지 않았습니다." };
}
server = await device.gatt!.connect();
let service: BluetoothRemoteGATTService;
try {
service = await server.getPrimaryService(ZEBRA_BLE_SERVICE_UUID);
} catch {
return {
success: false,
message:
"선택한 기기는 Zebra 프린터가 아니거나 BLE 인쇄를 지원하지 않습니다. 'ZD421' 등 Zebra 프린터를 선택해 주세요.",
};
}
const characteristic = await service.getCharacteristic(ZEBRA_BLE_CHAR_UUID);
const encoder = new TextEncoder();
const data = encoder.encode(zpl);
const totalChunks = Math.ceil(data.length / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, data.length);
const chunk = data.slice(start, end);
await characteristic.writeValue(chunk);
if (i < totalChunks - 1 && CHUNK_DELAY_MS > 0) {
await new Promise((r) => setTimeout(r, CHUNK_DELAY_MS));
}
}
return { success: true, message: "Zebra 프린터로 전송했습니다." };
} catch (err: unknown) {
const e = err as Error & { name?: string };
if (e.name === "NotFoundError") {
return { success: false, message: "Zebra 프린터(BLE)를 찾을 수 없습니다. 프린터 전원과 블루투스 설정을 확인하세요." };
}
if (e.name === "NotAllowedError") {
return { success: false, message: "블루투스 연결이 거부되었습니다." };
}
return {
success: false,
message: e.message || "Zebra BLE 출력 중 오류가 발생했습니다.",
};
} finally {
if (server && device?.gatt?.connected) {
try {
device.gatt.disconnect();
} catch {
// ignore
}
}
}
}