export class ThermoPrinter {
constructor(opts = {}) {
this._transport = opts.transport || null;
this._baudRate = opts.baudRate || 9600;
this._usbFilters = opts.usbFilters || [];
this._usbEndpoint = opts.usbEndpoint || 1;
this._usbConfig = opts.usbConfig || 1;
this._usbInterface = opts.usbInterface || 0;
this._device = null; this._port = null; this._writer = null;
this._connected = false;
this._activeTransport = null;
}
static get hasWebSerial() {
return typeof navigator !== 'undefined' && 'serial' in navigator;
}
static get hasWebUSB() {
return typeof navigator !== 'undefined' && 'usb' in navigator;
}
static async quickPrint(data, opts) {
const printer = new ThermoPrinter(opts);
await printer.connect();
try {
await printer.print(data);
} finally {
await printer.disconnect();
}
}
get transport() {
return this._activeTransport;
}
get connected() {
return this._connected;
}
async connect() {
if (this._connected) return;
const transport = this._resolveTransport();
if (!transport) {
throw new Error(
'ThermoPrinter: neither WebSerial nor WebUSB is available in this browser. ' +
'Use Chrome/Edge 89+ with a secure context (HTTPS or localhost).'
);
}
this._activeTransport = transport;
if (transport === 'webserial') {
await this._connectSerial();
} else {
await this._connectUSB();
}
this._connected = true;
}
async print(data, { chunkSize = 4096, chunkDelay = 20 } = {}) {
if (!this._connected) {
throw new Error('ThermoPrinter: not connected. Call connect() first.');
}
if (this._activeTransport === 'webserial') {
await this._writeSerial(data, chunkSize, chunkDelay);
} else {
await this._writeUSB(data, chunkSize, chunkDelay);
}
}
async disconnect() {
if (!this._connected) return;
try {
if (this._activeTransport === 'webserial') {
await this._disconnectSerial();
} else {
await this._disconnectUSB();
}
} finally {
this._connected = false;
this._activeTransport = null;
}
}
async _connectSerial() {
this._port = await navigator.serial.requestPort();
await this._port.open({ baudRate: this._baudRate });
this._writer = this._port.writable.getWriter();
}
async _writeSerial(data, chunkSize, chunkDelay) {
for (let offset = 0; offset < data.length; offset += chunkSize) {
const chunk = data.subarray(offset, offset + chunkSize);
await this._writer.write(chunk);
if (offset + chunkSize < data.length && chunkDelay > 0) {
await this._sleep(chunkDelay);
}
}
}
async _disconnectSerial() {
if (this._writer) {
await this._writer.releaseLock();
this._writer = null;
}
if (this._port) {
await this._port.close();
this._port = null;
}
}
async _connectUSB() {
this._device = await navigator.usb.requestDevice({
filters: this._usbFilters,
});
await this._device.open();
await this._device.selectConfiguration(this._usbConfig);
await this._device.claimInterface(this._usbInterface);
}
async _writeUSB(data, chunkSize, chunkDelay) {
for (let offset = 0; offset < data.length; offset += chunkSize) {
const chunk = data.subarray(offset, offset + chunkSize);
await this._device.transferOut(this._usbEndpoint, chunk);
if (offset + chunkSize < data.length && chunkDelay > 0) {
await this._sleep(chunkDelay);
}
}
}
async _disconnectUSB() {
if (this._device) {
await this._device.releaseInterface(this._usbInterface);
await this._device.close();
this._device = null;
}
}
_resolveTransport() {
if (this._transport) return this._transport;
if (ThermoPrinter.hasWebSerial) return 'webserial';
if (ThermoPrinter.hasWebUSB) return 'webusb';
return null;
}
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export default ThermoPrinter;