From 002f6dc8f3b0eee612a8616596aa435c0c26c98a Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 12 Feb 2026 08:04:20 +0000 Subject: [PATCH] feat: add printer connector with job queue --- src/printer/connector.ts | 134 +++++++++++++++++++++++++++++++++++++++ tests/connector.test.ts | 25 ++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/printer/connector.ts create mode 100644 tests/connector.test.ts diff --git a/src/printer/connector.ts b/src/printer/connector.ts new file mode 100644 index 0000000..d9d2dcd --- /dev/null +++ b/src/printer/connector.ts @@ -0,0 +1,134 @@ +interface PrinterConfig { + ip: string; + port: number; + timeout?: number; +} + +interface PrintJob { + id: string; + data: Uint8Array; + status: 'pending' | 'printing' | 'completed' | 'failed'; + createdAt: Date; + startedAt?: Date; + completedAt?: Date; + error?: string; +} + +export class PrinterConnector { + private config: PrinterConfig; + private queueItems: PrintJob[] = []; + private currentJob: PrintJob | null = null; + private connected = false; + private processing = false; + + constructor(config: PrinterConfig) { + this.config = { + timeout: 5000, + ...config + }; + } + + isConnected(): boolean { + return this.connected; + } + + getQueueLength(): number { + return this.queueItems.length + (this.currentJob ? 1 : 0); + } + + queue(data: Uint8Array): string { + const job: PrintJob = { + id: `job_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + data, + status: 'pending', + createdAt: new Date() + }; + + this.queueItems.push(job); + this.processQueue(); + + return job.id; + } + + getJob(jobId: string): PrintJob | undefined { + if (this.currentJob?.id === jobId) return this.currentJob; + return this.queueItems.find(j => j.id === jobId); + } + + getAllJobs(): PrintJob[] { + return this.currentJob + ? [this.currentJob, ...this.queueItems] + : [...this.queueItems]; + } + + cancelJob(jobId: string): boolean { + const index = this.queueItems.findIndex(j => j.id === jobId); + if (index >= 0 && this.queueItems[index].status === 'pending') { + this.queueItems[index].status = 'cancelled' as any; + this.queueItems.splice(index, 1); + return true; + } + return false; + } + + async processQueue(): Promise { + if (this.processing || this.queueItems.length === 0) return; + + this.processing = true; + + while (this.queueItems.length > 0) { + const job = this.queueItems.shift()!; + this.currentJob = job; + job.status = 'printing'; + job.startedAt = new Date(); + + try { + await this.sendToPrinter(job.data); + job.status = 'completed'; + job.completedAt = new Date(); + } catch (error) { + job.status = 'failed'; + job.error = String(error); + console.error(`Print job ${job.id} failed:`, error); + } + } + + this.currentJob = null; + this.processing = false; + } + + private async sendToPrinter(data: Uint8Array): Promise { + return new Promise((resolve, reject) => { + const socket = new (Bun as any).connect({ + hostname: this.config.ip, + port: this.config.port, + }); + + socket.then((conn: any) => { + this.connected = true; + conn.write(data); + conn.end(); + setTimeout(() => { + this.connected = false; + resolve(); + }, 1000); + }).catch((err: any) => { + this.connected = false; + reject(err); + }); + }); + } + + async getStatus(): Promise<{ online: boolean; paperStatus: string }> { + try { + const socket = await (Bun as any).connect({ + hostname: this.config.ip, + port: this.config.port, + }); + socket.end(); + return { online: true, paperStatus: 'ok' }; + } catch { + return { online: false, paperStatus: 'unknown' }; + } + } +} diff --git a/tests/connector.test.ts b/tests/connector.test.ts new file mode 100644 index 0000000..700013e --- /dev/null +++ b/tests/connector.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'bun:test'; +import { PrinterConnector } from '../src/printer/connector'; + +describe('Printer Connector', () => { + it('should create connector with config', () => { + const connector = new PrinterConnector({ + ip: '192.168.1.100', + port: 9100 + }); + expect(connector).toBeDefined(); + expect(connector.isConnected()).toBe(false); + }); + + it('should queue print jobs', () => { + const connector = new PrinterConnector({ + ip: '192.168.1.100', + port: 9100 + }); + + const data = new Uint8Array([0x1B, 0x40]); + const jobId = connector.queue(data); + expect(jobId).toMatch(/^job_/); + expect(connector.getQueueLength()).toBe(1); + }); +});