taro-bluetooth-print 2.3.1 → 2.4.0

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 +24 -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 +519 -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 +16 -1
  29. package/src/services/BatchPrintManager.ts +500 -0
  30. package/src/services/ConnectionManager.ts +4 -1
  31. package/src/services/PrintHistory.ts +336 -0
  32. package/src/services/PrintJobManager.ts +69 -9
  33. package/src/services/PrinterStatus.ts +267 -0
  34. package/src/services/index.ts +6 -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
+
30
+ /**
31
+ * Printer connection info
32
+ */
33
+ export interface PrinterConnection {
34
+ /** Custom printer ID */
35
+ printerId: string;
36
+ /** Device ID */
37
+ deviceId: string;
38
+ /** Device name */
39
+ name: string;
40
+ /** BluetoothPrinter instance */
41
+ printer: BluetoothPrinter;
42
+ /** Connection timestamp */
43
+ connectedAt: number;
44
+ /** Last activity timestamp */
45
+ lastActivity?: number;
46
+ }
47
+
48
+ /**
49
+ * Connection options
50
+ */
51
+ export interface MultiConnectOptions {
52
+ /** Custom printer ID (optional, auto-generated if not provided) */
53
+ printerId?: string;
54
+ /** Device ID to connect */
55
+ deviceId: string;
56
+ /** Connection timeout in ms */
57
+ timeout?: number;
58
+ }
59
+
60
+ /**
61
+ * Broadcast options
62
+ */
63
+ export interface BroadcastOptions {
64
+ /** Parallel or sequential broadcast */
65
+ parallel?: boolean;
66
+ /** Continue on individual failure */
67
+ continueOnError?: boolean;
68
+ }
69
+
70
+ /**
71
+ * Multi Printer Manager Events
72
+ */
73
+ export interface MultiPrinterManagerEvents {
74
+ /** Emitted when a printer connects */
75
+ 'printer-connected': (data: PrinterConnection) => void;
76
+ /** Emitted when a printer disconnects */
77
+ 'printer-disconnected': (data: { printerId: string; deviceId: string }) => void;
78
+ /** Emitted when a printer has an error */
79
+ 'printer-error': (data: { printerId: string; error: Error }) => void;
80
+ /** Emitted when broadcast completes */
81
+ 'broadcast-complete': (data: { success: number; failed: number }) => void;
82
+ }
83
+
84
+ /**
85
+ * Event handler map type
86
+ */
87
+ type EventHandlerMap = {
88
+ [K in keyof MultiPrinterManagerEvents]: Set<MultiPrinterManagerEvents[K]>;
89
+ };
90
+
91
+ /**
92
+ * Multi Printer Manager
93
+ *
94
+ * Manages multiple Bluetooth printer connections and supports:
95
+ * - Concurrent connections to multiple printers
96
+ * - Broadcasting print jobs to all printers
97
+ * - Individual printer control
98
+ * - Automatic reconnection
99
+ */
100
+ export class MultiPrinterManager {
101
+ private readonly logger = Logger.scope('MultiPrinterManager');
102
+ private readonly printers: Map<string, PrinterConnection> = new Map();
103
+ private readonly deviceToPrinter: Map<string, string> = new Map();
104
+ private readonly listeners: EventHandlerMap = {
105
+ 'printer-connected': new Set(),
106
+ 'printer-disconnected': new Set(),
107
+ 'printer-error': new Set(),
108
+ 'broadcast-complete': new Set(),
109
+ };
110
+
111
+ /**
112
+ * Creates a new MultiPrinterManager instance
113
+ */
114
+ constructor() {}
115
+
116
+ /**
117
+ * Register event listener
118
+ */
119
+ on<K extends keyof MultiPrinterManagerEvents>(
120
+ event: K,
121
+ callback: MultiPrinterManagerEvents[K]
122
+ ): void {
123
+ this.listeners[event].add(callback);
124
+ }
125
+
126
+ /**
127
+ * Remove event listener
128
+ */
129
+ off<K extends keyof MultiPrinterManagerEvents>(
130
+ event: K,
131
+ callback: MultiPrinterManagerEvents[K]
132
+ ): void {
133
+ this.listeners[event].delete(callback);
134
+ }
135
+
136
+ /**
137
+ * Emit an event
138
+ */
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ private emit<K extends keyof MultiPrinterManagerEvents>(event: K, data: any): void {
141
+ this.listeners[event].forEach(handler => {
142
+ try {
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ (handler as any)(data);
145
+ } catch (error) {
146
+ this.logger.error(`Error in event handler for "${event}":`, error);
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Connect to a printer
153
+ *
154
+ * @param printerId - Custom ID for this printer (will be auto-generated if not provided)
155
+ * @param deviceId - Bluetooth device ID
156
+ * @param deviceName - Optional device name
157
+ * @returns The printer ID used
158
+ */
159
+ async connect(
160
+ printerIdOrDeviceId: string,
161
+ deviceId?: string,
162
+ deviceName?: string
163
+ ): Promise<string> {
164
+ // Overload: connect(printerId, deviceId, deviceName?)
165
+ // Overload: connect(deviceId) - uses deviceId as printerId
166
+
167
+ let printerId: string;
168
+ let actualDeviceId: string;
169
+ let actualDeviceName: string | undefined;
170
+
171
+ if (deviceId === undefined) {
172
+ // Called with just deviceId: connect(deviceId)
173
+ printerId = printerIdOrDeviceId;
174
+ actualDeviceId = printerIdOrDeviceId;
175
+ } else {
176
+ // Called with printerId and deviceId: connect(printerId, deviceId, deviceName?)
177
+ printerId = printerIdOrDeviceId;
178
+ actualDeviceId = deviceId;
179
+ actualDeviceName = deviceName;
180
+ }
181
+
182
+ // Check if already connected
183
+ if (this.printers.has(printerId)) {
184
+ this.logger.warn(`Printer already connected: ${printerId}`);
185
+ return printerId;
186
+ }
187
+
188
+ // Check if device is already connected to another printer
189
+ const existingPrinterId = this.deviceToPrinter.get(actualDeviceId);
190
+ if (existingPrinterId) {
191
+ throw new BluetoothPrintError(
192
+ ErrorCode.CONNECTION_FAILED,
193
+ `Device ${actualDeviceId} is already connected as "${existingPrinterId}"`
194
+ );
195
+ }
196
+
197
+ this.logger.info(`Connecting printer "${printerId}" to device ${actualDeviceId}`);
198
+
199
+ try {
200
+ const printer = new BluetoothPrinter();
201
+
202
+ // Set up error handler
203
+ printer.on('error', (error) => {
204
+ this.emit('printer-error', { printerId, error });
205
+ });
206
+
207
+ // Connect
208
+ await printer.connect(actualDeviceId);
209
+
210
+ const connection: PrinterConnection = {
211
+ printerId,
212
+ deviceId: actualDeviceId,
213
+ name: actualDeviceName || `Printer ${printerId}`,
214
+ printer,
215
+ connectedAt: Date.now(),
216
+ lastActivity: Date.now(),
217
+ };
218
+
219
+ this.printers.set(printerId, connection);
220
+ this.deviceToPrinter.set(actualDeviceId, printerId);
221
+
222
+ this.emit('printer-connected', connection);
223
+ this.logger.info(`Printer "${printerId}" connected successfully`);
224
+
225
+ return printerId;
226
+ } catch (error) {
227
+ this.logger.error(`Failed to connect printer "${printerId}":`, error);
228
+ throw error;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Disconnect a printer
234
+ */
235
+ async disconnect(printerId: string): Promise<void> {
236
+ const connection = this.printers.get(printerId);
237
+ if (!connection) {
238
+ this.logger.warn(`Printer not found: ${printerId}`);
239
+ return;
240
+ }
241
+
242
+ this.logger.info(`Disconnecting printer "${printerId}"`);
243
+
244
+ try {
245
+ await connection.printer.disconnect();
246
+ } catch (error) {
247
+ this.logger.warn(`Error during disconnect for "${printerId}":`, error);
248
+ }
249
+
250
+ this.deviceToPrinter.delete(connection.deviceId);
251
+ this.printers.delete(printerId);
252
+
253
+ this.emit('printer-disconnected', {
254
+ printerId,
255
+ deviceId: connection.deviceId,
256
+ });
257
+
258
+ this.logger.info(`Printer "${printerId}" disconnected`);
259
+ }
260
+
261
+ /**
262
+ * Disconnect all printers
263
+ */
264
+ async disconnectAll(): Promise<void> {
265
+ this.logger.info('Disconnecting all printers');
266
+
267
+ const disconnectPromises = Array.from(this.printers.keys()).map(id =>
268
+ this.disconnect(id).catch(error => {
269
+ this.logger.error(`Error disconnecting "${id}":`, error);
270
+ })
271
+ );
272
+
273
+ await Promise.allSettled(disconnectPromises);
274
+
275
+ this.logger.info('All printers disconnected');
276
+ }
277
+
278
+ /**
279
+ * Get a printer by ID
280
+ */
281
+ getPrinter(printerId: string): BluetoothPrinter | undefined {
282
+ return this.printers.get(printerId)?.printer;
283
+ }
284
+
285
+ /**
286
+ * Get connection info for a printer
287
+ */
288
+ getConnection(printerId: string): PrinterConnection | undefined {
289
+ return this.printers.get(printerId);
290
+ }
291
+
292
+ /**
293
+ * Get all connected printers
294
+ */
295
+ getAllPrinters(): PrinterConnection[] {
296
+ return Array.from(this.printers.values());
297
+ }
298
+
299
+ /**
300
+ * Get printer count
301
+ */
302
+ get count(): number {
303
+ return this.printers.size;
304
+ }
305
+
306
+ /**
307
+ * Check if a printer is connected
308
+ */
309
+ isConnected(printerId: string): boolean {
310
+ return this.printers.has(printerId);
311
+ }
312
+
313
+ /**
314
+ * Update last activity timestamp for a printer
315
+ */
316
+ touch(printerId: string): void {
317
+ const connection = this.printers.get(printerId);
318
+ if (connection) {
319
+ connection.lastActivity = Date.now();
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Print to a specific printer
325
+ */
326
+ async print(printerId: string, data: Uint8Array): Promise<void> {
327
+ const connection = this.printers.get(printerId);
328
+ if (!connection) {
329
+ throw new BluetoothPrintError(
330
+ ErrorCode.DEVICE_NOT_FOUND,
331
+ `Printer not found: ${printerId}`
332
+ );
333
+ }
334
+
335
+ connection.lastActivity = Date.now();
336
+ this.touch(printerId);
337
+
338
+ // Note: Actual printing should be done through the printer's API
339
+ // This method is a placeholder for direct buffer printing
340
+ this.logger.debug(`Print to "${printerId}": ${data.length} bytes`);
341
+ }
342
+
343
+ /**
344
+ * Broadcast data to all connected printers
345
+ */
346
+ async broadcast(
347
+ data: Uint8Array,
348
+ options: BroadcastOptions = {}
349
+ ): Promise<{ success: number; failed: number }> {
350
+ const { parallel = true, continueOnError = true } = options;
351
+
352
+ this.logger.info(`Broadcasting to ${this.printers.size} printers`);
353
+
354
+ const results = { success: 0, failed: 0 };
355
+
356
+ if (this.printers.size === 0) {
357
+ return results;
358
+ }
359
+
360
+ const printPromises = Array.from(this.printers.entries()).map(
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 === '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 === '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';