taro-bluetooth-print 2.4.0 → 2.5.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 (43) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +10 -2
  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/adapters/QQAdapter.d.ts +22 -0
  7. package/dist/types/adapters/ReactNativeAdapter.d.ts +111 -0
  8. package/dist/types/adapters/index.d.ts +13 -0
  9. package/dist/types/device/MultiPrinterManager.d.ts +1 -1
  10. package/dist/types/drivers/StarPrinter.d.ts +243 -0
  11. package/dist/types/drivers/index.d.ts +1 -0
  12. package/dist/types/encoding/EncodingService.d.ts +41 -2
  13. package/dist/types/encoding/index.d.ts +2 -1
  14. package/dist/types/encoding/korean-japanese.d.ts +127 -0
  15. package/dist/types/index.d.ts +1 -1
  16. package/dist/types/services/BatchPrintManager.d.ts +98 -5
  17. package/dist/types/services/PrintStatistics.d.ts +189 -0
  18. package/dist/types/services/ScheduledRetryManager.d.ts +213 -0
  19. package/dist/types/services/index.d.ts +5 -3
  20. package/dist/types/utils/image.d.ts +40 -119
  21. package/dist/types/utils/platform.d.ts +2 -0
  22. package/package.json +1 -1
  23. package/src/adapters/AdapterFactory.ts +5 -0
  24. package/src/adapters/QQAdapter.ts +36 -0
  25. package/src/adapters/ReactNativeAdapter.ts +517 -0
  26. package/src/adapters/index.ts +14 -0
  27. package/src/config/PrinterConfigManager.ts +10 -6
  28. package/src/device/MultiPrinterManager.ts +10 -10
  29. package/src/drivers/StarPrinter.ts +555 -0
  30. package/src/drivers/index.ts +10 -0
  31. package/src/encoding/EncodingService.ts +261 -4
  32. package/src/encoding/index.ts +17 -1
  33. package/src/encoding/korean-japanese.ts +289 -0
  34. package/src/index.ts +1 -5
  35. package/src/services/BatchPrintManager.ts +312 -42
  36. package/src/services/PrintHistory.ts +13 -11
  37. package/src/services/PrintJobManager.ts +3 -3
  38. package/src/services/PrintStatistics.ts +504 -0
  39. package/src/services/PrinterStatus.ts +4 -10
  40. package/src/services/ScheduledRetryManager.ts +564 -0
  41. package/src/services/index.ts +38 -3
  42. package/src/utils/image.ts +476 -342
  43. package/src/utils/platform.ts +20 -34
@@ -0,0 +1,517 @@
1
+ /**
2
+ * React Native Bluetooth Adapter
3
+ * Implements the IPrinterAdapter interface for React Native using react-native-ble-plx
4
+ *
5
+ * React Native does not have a native Web BLE API, so this adapter uses
6
+ * the react-native-ble-plx library for BLE operations on iOS and Android.
7
+ */
8
+
9
+ // Platform type - React Native availability is checked at runtime via Platform.OS
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const Platform = (globalThis as any).Platform;
12
+
13
+ import {
14
+ BaseAdapter,
15
+ } from './BaseAdapter';
16
+ import { IPrinterAdapter, IAdapterOptions, PrinterState } from '@/types';
17
+ import { Logger } from '@/utils/logger';
18
+ import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
19
+
20
+ /**
21
+ * BLE characteristic info with full UUIDs
22
+ */
23
+ interface RNCharacteristic {
24
+ uuid: string;
25
+ isWritableWithResponse: boolean;
26
+ isWritableWithoutResponse: boolean;
27
+ isReadable: boolean;
28
+ isNotifiable: boolean;
29
+ isIndicatable: boolean;
30
+ }
31
+
32
+ /**
33
+ * RNService info with characteristic list
34
+ */
35
+ interface RNService {
36
+ uuid: string;
37
+ characteristics: RNCharacteristic[];
38
+ }
39
+
40
+ /**
41
+ * React Native BLE Manager interface
42
+ * Compatible with react-native-ble-plx API
43
+ */
44
+ interface BLEManager {
45
+ startDeviceScan(
46
+ serviceUUIDs: string[] | null,
47
+ options: Record<string, unknown> | null,
48
+ onDeviceScanned: (error: unknown, device: unknown) => void
49
+ ): void;
50
+ stopDeviceScan(): void;
51
+ connectToDevice(
52
+ deviceIdentifier: string,
53
+ options: Record<string, unknown>
54
+ ): Promise<unknown>;
55
+ disconnectFromDevice(
56
+ deviceIdentifier: string,
57
+ force?: boolean
58
+ ): Promise<unknown>;
59
+ discoverAllServicesAndCharacteristicsForDevice(
60
+ deviceIdentifier: string
61
+ ): Promise<unknown>;
62
+ writeCharacteristicWithResponseForDevice(
63
+ deviceIdentifier: string,
64
+ serviceUUID: string,
65
+ characteristicUUID: string,
66
+ value: string,
67
+ transactionId?: string
68
+ ): Promise<unknown>;
69
+ writeCharacteristicWithoutResponseForDevice(
70
+ deviceIdentifier: string,
71
+ serviceUUID: string,
72
+ characteristicUUID: string,
73
+ value: string,
74
+ transactionId?: string
75
+ ): Promise<unknown>;
76
+ readCharacteristicForDevice(
77
+ deviceIdentifier: string,
78
+ serviceUUID: string,
79
+ characteristicUUID: string,
80
+ transactionId?: string
81
+ ): Promise<unknown>;
82
+ monitorCharacteristicForDevice(
83
+ deviceIdentifier: string,
84
+ serviceUUID: string,
85
+ characteristicUUID: string,
86
+ onUpdate: (error: unknown, characteristic: unknown) => void,
87
+ transactionId?: string
88
+ ): { remove: () => void };
89
+ }
90
+
91
+ /**
92
+ * BLE Device interface from react-native-ble-plx
93
+ */
94
+ interface RNDevice {
95
+ id: string;
96
+ name: string | null;
97
+ isConnected: boolean;
98
+ rssi: number;
99
+ mtu: number;
100
+ requestConnectionPriority(): Promise<void>;
101
+ }
102
+
103
+ /**
104
+ * React Native Bluetooth Low Energy adapter
105
+ *
106
+ * Uses react-native-ble-plx for BLE operations on iOS and Android.
107
+ * This adapter does NOT extend MiniProgramAdapter because React Native
108
+ * has a fundamentally different BLE API compared to mini-program platforms.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * import BleManager from 'react-native-ble-plx';
113
+ * import { ReactNativeAdapter } from 'taro-bluetooth-print';
114
+ *
115
+ * BleManager.start({ showAlert: false });
116
+ *
117
+ * const adapter = new ReactNativeAdapter({ bleManager: BleManager });
118
+ * await adapter.connect('device-uuid-123');
119
+ * await adapter.write('device-uuid-123', buffer);
120
+ * await adapter.disconnect('device-uuid-123');
121
+ * ```
122
+ */
123
+ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
124
+ private bleManager: BLEManager;
125
+ private deviceCache: Map<string, RNDevice> = new Map();
126
+
127
+ /**
128
+ * Creates a new ReactNativeAdapter instance
129
+ *
130
+ * @param options - Configuration options
131
+ * @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
132
+ * @throws {Error} If bleManager is not provided or not supported
133
+ */
134
+ constructor(options: { bleManager: BLEManager }) {
135
+ super();
136
+
137
+ if (!options?.bleManager) {
138
+ throw new Error(
139
+ 'ReactNativeAdapter requires a bleManager instance (e.g., react-native-ble-plx). ' +
140
+ 'Please pass { bleManager: yourBleManager } in the constructor.'
141
+ );
142
+ }
143
+
144
+ this.bleManager = options.bleManager;
145
+
146
+ // Validate platform
147
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
148
+ Logger.scope('ReactNativeAdapter').warn(
149
+ `Running on unsupported platform: ${Platform.OS}. BLE may not work correctly.`
150
+ );
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Connect to a Bluetooth device and discover services
156
+ *
157
+ * @param deviceId - Unique identifier (UUID) of the device to connect to
158
+ * @throws {BluetoothPrintError} When connection fails or device not found
159
+ */
160
+ async connect(deviceId: string): Promise<void> {
161
+ this.validateDeviceId(deviceId);
162
+
163
+ if (this.isDeviceConnected(deviceId)) {
164
+ Logger.scope('ReactNativeAdapter').warn('Device already connected:', deviceId);
165
+ this.updateState(PrinterState.CONNECTED);
166
+ return;
167
+ }
168
+
169
+ this.updateState(PrinterState.CONNECTING);
170
+ Logger.scope('ReactNativeAdapter').debug('Connecting to device:', deviceId);
171
+
172
+ try {
173
+ // Add connection timeout
174
+ const timeoutMs = 15000;
175
+ const connectionPromise = this.performConnect(deviceId);
176
+
177
+ let timeoutHandle: NodeJS.Timeout | null = null;
178
+ const timeoutPromise = new Promise<never>((_, reject) => {
179
+ timeoutHandle = setTimeout(() => {
180
+ reject(new Error('Connection timeout'));
181
+ }, timeoutMs);
182
+ });
183
+
184
+ const device = await Promise.race([connectionPromise, timeoutPromise]);
185
+
186
+ if (timeoutHandle) {
187
+ clearTimeout(timeoutHandle);
188
+ }
189
+
190
+ this.deviceCache.set(deviceId, device as RNDevice);
191
+ await this.discoverServices(deviceId, device as RNDevice);
192
+
193
+ this.updateState(PrinterState.CONNECTED);
194
+ Logger.scope('ReactNativeAdapter').info('Device connected successfully');
195
+ } catch (error) {
196
+ this.updateState(PrinterState.DISCONNECTED);
197
+ this.cleanupDevice(deviceId);
198
+
199
+ const errorMsg = (error as Error).message || '';
200
+ if (errorMsg.includes('timeout')) {
201
+ throw new BluetoothPrintError(
202
+ ErrorCode.CONNECTION_TIMEOUT,
203
+ `Connection to device ${deviceId} timed out`,
204
+ error as Error
205
+ );
206
+ } else if (errorMsg.includes('not found') || errorMsg.includes('not exist')) {
207
+ throw new BluetoothPrintError(
208
+ ErrorCode.DEVICE_NOT_FOUND,
209
+ `Device ${deviceId} not found`,
210
+ error as Error
211
+ );
212
+ }
213
+
214
+ throw new BluetoothPrintError(
215
+ ErrorCode.CONNECTION_FAILED,
216
+ `Failed to connect to device ${deviceId}`,
217
+ error as Error
218
+ );
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Perform the actual BLE connection
224
+ */
225
+ private async performConnect(deviceId: string): Promise<unknown> {
226
+ const device = await (this.bleManager.connectToDevice(deviceId, {
227
+ timeout: 10000,
228
+ }) as Promise<RNDevice>);
229
+
230
+ // Request connection priority for better throughput
231
+ try {
232
+ await device.requestConnectionPriority();
233
+ } catch {
234
+ // Ignore priority request errors
235
+ }
236
+
237
+ return device;
238
+ }
239
+
240
+ /**
241
+ * Disconnect from a Bluetooth device
242
+ *
243
+ * @param deviceId - Unique identifier of the device to disconnect from
244
+ */
245
+ async disconnect(deviceId: string): Promise<void> {
246
+ this.validateDeviceId(deviceId);
247
+ this.updateState(PrinterState.DISCONNECTING);
248
+ Logger.scope('ReactNativeAdapter').debug('Disconnecting from device:', deviceId);
249
+
250
+ try {
251
+ await (this.bleManager.disconnectFromDevice(deviceId, true) as Promise<void>);
252
+ this.cleanupDevice(deviceId);
253
+ this.deviceCache.delete(deviceId);
254
+ this.updateState(PrinterState.DISCONNECTED);
255
+ Logger.scope('ReactNativeAdapter').info('Device disconnected successfully');
256
+ } catch (error) {
257
+ Logger.scope('ReactNativeAdapter').warn('Disconnect error (ignored):', error);
258
+ this.cleanupDevice(deviceId);
259
+ this.deviceCache.delete(deviceId);
260
+ this.updateState(PrinterState.DISCONNECTED);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Write data to the Bluetooth device in chunks
266
+ *
267
+ * Features:
268
+ * - Automatic chunk size adjustment
269
+ * - Dynamic delay for congestion control
270
+ * - Retry with exponential backoff
271
+ * - Write timeout per chunk
272
+ *
273
+ * @param deviceId - Unique identifier of the connected device
274
+ * @param buffer - Data to write as ArrayBuffer
275
+ * @param options - Optional write settings (chunkSize, delay, retries)
276
+ * @throws {BluetoothPrintError} When write fails after all retries
277
+ */
278
+ async write(deviceId: string, buffer: ArrayBuffer, options?: IAdapterOptions): Promise<void> {
279
+ this.validateDeviceId(deviceId);
280
+ this.validateBuffer(buffer);
281
+ const serviceInfo = this.getServiceInfo(deviceId);
282
+ const validatedOptions = this.validateOptions(options);
283
+
284
+ const device = this.deviceCache.get(deviceId);
285
+ if (!device || !device.isConnected) {
286
+ this.cleanupDevice(deviceId);
287
+ throw new BluetoothPrintError(
288
+ ErrorCode.DEVICE_DISCONNECTED,
289
+ `Device ${deviceId} is not connected`
290
+ );
291
+ }
292
+
293
+ let { chunkSize } = validatedOptions;
294
+ const { delay, retries } = validatedOptions;
295
+ const data = new Uint8Array(buffer);
296
+ const totalChunks = Math.ceil(data.length / chunkSize);
297
+
298
+ Logger.scope('ReactNativeAdapter').debug(
299
+ `Writing ${data.length} bytes in ${totalChunks} chunks`
300
+ );
301
+
302
+ if (data.length === 0) {
303
+ Logger.scope('ReactNativeAdapter').warn('No data to write');
304
+ return;
305
+ }
306
+
307
+ // Adaptive transmission parameters
308
+ let successCount = 0;
309
+ let consecutiveFailures = 0;
310
+ const minChunkSize = 10;
311
+ const maxChunkSize = Math.min(512, device.mtu - 5); // Respect MTU if available
312
+ let baseDelay = delay;
313
+ const maxDelay = 200;
314
+
315
+ for (let i = 0; i < data.length; i += chunkSize) {
316
+ const chunk = data.slice(i, i + chunkSize);
317
+ const chunkNum = Math.floor(i / chunkSize) + 1;
318
+ let attempt = 0;
319
+ let writeSuccess = false;
320
+
321
+ while (attempt <= retries) {
322
+ try {
323
+ // Convert Uint8Array to base64 string for react-native-ble-plx
324
+ const base64Value = this.arrayBufferToBase64(chunk.buffer);
325
+
326
+ // Timeout per write
327
+ const timeoutMs = Math.max(2000, Math.min(10000, 1000 + chunk.length * 10));
328
+ let timeoutHandle: NodeJS.Timeout | null = null;
329
+
330
+ const writePromise = (async () => {
331
+ try {
332
+ await (this.bleManager.writeCharacteristicWithResponseForDevice(
333
+ deviceId,
334
+ serviceInfo.serviceId,
335
+ serviceInfo.characteristicId,
336
+ base64Value
337
+ ) as Promise<void>);
338
+ } catch {
339
+ // Fallback to without-response if with-response fails
340
+ await (this.bleManager.writeCharacteristicWithoutResponseForDevice(
341
+ deviceId,
342
+ serviceInfo.serviceId,
343
+ serviceInfo.characteristicId,
344
+ base64Value
345
+ ) as Promise<void>);
346
+ }
347
+ })();
348
+
349
+ const timeoutPromise = new Promise<never>((_, reject) => {
350
+ timeoutHandle = setTimeout(() => {
351
+ reject(new Error(`Write timeout after ${timeoutMs}ms`));
352
+ }, timeoutMs);
353
+ });
354
+
355
+ await Promise.race([writePromise, timeoutPromise]);
356
+
357
+ if (timeoutHandle) {
358
+ clearTimeout(timeoutHandle);
359
+ }
360
+
361
+ Logger.scope('ReactNativeAdapter').debug(
362
+ `Chunk ${chunkNum}/${totalChunks} written successfully`
363
+ );
364
+ writeSuccess = true;
365
+ break;
366
+ } catch (error) {
367
+ attempt++;
368
+ if (attempt > retries) {
369
+ Logger.scope('ReactNativeAdapter').error(
370
+ `Chunk ${chunkNum} failed after ${retries} retries`
371
+ );
372
+ throw new BluetoothPrintError(
373
+ ErrorCode.WRITE_FAILED,
374
+ `Failed to write chunk ${chunkNum}/${totalChunks}`,
375
+ error as Error
376
+ );
377
+ }
378
+ Logger.scope('ReactNativeAdapter').warn(
379
+ `Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`
380
+ );
381
+
382
+ const retryDelay = baseDelay * Math.pow(2, attempt - 1);
383
+ await new Promise(r => setTimeout(r, Math.min(retryDelay, maxDelay)));
384
+ }
385
+ }
386
+
387
+ // Adaptive chunk size and delay adjustment
388
+ if (writeSuccess) {
389
+ successCount++;
390
+ consecutiveFailures = 0;
391
+
392
+ if (successCount % 3 === 0 && chunkSize < maxChunkSize) {
393
+ chunkSize = Math.min(maxChunkSize, chunkSize + 5);
394
+ baseDelay = Math.max(baseDelay / 1.2, validatedOptions.delay);
395
+ }
396
+ } else {
397
+ consecutiveFailures++;
398
+ successCount = Math.max(0, successCount - 1);
399
+
400
+ if (consecutiveFailures >= 2 && chunkSize > minChunkSize) {
401
+ chunkSize = Math.max(minChunkSize, chunkSize - 5);
402
+ baseDelay = Math.min(baseDelay * 1.5, maxDelay);
403
+ consecutiveFailures = 0;
404
+ }
405
+ }
406
+
407
+ // Inter-chunk delay
408
+ if (i + chunkSize < data.length) {
409
+ await new Promise(r => setTimeout(r, baseDelay));
410
+ }
411
+ }
412
+
413
+ Logger.scope('ReactNativeAdapter').info(`Successfully wrote ${data.length} bytes`);
414
+ }
415
+
416
+ /**
417
+ * Start discovering nearby Bluetooth devices
418
+ *
419
+ * Note: This is optional in IPrinterAdapter. In React Native BLE,
420
+ * device discovery is typically done via scan events.
421
+ */
422
+ startDiscovery?(): Promise<void> {
423
+ return new Promise((resolve, reject) => {
424
+ try {
425
+ this.bleManager.startDeviceScan(
426
+ null,
427
+ { allowDuplicates: false },
428
+ (error: unknown) => {
429
+ if (error) {
430
+ reject(error);
431
+ }
432
+ }
433
+ );
434
+ resolve();
435
+ } catch (error) {
436
+ reject(error);
437
+ }
438
+ });
439
+ }
440
+
441
+ /**
442
+ * Stop discovering nearby Bluetooth devices
443
+ */
444
+ stopDiscovery?(): Promise<void> {
445
+ return new Promise((resolve) => {
446
+ this.bleManager.stopDeviceScan();
447
+ resolve();
448
+ });
449
+ }
450
+
451
+ /**
452
+ * Discover services and characteristics for a connected device
453
+ *
454
+ * @param deviceId - Device identifier
455
+ * @param device - Connected device object
456
+ */
457
+ private async discoverServices(deviceId: string, _device: RNDevice): Promise<void> {
458
+ Logger.scope('ReactNativeAdapter').debug('Discovering services for device:', deviceId);
459
+
460
+ try {
461
+ const servicesResult = await (this.bleManager.discoverAllServicesAndCharacteristicsForDevice(
462
+ deviceId
463
+ ) as Promise<{ services: RNService[] }>);
464
+
465
+ const services = servicesResult.services || [];
466
+
467
+ for (const service of services) {
468
+ const writeChar = service.characteristics.find(
469
+ (c: RNCharacteristic) =>
470
+ c.isWritableWithResponse || c.isWritableWithoutResponse
471
+ );
472
+
473
+ if (writeChar) {
474
+ this.serviceCache.set(deviceId, {
475
+ serviceId: service.uuid,
476
+ characteristicId: writeChar.uuid,
477
+ });
478
+ Logger.scope('ReactNativeAdapter').info('Found writeable characteristic:', {
479
+ service: service.uuid,
480
+ characteristic: writeChar.uuid,
481
+ });
482
+ return;
483
+ }
484
+ }
485
+
486
+ throw new BluetoothPrintError(
487
+ ErrorCode.CHARACTERISTIC_NOT_FOUND,
488
+ 'No writeable characteristic found. Make sure the device is a supported printer.'
489
+ );
490
+ } catch (error) {
491
+ if (error instanceof BluetoothPrintError) {
492
+ throw error;
493
+ }
494
+ throw new BluetoothPrintError(
495
+ ErrorCode.SERVICE_DISCOVERY_FAILED,
496
+ 'Failed to discover device services',
497
+ error as Error
498
+ );
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Convert ArrayBuffer to base64 string for react-native-ble-plx
504
+ *
505
+ * @param buffer - ArrayBuffer to convert
506
+ * @returns Base64 encoded string
507
+ */
508
+ private arrayBufferToBase64(buffer: ArrayBuffer): string {
509
+ const bytes = new Uint8Array(buffer);
510
+ let binary = '';
511
+ for (let i = 0; i < bytes.byteLength; i++) {
512
+ binary += String.fromCharCode(bytes[i] ?? 0);
513
+ }
514
+ // Use built-in btoa (available in React Native)
515
+ return globalThis.btoa(binary);
516
+ }
517
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Adapters barrel export
3
+ */
4
+
5
+ export { BaseAdapter, MiniProgramAdapter } from './BaseAdapter';
6
+ export type { MiniProgramBLEApi, ServiceInfo, BLECharacteristic, BLECharacteristicProperties } from './BaseAdapter';
7
+ export { TaroAdapter } from './TaroAdapter';
8
+ export { AlipayAdapter } from './AlipayAdapter';
9
+ export { BaiduAdapter } from './BaiduAdapter';
10
+ export { ByteDanceAdapter } from './ByteDanceAdapter';
11
+ export { QQAdapter } from './QQAdapter';
12
+ export { ReactNativeAdapter } from './ReactNativeAdapter';
13
+ export { WebBluetoothAdapter } from './WebBluetoothAdapter';
14
+ export { AdapterFactory } from './AdapterFactory';
@@ -425,12 +425,16 @@ export class PrinterConfigManager {
425
425
  * Export all configuration as JSON
426
426
  */
427
427
  export(): string {
428
- return JSON.stringify({
429
- printers: Array.from(this.printers.values()),
430
- globalConfig: this.globalConfig,
431
- lastUsedPrinterId: this.lastUsedPrinterId,
432
- exportedAt: Date.now(),
433
- }, null, 2);
428
+ return JSON.stringify(
429
+ {
430
+ printers: Array.from(this.printers.values()),
431
+ globalConfig: this.globalConfig,
432
+ lastUsedPrinterId: this.lastUsedPrinterId,
433
+ exportedAt: Date.now(),
434
+ },
435
+ null,
436
+ 2
437
+ );
434
438
  }
435
439
 
436
440
  /**
@@ -26,6 +26,7 @@
26
26
  import { Logger } from '@/utils/logger';
27
27
  import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
28
28
  import { BluetoothPrinter } from '@/core/BluetoothPrinter';
29
+ import { PrinterState } from '@/types';
29
30
 
30
31
  /**
31
32
  * Printer connection info
@@ -137,11 +138,12 @@ export class MultiPrinterManager {
137
138
  * Emit an event
138
139
  */
139
140
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
140
142
  private emit<K extends keyof MultiPrinterManagerEvents>(event: K, data: any): void {
141
143
  this.listeners[event].forEach(handler => {
142
144
  try {
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- (handler as any)(data);
145
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
146
+ (handler as (data: any) => void)(data);
145
147
  } catch (error) {
146
148
  this.logger.error(`Error in event handler for "${event}":`, error);
147
149
  }
@@ -200,7 +202,7 @@ export class MultiPrinterManager {
200
202
  const printer = new BluetoothPrinter();
201
203
 
202
204
  // Set up error handler
203
- printer.on('error', (error) => {
205
+ printer.on('error', error => {
204
206
  this.emit('printer-error', { printerId, error });
205
207
  });
206
208
 
@@ -323,13 +325,10 @@ export class MultiPrinterManager {
323
325
  /**
324
326
  * Print to a specific printer
325
327
  */
326
- async print(printerId: string, data: Uint8Array): Promise<void> {
328
+ print(printerId: string, data: Uint8Array): void {
327
329
  const connection = this.printers.get(printerId);
328
330
  if (!connection) {
329
- throw new BluetoothPrintError(
330
- ErrorCode.DEVICE_NOT_FOUND,
331
- `Printer not found: ${printerId}`
332
- );
331
+ throw new BluetoothPrintError(ErrorCode.DEVICE_NOT_FOUND, `Printer not found: ${printerId}`);
333
332
  }
334
333
 
335
334
  connection.lastActivity = Date.now();
@@ -358,6 +357,7 @@ export class MultiPrinterManager {
358
357
  }
359
358
 
360
359
  const printPromises = Array.from(this.printers.entries()).map(
360
+ // eslint-disable-next-line @typescript-eslint/require-await
361
361
  async ([printerId, connection]) => {
362
362
  try {
363
363
  // Update activity
@@ -401,7 +401,7 @@ export class MultiPrinterManager {
401
401
  */
402
402
  getIdlePrinters(): PrinterConnection[] {
403
403
  return Array.from(this.printers.values())
404
- .filter(c => c.printer.state === 'connected')
404
+ .filter(c => c.printer.state === PrinterState.CONNECTED)
405
405
  .sort((a, b) => (a.lastActivity ?? 0) - (b.lastActivity ?? 0));
406
406
  }
407
407
 
@@ -420,7 +420,7 @@ export class MultiPrinterManager {
420
420
  };
421
421
 
422
422
  for (const connection of this.printers.values()) {
423
- if (connection.printer.state === 'connected') {
423
+ if (connection.printer.state === PrinterState.CONNECTED) {
424
424
  stats.connected++;
425
425
  }
426
426