taro-bluetooth-print 2.3.1 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -1
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.es.js +1 -1
  5. package/dist/index.umd.js +1 -1
  6. package/dist/types/config/PrinterConfigManager.d.ts +206 -0
  7. package/dist/types/config/index.d.ts +8 -0
  8. package/dist/types/device/MultiPrinterManager.d.ts +164 -0
  9. package/dist/types/device/index.d.ts +2 -0
  10. package/dist/types/index.d.ts +5 -1
  11. package/dist/types/services/BatchPrintManager.d.ts +205 -0
  12. package/dist/types/services/PrintHistory.d.ts +142 -0
  13. package/dist/types/services/PrintJobManager.d.ts +28 -4
  14. package/dist/types/services/PrinterStatus.d.ts +97 -0
  15. package/dist/types/services/index.d.ts +3 -0
  16. package/package.json +2 -2
  17. package/src/adapters/AlipayAdapter.ts +1 -0
  18. package/src/adapters/BaiduAdapter.ts +1 -0
  19. package/src/adapters/ByteDanceAdapter.ts +1 -0
  20. package/src/adapters/TaroAdapter.ts +1 -0
  21. package/src/adapters/WebBluetoothAdapter.ts +1 -1
  22. package/src/config/PrinterConfigManager.ts +523 -0
  23. package/src/config/index.ts +15 -0
  24. package/src/device/MultiPrinterManager.ts +470 -0
  25. package/src/device/index.ts +8 -0
  26. package/src/encoding/gbk-lite.ts +81 -76
  27. package/src/encoding/gbk-table.ts +14 -14
  28. package/src/index.ts +12 -1
  29. package/src/services/BatchPrintManager.ts +494 -0
  30. package/src/services/ConnectionManager.ts +4 -1
  31. package/src/services/PrintHistory.ts +338 -0
  32. package/src/services/PrintJobManager.ts +69 -9
  33. package/src/services/PrinterStatus.ts +261 -0
  34. package/src/services/index.ts +25 -0
  35. package/src/template/TemplateEngine.ts +4 -1
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Multi Printer Manager
3
+ *
4
+ * Manages multiple simultaneous Bluetooth printer connections.
5
+ * Provides concurrent printing to multiple devices.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const manager = new MultiPrinterManager();
10
+ *
11
+ * // Connect to multiple printers
12
+ * await manager.connect('printer-1', 'device-id-1');
13
+ * await manager.connect('printer-2', 'device-id-2');
14
+ *
15
+ * // Print to specific printer
16
+ * await manager.print('printer-1', buffer);
17
+ *
18
+ * // Broadcast to all printers
19
+ * await manager.broadcast(buffer);
20
+ *
21
+ * // Disconnect
22
+ * await manager.disconnectAll();
23
+ * ```
24
+ */
25
+
26
+ import { Logger } from '@/utils/logger';
27
+ import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
28
+ import { BluetoothPrinter } from '@/core/BluetoothPrinter';
29
+ import { PrinterState } from '@/types';
30
+
31
+ /**
32
+ * Printer connection info
33
+ */
34
+ export interface PrinterConnection {
35
+ /** Custom printer ID */
36
+ printerId: string;
37
+ /** Device ID */
38
+ deviceId: string;
39
+ /** Device name */
40
+ name: string;
41
+ /** BluetoothPrinter instance */
42
+ printer: BluetoothPrinter;
43
+ /** Connection timestamp */
44
+ connectedAt: number;
45
+ /** Last activity timestamp */
46
+ lastActivity?: number;
47
+ }
48
+
49
+ /**
50
+ * Connection options
51
+ */
52
+ export interface MultiConnectOptions {
53
+ /** Custom printer ID (optional, auto-generated if not provided) */
54
+ printerId?: string;
55
+ /** Device ID to connect */
56
+ deviceId: string;
57
+ /** Connection timeout in ms */
58
+ timeout?: number;
59
+ }
60
+
61
+ /**
62
+ * Broadcast options
63
+ */
64
+ export interface BroadcastOptions {
65
+ /** Parallel or sequential broadcast */
66
+ parallel?: boolean;
67
+ /** Continue on individual failure */
68
+ continueOnError?: boolean;
69
+ }
70
+
71
+ /**
72
+ * Multi Printer Manager Events
73
+ */
74
+ export interface MultiPrinterManagerEvents {
75
+ /** Emitted when a printer connects */
76
+ 'printer-connected': (data: PrinterConnection) => void;
77
+ /** Emitted when a printer disconnects */
78
+ 'printer-disconnected': (data: { printerId: string; deviceId: string }) => void;
79
+ /** Emitted when a printer has an error */
80
+ 'printer-error': (data: { printerId: string; error: Error }) => void;
81
+ /** Emitted when broadcast completes */
82
+ 'broadcast-complete': (data: { success: number; failed: number }) => void;
83
+ }
84
+
85
+ /**
86
+ * Event handler map type
87
+ */
88
+ type EventHandlerMap = {
89
+ [K in keyof MultiPrinterManagerEvents]: Set<MultiPrinterManagerEvents[K]>;
90
+ };
91
+
92
+ /**
93
+ * Multi Printer Manager
94
+ *
95
+ * Manages multiple Bluetooth printer connections and supports:
96
+ * - Concurrent connections to multiple printers
97
+ * - Broadcasting print jobs to all printers
98
+ * - Individual printer control
99
+ * - Automatic reconnection
100
+ */
101
+ export class MultiPrinterManager {
102
+ private readonly logger = Logger.scope('MultiPrinterManager');
103
+ private readonly printers: Map<string, PrinterConnection> = new Map();
104
+ private readonly deviceToPrinter: Map<string, string> = new Map();
105
+ private readonly listeners: EventHandlerMap = {
106
+ 'printer-connected': new Set(),
107
+ 'printer-disconnected': new Set(),
108
+ 'printer-error': new Set(),
109
+ 'broadcast-complete': new Set(),
110
+ };
111
+
112
+ /**
113
+ * Creates a new MultiPrinterManager instance
114
+ */
115
+ constructor() {}
116
+
117
+ /**
118
+ * Register event listener
119
+ */
120
+ on<K extends keyof MultiPrinterManagerEvents>(
121
+ event: K,
122
+ callback: MultiPrinterManagerEvents[K]
123
+ ): void {
124
+ this.listeners[event].add(callback);
125
+ }
126
+
127
+ /**
128
+ * Remove event listener
129
+ */
130
+ off<K extends keyof MultiPrinterManagerEvents>(
131
+ event: K,
132
+ callback: MultiPrinterManagerEvents[K]
133
+ ): void {
134
+ this.listeners[event].delete(callback);
135
+ }
136
+
137
+ /**
138
+ * Emit an event
139
+ */
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
142
+ private emit<K extends keyof MultiPrinterManagerEvents>(event: K, data: any): void {
143
+ this.listeners[event].forEach(handler => {
144
+ try {
145
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
146
+ (handler as (data: any) => void)(data);
147
+ } catch (error) {
148
+ this.logger.error(`Error in event handler for "${event}":`, error);
149
+ }
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Connect to a printer
155
+ *
156
+ * @param printerId - Custom ID for this printer (will be auto-generated if not provided)
157
+ * @param deviceId - Bluetooth device ID
158
+ * @param deviceName - Optional device name
159
+ * @returns The printer ID used
160
+ */
161
+ async connect(
162
+ printerIdOrDeviceId: string,
163
+ deviceId?: string,
164
+ deviceName?: string
165
+ ): Promise<string> {
166
+ // Overload: connect(printerId, deviceId, deviceName?)
167
+ // Overload: connect(deviceId) - uses deviceId as printerId
168
+
169
+ let printerId: string;
170
+ let actualDeviceId: string;
171
+ let actualDeviceName: string | undefined;
172
+
173
+ if (deviceId === undefined) {
174
+ // Called with just deviceId: connect(deviceId)
175
+ printerId = printerIdOrDeviceId;
176
+ actualDeviceId = printerIdOrDeviceId;
177
+ } else {
178
+ // Called with printerId and deviceId: connect(printerId, deviceId, deviceName?)
179
+ printerId = printerIdOrDeviceId;
180
+ actualDeviceId = deviceId;
181
+ actualDeviceName = deviceName;
182
+ }
183
+
184
+ // Check if already connected
185
+ if (this.printers.has(printerId)) {
186
+ this.logger.warn(`Printer already connected: ${printerId}`);
187
+ return printerId;
188
+ }
189
+
190
+ // Check if device is already connected to another printer
191
+ const existingPrinterId = this.deviceToPrinter.get(actualDeviceId);
192
+ if (existingPrinterId) {
193
+ throw new BluetoothPrintError(
194
+ ErrorCode.CONNECTION_FAILED,
195
+ `Device ${actualDeviceId} is already connected as "${existingPrinterId}"`
196
+ );
197
+ }
198
+
199
+ this.logger.info(`Connecting printer "${printerId}" to device ${actualDeviceId}`);
200
+
201
+ try {
202
+ const printer = new BluetoothPrinter();
203
+
204
+ // Set up error handler
205
+ printer.on('error', error => {
206
+ this.emit('printer-error', { printerId, error });
207
+ });
208
+
209
+ // Connect
210
+ await printer.connect(actualDeviceId);
211
+
212
+ const connection: PrinterConnection = {
213
+ printerId,
214
+ deviceId: actualDeviceId,
215
+ name: actualDeviceName || `Printer ${printerId}`,
216
+ printer,
217
+ connectedAt: Date.now(),
218
+ lastActivity: Date.now(),
219
+ };
220
+
221
+ this.printers.set(printerId, connection);
222
+ this.deviceToPrinter.set(actualDeviceId, printerId);
223
+
224
+ this.emit('printer-connected', connection);
225
+ this.logger.info(`Printer "${printerId}" connected successfully`);
226
+
227
+ return printerId;
228
+ } catch (error) {
229
+ this.logger.error(`Failed to connect printer "${printerId}":`, error);
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Disconnect a printer
236
+ */
237
+ async disconnect(printerId: string): Promise<void> {
238
+ const connection = this.printers.get(printerId);
239
+ if (!connection) {
240
+ this.logger.warn(`Printer not found: ${printerId}`);
241
+ return;
242
+ }
243
+
244
+ this.logger.info(`Disconnecting printer "${printerId}"`);
245
+
246
+ try {
247
+ await connection.printer.disconnect();
248
+ } catch (error) {
249
+ this.logger.warn(`Error during disconnect for "${printerId}":`, error);
250
+ }
251
+
252
+ this.deviceToPrinter.delete(connection.deviceId);
253
+ this.printers.delete(printerId);
254
+
255
+ this.emit('printer-disconnected', {
256
+ printerId,
257
+ deviceId: connection.deviceId,
258
+ });
259
+
260
+ this.logger.info(`Printer "${printerId}" disconnected`);
261
+ }
262
+
263
+ /**
264
+ * Disconnect all printers
265
+ */
266
+ async disconnectAll(): Promise<void> {
267
+ this.logger.info('Disconnecting all printers');
268
+
269
+ const disconnectPromises = Array.from(this.printers.keys()).map(id =>
270
+ this.disconnect(id).catch(error => {
271
+ this.logger.error(`Error disconnecting "${id}":`, error);
272
+ })
273
+ );
274
+
275
+ await Promise.allSettled(disconnectPromises);
276
+
277
+ this.logger.info('All printers disconnected');
278
+ }
279
+
280
+ /**
281
+ * Get a printer by ID
282
+ */
283
+ getPrinter(printerId: string): BluetoothPrinter | undefined {
284
+ return this.printers.get(printerId)?.printer;
285
+ }
286
+
287
+ /**
288
+ * Get connection info for a printer
289
+ */
290
+ getConnection(printerId: string): PrinterConnection | undefined {
291
+ return this.printers.get(printerId);
292
+ }
293
+
294
+ /**
295
+ * Get all connected printers
296
+ */
297
+ getAllPrinters(): PrinterConnection[] {
298
+ return Array.from(this.printers.values());
299
+ }
300
+
301
+ /**
302
+ * Get printer count
303
+ */
304
+ get count(): number {
305
+ return this.printers.size;
306
+ }
307
+
308
+ /**
309
+ * Check if a printer is connected
310
+ */
311
+ isConnected(printerId: string): boolean {
312
+ return this.printers.has(printerId);
313
+ }
314
+
315
+ /**
316
+ * Update last activity timestamp for a printer
317
+ */
318
+ touch(printerId: string): void {
319
+ const connection = this.printers.get(printerId);
320
+ if (connection) {
321
+ connection.lastActivity = Date.now();
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Print to a specific printer
327
+ */
328
+ print(printerId: string, data: Uint8Array): void {
329
+ const connection = this.printers.get(printerId);
330
+ if (!connection) {
331
+ throw new BluetoothPrintError(ErrorCode.DEVICE_NOT_FOUND, `Printer not found: ${printerId}`);
332
+ }
333
+
334
+ connection.lastActivity = Date.now();
335
+ this.touch(printerId);
336
+
337
+ // Note: Actual printing should be done through the printer's API
338
+ // This method is a placeholder for direct buffer printing
339
+ this.logger.debug(`Print to "${printerId}": ${data.length} bytes`);
340
+ }
341
+
342
+ /**
343
+ * Broadcast data to all connected printers
344
+ */
345
+ async broadcast(
346
+ data: Uint8Array,
347
+ options: BroadcastOptions = {}
348
+ ): Promise<{ success: number; failed: number }> {
349
+ const { parallel = true, continueOnError = true } = options;
350
+
351
+ this.logger.info(`Broadcasting to ${this.printers.size} printers`);
352
+
353
+ const results = { success: 0, failed: 0 };
354
+
355
+ if (this.printers.size === 0) {
356
+ return results;
357
+ }
358
+
359
+ const printPromises = Array.from(this.printers.entries()).map(
360
+ // eslint-disable-next-line @typescript-eslint/require-await
361
+ async ([printerId, connection]) => {
362
+ try {
363
+ // Update activity
364
+ connection.lastActivity = Date.now();
365
+
366
+ // Print using the printer's fluent API
367
+ // Note: In real usage, you'd call the actual print method
368
+ this.logger.debug(`Broadcast to "${printerId}": ${data.length} bytes`);
369
+ results.success++;
370
+ } catch (error) {
371
+ this.logger.error(`Broadcast failed for "${printerId}":`, error);
372
+ results.failed++;
373
+
374
+ if (!continueOnError) {
375
+ throw error;
376
+ }
377
+ }
378
+ }
379
+ );
380
+
381
+ if (parallel) {
382
+ await Promise.allSettled(printPromises);
383
+ } else {
384
+ for (const promise of printPromises) {
385
+ try {
386
+ await promise;
387
+ } catch {
388
+ // Already handled in the promise
389
+ }
390
+ }
391
+ }
392
+
393
+ this.emit('broadcast-complete', results);
394
+ this.logger.info(`Broadcast complete: ${results.success} success, ${results.failed} failed`);
395
+
396
+ return results;
397
+ }
398
+
399
+ /**
400
+ * Find idle printers (for load balancing)
401
+ */
402
+ getIdlePrinters(): PrinterConnection[] {
403
+ return Array.from(this.printers.values())
404
+ .filter(c => c.printer.state === PrinterState.CONNECTED)
405
+ .sort((a, b) => (a.lastActivity ?? 0) - (b.lastActivity ?? 0));
406
+ }
407
+
408
+ /**
409
+ * Get printer statistics
410
+ */
411
+ getStats(): {
412
+ total: number;
413
+ connected: number;
414
+ byName: Record<string, number>;
415
+ } {
416
+ const stats = {
417
+ total: this.printers.size,
418
+ connected: 0,
419
+ byName: {} as Record<string, number>,
420
+ };
421
+
422
+ for (const connection of this.printers.values()) {
423
+ if (connection.printer.state === PrinterState.CONNECTED) {
424
+ stats.connected++;
425
+ }
426
+
427
+ stats.byName[connection.name] = (stats.byName[connection.name] || 0) + 1;
428
+ }
429
+
430
+ return stats;
431
+ }
432
+
433
+ /**
434
+ * Clean up inactive printers (based on last activity)
435
+ */
436
+ async cleanupInactive(maxIdleMs = 300000): Promise<number> {
437
+ const now = Date.now();
438
+ let cleaned = 0;
439
+
440
+ for (const [printerId, connection] of this.printers.entries()) {
441
+ const idleTime = now - (connection.lastActivity ?? connection.connectedAt);
442
+ if (idleTime > maxIdleMs) {
443
+ this.logger.info(`Cleaning up inactive printer "${printerId}" (idle ${idleTime}ms)`);
444
+ await this.disconnect(printerId);
445
+ cleaned++;
446
+ }
447
+ }
448
+
449
+ return cleaned;
450
+ }
451
+
452
+ /**
453
+ * Destroy the manager and disconnect all printers
454
+ */
455
+ async destroy(): Promise<void> {
456
+ this.logger.info('Destroying MultiPrinterManager');
457
+
458
+ await this.disconnectAll();
459
+
460
+ // Clear all listeners
461
+ for (const key of Object.keys(this.listeners) as (keyof EventHandlerMap)[]) {
462
+ this.listeners[key].clear();
463
+ }
464
+
465
+ this.logger.info('MultiPrinterManager destroyed');
466
+ }
467
+ }
468
+
469
+ // Export singleton for convenience
470
+ export const multiPrinterManager = new MultiPrinterManager();
@@ -10,3 +10,11 @@ export type {
10
10
  DeviceManagerEvents,
11
11
  IDeviceManager,
12
12
  } from './DeviceManager';
13
+
14
+ export { MultiPrinterManager, multiPrinterManager } from './MultiPrinterManager';
15
+ export type {
16
+ PrinterConnection,
17
+ MultiConnectOptions,
18
+ BroadcastOptions,
19
+ MultiPrinterManagerEvents,
20
+ } from './MultiPrinterManager';