taro-bluetooth-print 2.3.0 → 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.
- package/CHANGELOG.md +73 -195
- package/README.md +134 -386
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/config/PrinterConfigManager.d.ts +206 -0
- package/dist/types/config/index.d.ts +8 -0
- package/dist/types/core/BluetoothPrinter.d.ts +1 -1
- package/dist/types/core/EventEmitter.d.ts +6 -26
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/device/MultiPrinterManager.d.ts +164 -0
- package/dist/types/device/index.d.ts +2 -0
- package/dist/types/drivers/CpclDriver.d.ts +304 -0
- package/dist/types/drivers/GPrinterDriver.d.ts +63 -0
- package/dist/types/drivers/ZplDriver.d.ts +325 -0
- package/dist/types/drivers/index.d.ts +9 -0
- package/dist/types/encoding/gbk-lite.d.ts +8 -0
- package/dist/types/encoding/gbk-table.d.ts +8 -30
- package/dist/types/index.d.ts +12 -8
- package/dist/types/services/BatchPrintManager.d.ts +205 -0
- package/dist/types/services/ConnectionManager.d.ts +1 -1
- package/dist/types/services/PrintHistory.d.ts +142 -0
- package/dist/types/services/PrintJobManager.d.ts +28 -4
- package/dist/types/services/PrinterStatus.d.ts +97 -0
- package/dist/types/services/index.d.ts +11 -0
- package/package.json +25 -7
- package/src/adapters/AlipayAdapter.ts +1 -0
- package/src/adapters/BaiduAdapter.ts +1 -0
- package/src/adapters/BaseAdapter.ts +6 -8
- package/src/adapters/ByteDanceAdapter.ts +1 -0
- package/src/adapters/TaroAdapter.ts +1 -0
- package/src/adapters/WebBluetoothAdapter.ts +1 -1
- package/src/config/PrinterConfigManager.ts +519 -0
- package/src/config/index.ts +15 -0
- package/src/core/BluetoothPrinter.ts +15 -15
- package/src/core/EventEmitter.ts +15 -15
- package/src/core/index.ts +7 -0
- package/src/device/MultiPrinterManager.ts +470 -0
- package/src/device/index.ts +8 -0
- package/src/drivers/CpclDriver.ts +549 -0
- package/src/drivers/GPrinterDriver.ts +115 -0
- package/src/drivers/TsplDriver.ts +9 -21
- package/src/drivers/ZplDriver.ts +543 -0
- package/src/drivers/index.ts +37 -0
- package/src/encoding/gbk-lite.ts +113 -0
- package/src/encoding/gbk-table.ts +80 -58
- package/src/index.ts +40 -35
- package/src/plugins/PluginManager.ts +3 -1
- package/src/plugins/builtin/LoggingPlugin.ts +4 -2
- package/src/plugins/builtin/RetryPlugin.ts +8 -14
- package/src/services/BatchPrintManager.ts +500 -0
- package/src/services/ConnectionManager.ts +25 -22
- package/src/services/PrintHistory.ts +336 -0
- package/src/services/PrintJobManager.ts +69 -9
- package/src/services/PrinterStatus.ts +267 -0
- package/src/services/index.ts +22 -0
- package/src/template/TemplateEngine.ts +4 -1
package/src/core/EventEmitter.ts
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* });
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
|
+
import { Logger } from '@/utils/logger';
|
|
28
|
+
|
|
27
29
|
export class EventEmitter<T> {
|
|
28
|
-
// 使用Map存储事件监听器,Set确保每个监听器唯一
|
|
29
30
|
private listeners: Map<keyof T, Set<(data: T[keyof T]) => void>> = new Map();
|
|
30
|
-
|
|
31
|
-
// Debug mode flag
|
|
32
31
|
private debugMode = false;
|
|
32
|
+
protected readonly logger = Logger.scope('EventEmitter');
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Subscribe to an event
|
|
@@ -45,7 +45,7 @@ export class EventEmitter<T> {
|
|
|
45
45
|
this.listeners.get(event)!.add(handler as (data: T[keyof T]) => void);
|
|
46
46
|
|
|
47
47
|
if (this.debugMode) {
|
|
48
|
-
|
|
48
|
+
this.logger.debug(`EventEmitter: Added listener for "${String(event)}"`, {
|
|
49
49
|
listenerCount: this.listenerCount(event),
|
|
50
50
|
});
|
|
51
51
|
}
|
|
@@ -90,7 +90,7 @@ export class EventEmitter<T> {
|
|
|
90
90
|
this.listeners.set(event, newHandlers);
|
|
91
91
|
|
|
92
92
|
if (this.debugMode) {
|
|
93
|
-
|
|
93
|
+
this.logger.debug(`EventEmitter: Prepend listener for "${String(event)}"`, {
|
|
94
94
|
listenerCount: this.listenerCount(event),
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -125,7 +125,7 @@ export class EventEmitter<T> {
|
|
|
125
125
|
handlers.delete(handler as (data: T[keyof T]) => void);
|
|
126
126
|
|
|
127
127
|
if (this.debugMode) {
|
|
128
|
-
|
|
128
|
+
this.logger.debug(`EventEmitter: Removed listener for "${String(event)}"`, {
|
|
129
129
|
listenerCount: handlers.size,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
@@ -135,7 +135,7 @@ export class EventEmitter<T> {
|
|
|
135
135
|
this.listeners.delete(event);
|
|
136
136
|
|
|
137
137
|
if (this.debugMode) {
|
|
138
|
-
|
|
138
|
+
this.logger.debug(`EventEmitter: Removed event "${String(event)}" (no more listeners)`);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -154,7 +154,7 @@ export class EventEmitter<T> {
|
|
|
154
154
|
const data = args[0] as T[K];
|
|
155
155
|
|
|
156
156
|
if (this.debugMode) {
|
|
157
|
-
|
|
157
|
+
this.logger.debug(`EventEmitter: Emitting "${String(event)}"`, {
|
|
158
158
|
data,
|
|
159
159
|
listenerCount: this.listenerCount(event),
|
|
160
160
|
});
|
|
@@ -184,7 +184,7 @@ export class EventEmitter<T> {
|
|
|
184
184
|
}
|
|
185
185
|
} catch (error) {
|
|
186
186
|
// 捕获并处理事件处理程序中的错误,避免影响其他监听器
|
|
187
|
-
|
|
187
|
+
this.logger.error(`Error in event handler for "${String(event)}":`, error);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -204,7 +204,7 @@ export class EventEmitter<T> {
|
|
|
204
204
|
const data = args[0] as T[K];
|
|
205
205
|
|
|
206
206
|
if (this.debugMode) {
|
|
207
|
-
|
|
207
|
+
this.logger.debug(`EventEmitter: Emitting async "${String(event)}"`, {
|
|
208
208
|
data,
|
|
209
209
|
listenerCount: this.listenerCount(event),
|
|
210
210
|
});
|
|
@@ -238,7 +238,7 @@ export class EventEmitter<T> {
|
|
|
238
238
|
}
|
|
239
239
|
} catch (error) {
|
|
240
240
|
// 捕获并处理事件处理程序中的错误,避免影响其他监听器
|
|
241
|
-
|
|
241
|
+
this.logger.error(`Error in event handler for "${String(event)}":`, error);
|
|
242
242
|
}
|
|
243
243
|
})()
|
|
244
244
|
);
|
|
@@ -247,7 +247,7 @@ export class EventEmitter<T> {
|
|
|
247
247
|
await Promise.all(promises);
|
|
248
248
|
|
|
249
249
|
if (this.debugMode) {
|
|
250
|
-
|
|
250
|
+
this.logger.debug(`EventEmitter: Finished emitting async "${String(event)}"`);
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -262,7 +262,7 @@ export class EventEmitter<T> {
|
|
|
262
262
|
this.listeners.delete(event);
|
|
263
263
|
|
|
264
264
|
if (this.debugMode) {
|
|
265
|
-
|
|
265
|
+
this.logger.debug(
|
|
266
266
|
`EventEmitter: Removed all ${listenerCount} listeners for "${String(event)}"`
|
|
267
267
|
);
|
|
268
268
|
}
|
|
@@ -272,7 +272,7 @@ export class EventEmitter<T> {
|
|
|
272
272
|
this.listeners.clear();
|
|
273
273
|
|
|
274
274
|
if (this.debugMode) {
|
|
275
|
-
|
|
275
|
+
this.logger.debug(`EventEmitter: Removed all ${eventCount} events and their listeners`);
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -327,7 +327,7 @@ export class EventEmitter<T> {
|
|
|
327
327
|
*/
|
|
328
328
|
setDebugMode(enabled: boolean): void {
|
|
329
329
|
this.debugMode = enabled;
|
|
330
|
-
|
|
330
|
+
this.logger.debug(`EventEmitter: Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/**
|
|
@@ -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();
|
package/src/device/index.ts
CHANGED
|
@@ -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';
|