taro-bluetooth-print 2.9.3 → 2.9.6
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 +44 -396
- package/README.md +197 -132
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/adapters/BaseAdapter.d.ts +5 -0
- package/dist/types/adapters/WebBluetoothAdapter.d.ts +5 -0
- package/dist/types/core/BluetoothPrinter.d.ts +10 -0
- package/dist/types/drivers/CpclDriver.d.ts +9 -4
- package/dist/types/drivers/ZplDriver.d.ts +9 -9
- package/dist/types/queue/PrintQueue.d.ts +1 -0
- package/dist/types/services/interfaces/IConnectionManager.d.ts +5 -0
- package/dist/types/types.d.ts +5 -0
- package/package.json +12 -2
- package/src/adapters/BaseAdapter.ts +10 -0
- package/src/adapters/WebBluetoothAdapter.ts +48 -4
- package/src/core/BluetoothPrinter.ts +40 -19
- package/src/drivers/CpclDriver.ts +34 -7
- package/src/drivers/ZplDriver.ts +31 -12
- package/src/queue/PrintQueue.ts +9 -1
- package/src/services/ConnectionManager.ts +24 -22
- package/src/services/PrinterStatus.ts +5 -1
- package/src/services/interfaces/IConnectionManager.ts +6 -0
- package/src/types.ts +6 -0
|
@@ -147,6 +147,11 @@ export declare abstract class BaseAdapter implements IPrinterAdapter {
|
|
|
147
147
|
* @param deviceId - Device ID
|
|
148
148
|
*/
|
|
149
149
|
protected cleanupDevice(deviceId: string): void;
|
|
150
|
+
/**
|
|
151
|
+
* Cleanup resources and destroy the adapter instance
|
|
152
|
+
* Removes all event listeners and releases resources
|
|
153
|
+
*/
|
|
154
|
+
destroy(): void;
|
|
150
155
|
}
|
|
151
156
|
/**
|
|
152
157
|
* Base adapter for mini-program platforms (Taro/WeChat, Alipay, Baidu, ByteDance).
|
|
@@ -173,4 +173,9 @@ export declare class WebBluetoothAdapter extends BaseAdapter {
|
|
|
173
173
|
* Uses device name + first seen timestamp as identifier
|
|
174
174
|
*/
|
|
175
175
|
private generateFallbackDeviceId;
|
|
176
|
+
/**
|
|
177
|
+
* Cleanup resources and destroy the adapter instance
|
|
178
|
+
* Removes all event listeners and releases resources
|
|
179
|
+
*/
|
|
180
|
+
destroy(): void;
|
|
176
181
|
}
|
|
@@ -338,4 +338,14 @@ export declare class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
338
338
|
* @returns Command builder instance
|
|
339
339
|
*/
|
|
340
340
|
getCommandBuilder(): ICommandBuilder;
|
|
341
|
+
/**
|
|
342
|
+
* Cleanup resources and destroy the printer instance
|
|
343
|
+
* Removes all event listeners and releases resources
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```typescript
|
|
347
|
+
* printer.destroy();
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
destroy(): void;
|
|
341
351
|
}
|
|
@@ -264,12 +264,17 @@ export declare class CpclDriver {
|
|
|
264
264
|
*/
|
|
265
265
|
logo(x: number, y: number, logoName: string): this;
|
|
266
266
|
/**
|
|
267
|
-
* Download logo to printer (
|
|
268
|
-
*
|
|
267
|
+
* Download logo to printer memory (macro/form storage).
|
|
268
|
+
* Encodes bitmap using CPCL CG (Compressed Graphic) command.
|
|
269
|
+
* The bitmap should be 1-bit monochrome data.
|
|
269
270
|
* @param logoName - Name to store logo as
|
|
270
|
-
* @param
|
|
271
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
272
|
+
* @param options - Optional width and height (will be inferred from bitmap size if omitted)
|
|
271
273
|
*/
|
|
272
|
-
downloadLogo(logoName: string,
|
|
274
|
+
downloadLogo(logoName: string, bitmap: Uint8Array, options?: {
|
|
275
|
+
width?: number;
|
|
276
|
+
height?: number;
|
|
277
|
+
}): this;
|
|
273
278
|
/**
|
|
274
279
|
* Print stored logo
|
|
275
280
|
* @param logoName - Logo name
|
|
@@ -267,15 +267,15 @@ export declare class ZplDriver {
|
|
|
267
267
|
*/
|
|
268
268
|
ellipse(x: number, y: number, width: number, height: number, borderThickness?: number): this;
|
|
269
269
|
/**
|
|
270
|
-
* Add image from raw bitmap
|
|
271
|
-
*
|
|
272
|
-
* @param
|
|
273
|
-
* @param
|
|
274
|
-
* @param
|
|
275
|
-
* @param
|
|
276
|
-
* @param
|
|
277
|
-
*/
|
|
278
|
-
image(
|
|
270
|
+
* Add image from raw bitmap using ZPL ^GFA (Graphic Field) command.
|
|
271
|
+
* The bitmap should be 1-bit (monochrome) data where each bit represents a pixel.
|
|
272
|
+
* @param x - X position in dots
|
|
273
|
+
* @param y - Y position in dots
|
|
274
|
+
* @param width - Image width in pixels
|
|
275
|
+
* @param height - Image height in pixels
|
|
276
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
277
|
+
*/
|
|
278
|
+
image(x: number, y: number, width: number, height: number, bitmap: Uint8Array): this;
|
|
279
279
|
/**
|
|
280
280
|
* Set darkness/print density
|
|
281
281
|
* @param value - Darkness value (0-30, default: 15)
|
|
@@ -46,4 +46,9 @@ export interface IConnectionManager {
|
|
|
46
46
|
* @returns IPrinterAdapter - Printer adapter
|
|
47
47
|
*/
|
|
48
48
|
getAdapter(): IPrinterAdapter;
|
|
49
|
+
/**
|
|
50
|
+
* Cleanup resources and destroy the connection manager
|
|
51
|
+
* Stops heartbeat, clears timers, and removes event listeners
|
|
52
|
+
*/
|
|
53
|
+
destroy(): void;
|
|
49
54
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -67,6 +67,11 @@ export interface IPrinterAdapter {
|
|
|
67
67
|
* @param callback - Function to call when the state changes
|
|
68
68
|
*/
|
|
69
69
|
onStateChange?(callback: (state: PrinterState) => void): void;
|
|
70
|
+
/**
|
|
71
|
+
* Cleanup resources and destroy the adapter instance
|
|
72
|
+
* Removes all event listeners and releases resources
|
|
73
|
+
*/
|
|
74
|
+
destroy?(): void;
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* Interface for printer drivers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taro-bluetooth-print",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.6",
|
|
4
4
|
"description": "Taro 蓝牙打印库 v2.6 - 轻量级、高性能、跨平台支持微信、支付宝、百度、字节跳动小程序及H5 Web Bluetooth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -88,5 +88,15 @@
|
|
|
88
88
|
"vitepress": "^1.6.4",
|
|
89
89
|
"vitest": "^4.0.18"
|
|
90
90
|
},
|
|
91
|
-
"sideEffects": false
|
|
91
|
+
"sideEffects": false,
|
|
92
|
+
"pnpm": {
|
|
93
|
+
"overrides": {
|
|
94
|
+
"esbuild@<=0.24.2": ">=0.25.0",
|
|
95
|
+
"lodash-es@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
96
|
+
"lodash-es@>=4.0.0 <=4.17.23": ">=4.18.0",
|
|
97
|
+
"lodash-es@<=4.17.23": ">=4.18.0",
|
|
98
|
+
"vite@<=6.4.1": ">=6.4.2",
|
|
99
|
+
"postcss@<8.5.10": ">=8.5.10"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
92
102
|
}
|
|
@@ -185,6 +185,16 @@ export abstract class BaseAdapter implements IPrinterAdapter {
|
|
|
185
185
|
protected cleanupDevice(deviceId: string): void {
|
|
186
186
|
this.serviceCache.delete(deviceId);
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Cleanup resources and destroy the adapter instance
|
|
191
|
+
* Removes all event listeners and releases resources
|
|
192
|
+
*/
|
|
193
|
+
destroy(): void {
|
|
194
|
+
this.logger.debug('Destroying BaseAdapter');
|
|
195
|
+
this.serviceCache.clear();
|
|
196
|
+
this.stateCallback = undefined;
|
|
197
|
+
}
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
/**
|
|
@@ -10,6 +10,14 @@ import { IAdapterOptions, PrinterState } from '@/types';
|
|
|
10
10
|
import { BaseAdapter } from './BaseAdapter';
|
|
11
11
|
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Extended Bluetooth device interface with RSSI support
|
|
15
|
+
* This is a vendor-specific extension not part of the standard Web Bluetooth API
|
|
16
|
+
*/
|
|
17
|
+
interface BluetoothDeviceWithRssi extends BluetoothDevice {
|
|
18
|
+
readRemoteRssi(): Promise<number>;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Web Bluetooth device information
|
|
15
23
|
*/
|
|
@@ -210,10 +218,12 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
210
218
|
// Get RSSI if available (may not be available on all devices)
|
|
211
219
|
let rssi: number | undefined;
|
|
212
220
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
const deviceWithRssi = characteristic.service.device as unknown as BluetoothDeviceWithRssi;
|
|
222
|
+
if (
|
|
223
|
+
'readRemoteRssi' in deviceWithRssi &&
|
|
224
|
+
typeof deviceWithRssi.readRemoteRssi === 'function'
|
|
225
|
+
) {
|
|
226
|
+
rssi = await deviceWithRssi.readRemoteRssi();
|
|
217
227
|
}
|
|
218
228
|
} catch {
|
|
219
229
|
this.logger.debug('RSSI reading not supported on this device');
|
|
@@ -681,4 +691,38 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
681
691
|
const timestamp = Date.now().toString(36);
|
|
682
692
|
return `fallback_${name}_${timestamp}`;
|
|
683
693
|
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Cleanup resources and destroy the adapter instance
|
|
697
|
+
* Removes all event listeners and releases resources
|
|
698
|
+
*/
|
|
699
|
+
destroy(): void {
|
|
700
|
+
this.logger.debug('Destroying WebBluetoothAdapter');
|
|
701
|
+
|
|
702
|
+
// Clear any pending connection cleanup timeout
|
|
703
|
+
if (this.connectionCleanupTimeout) {
|
|
704
|
+
clearTimeout(this.connectionCleanupTimeout);
|
|
705
|
+
this.connectionCleanupTimeout = null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Disconnect all devices
|
|
709
|
+
for (const [deviceId, deviceInfo] of this.devices) {
|
|
710
|
+
try {
|
|
711
|
+
if (deviceInfo.server.connected) {
|
|
712
|
+
deviceInfo.server.disconnect();
|
|
713
|
+
}
|
|
714
|
+
} catch (error) {
|
|
715
|
+
this.logger.warn(`Error disconnecting device ${deviceId}:`, error);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Clear all device caches
|
|
720
|
+
this.devices.clear();
|
|
721
|
+
this.discoveredDevices.clear();
|
|
722
|
+
|
|
723
|
+
// Call parent destroy
|
|
724
|
+
super.destroy();
|
|
725
|
+
|
|
726
|
+
this.logger.info('WebBluetoothAdapter destroyed');
|
|
727
|
+
}
|
|
684
728
|
}
|
|
@@ -131,19 +131,12 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
131
131
|
* Updates the current state based on the connection manager and print job manager states
|
|
132
132
|
*/
|
|
133
133
|
private updateState(): void {
|
|
134
|
-
//
|
|
135
|
-
const connectionState =
|
|
136
|
-
typeof this.connectionManager.getState === 'function'
|
|
137
|
-
? this.connectionManager.getState()
|
|
138
|
-
: PrinterState.CONNECTED;
|
|
134
|
+
// All interface methods are guaranteed to exist
|
|
135
|
+
const connectionState = this.connectionManager.getState();
|
|
139
136
|
|
|
140
|
-
const isPrinting =
|
|
141
|
-
typeof this.printJobManager.isInProgress === 'function'
|
|
142
|
-
? this.printJobManager.isInProgress()
|
|
143
|
-
: false;
|
|
137
|
+
const isPrinting = this.printJobManager.isInProgress();
|
|
144
138
|
|
|
145
|
-
const isPaused =
|
|
146
|
-
typeof this.printJobManager.isPaused === 'function' ? this.printJobManager.isPaused() : false;
|
|
139
|
+
const isPaused = this.printJobManager.isPaused();
|
|
147
140
|
|
|
148
141
|
// Determine the final state
|
|
149
142
|
if (isPaused) {
|
|
@@ -425,10 +418,7 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
425
418
|
* ```
|
|
426
419
|
*/
|
|
427
420
|
async print(): Promise<void> {
|
|
428
|
-
const isConnected =
|
|
429
|
-
typeof this.connectionManager.isConnected === 'function'
|
|
430
|
-
? this.connectionManager.isConnected()
|
|
431
|
-
: true; // Default to true for mock objects
|
|
421
|
+
const isConnected = this.connectionManager.isConnected();
|
|
432
422
|
|
|
433
423
|
if (!isConnected) {
|
|
434
424
|
throw new BluetoothPrintError(
|
|
@@ -453,10 +443,7 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
453
443
|
try {
|
|
454
444
|
await this.printJobManager.start(buffer);
|
|
455
445
|
|
|
456
|
-
const isPaused =
|
|
457
|
-
typeof this.printJobManager.isPaused === 'function'
|
|
458
|
-
? this.printJobManager.isPaused()
|
|
459
|
-
: false;
|
|
446
|
+
const isPaused = this.printJobManager.isPaused();
|
|
460
447
|
|
|
461
448
|
if (isPaused) {
|
|
462
449
|
// Print job was paused
|
|
@@ -607,4 +594,38 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
607
594
|
getCommandBuilder(): ICommandBuilder {
|
|
608
595
|
return this.commandBuilder;
|
|
609
596
|
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Cleanup resources and destroy the printer instance
|
|
600
|
+
* Removes all event listeners and releases resources
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```typescript
|
|
604
|
+
* printer.destroy();
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
destroy(): void {
|
|
608
|
+
this.printerLogger.info('Destroying BluetoothPrinter instance');
|
|
609
|
+
|
|
610
|
+
// Cancel any pending print job
|
|
611
|
+
this.printJobManager.cancel();
|
|
612
|
+
|
|
613
|
+
// Clear command buffer
|
|
614
|
+
this.commandBuilder.clear();
|
|
615
|
+
|
|
616
|
+
// Disconnect if connected
|
|
617
|
+
if (this.connectionManager.isConnected()) {
|
|
618
|
+
this.connectionManager.disconnect().catch(error => {
|
|
619
|
+
this.printerLogger.warn('Error during disconnect in destroy:', error);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Cleanup connection manager resources (IConnectionManager now has destroy())
|
|
624
|
+
this.connectionManager.destroy();
|
|
625
|
+
|
|
626
|
+
// Remove all event listeners
|
|
627
|
+
this.removeAllListeners();
|
|
628
|
+
|
|
629
|
+
this.printerLogger.info('BluetoothPrinter instance destroyed');
|
|
630
|
+
}
|
|
610
631
|
}
|
|
@@ -478,16 +478,43 @@ export class CpclDriver {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
|
-
* Download logo to printer (
|
|
482
|
-
*
|
|
481
|
+
* Download logo to printer memory (macro/form storage).
|
|
482
|
+
* Encodes bitmap using CPCL CG (Compressed Graphic) command.
|
|
483
|
+
* The bitmap should be 1-bit monochrome data.
|
|
483
484
|
* @param logoName - Name to store logo as
|
|
484
|
-
* @param
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
486
|
+
* @param options - Optional width and height (will be inferred from bitmap size if omitted)
|
|
487
|
+
*/
|
|
488
|
+
downloadLogo(
|
|
489
|
+
logoName: string,
|
|
490
|
+
bitmap: Uint8Array,
|
|
491
|
+
options?: { width?: number; height?: number }
|
|
492
|
+
): this {
|
|
493
|
+
const width = options?.width ?? 100;
|
|
494
|
+
const bytesPerRow = Math.ceil(width / 8);
|
|
495
|
+
const height = options?.height ?? Math.max(1, Math.floor(bitmap.length / bytesPerRow));
|
|
496
|
+
const totalBytes = bytesPerRow * height;
|
|
497
|
+
|
|
498
|
+
// Validate bitmap size
|
|
499
|
+
if (bitmap.length < totalBytes) {
|
|
500
|
+
this.logger.warn(
|
|
501
|
+
`CPCL logo bitmap size mismatch: expected ${totalBytes}, got ${bitmap.length}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Convert bitmap bytes to hex string (uppercase)
|
|
506
|
+
const hexData: string[] = [];
|
|
507
|
+
const limit = Math.min(bitmap.length, totalBytes);
|
|
508
|
+
for (let i = 0; i < limit; i++) {
|
|
509
|
+
const byte = bitmap[i]!;
|
|
510
|
+
hexData.push(byte.toString(16).padStart(2, '0').toUpperCase());
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// CPCL macro definition with graphic data
|
|
487
514
|
this.commands.push(`! DF ${logoName}`);
|
|
488
|
-
|
|
489
|
-
this.logger.debug('CPCL logo download not fully implemented');
|
|
515
|
+
this.commands.push(`CG ${totalBytes} ${bytesPerRow} ${height} ${hexData.join('')}`);
|
|
490
516
|
this.commands.push(`! DF`);
|
|
517
|
+
this.logger.debug(`CPCL logo downloaded: ${logoName} (${width}x${height})`);
|
|
491
518
|
return this;
|
|
492
519
|
}
|
|
493
520
|
|
package/src/drivers/ZplDriver.ts
CHANGED
|
@@ -428,18 +428,37 @@ export class ZplDriver {
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
/**
|
|
431
|
-
* Add image from raw bitmap
|
|
432
|
-
*
|
|
433
|
-
* @param
|
|
434
|
-
* @param
|
|
435
|
-
* @param
|
|
436
|
-
* @param
|
|
437
|
-
* @param
|
|
438
|
-
*/
|
|
439
|
-
image(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
431
|
+
* Add image from raw bitmap using ZPL ^GFA (Graphic Field) command.
|
|
432
|
+
* The bitmap should be 1-bit (monochrome) data where each bit represents a pixel.
|
|
433
|
+
* @param x - X position in dots
|
|
434
|
+
* @param y - Y position in dots
|
|
435
|
+
* @param width - Image width in pixels
|
|
436
|
+
* @param height - Image height in pixels
|
|
437
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
438
|
+
*/
|
|
439
|
+
image(x: number, y: number, width: number, height: number, bitmap: Uint8Array): this {
|
|
440
|
+
const bytesPerRow = Math.ceil(width / 8);
|
|
441
|
+
const totalBytes = bytesPerRow * height;
|
|
442
|
+
|
|
443
|
+
// Validate bitmap size
|
|
444
|
+
if (bitmap.length < totalBytes) {
|
|
445
|
+
this.logger.warn(
|
|
446
|
+
`ZPL image bitmap size mismatch: expected ${totalBytes}, got ${bitmap.length}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Convert bitmap bytes to hex string (uppercase)
|
|
451
|
+
const hexData: string[] = [];
|
|
452
|
+
const limit = Math.min(bitmap.length, totalBytes);
|
|
453
|
+
for (let i = 0; i < limit; i++) {
|
|
454
|
+
const byte = bitmap[i]!;
|
|
455
|
+
hexData.push(byte.toString(16).padStart(2, '0').toUpperCase());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.commands.push(`^FO${x},${y}`);
|
|
459
|
+
this.commands.push(`^GFA,${totalBytes},${totalBytes},${bytesPerRow},${hexData.join('')}`);
|
|
460
|
+
this.commands.push('^FS');
|
|
461
|
+
this.logger.debug(`ZPL image encoded: ${width}x${height} at (${x},${y})`);
|
|
443
462
|
return this;
|
|
444
463
|
}
|
|
445
464
|
|
package/src/queue/PrintQueue.ts
CHANGED
|
@@ -149,6 +149,7 @@ export class PrintQueue implements IPrintQueue {
|
|
|
149
149
|
private activeJobs = 0;
|
|
150
150
|
private jobCounter = 0;
|
|
151
151
|
private executor: JobExecutor | null = null;
|
|
152
|
+
private retryTimerId: ReturnType<typeof setTimeout> | null = null;
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
155
|
* Creates a new PrintQueue instance
|
|
@@ -257,6 +258,12 @@ export class PrintQueue implements IPrintQueue {
|
|
|
257
258
|
* Clear all pending jobs
|
|
258
259
|
*/
|
|
259
260
|
clear(): void {
|
|
261
|
+
// Cancel pending retry timer if one is active
|
|
262
|
+
if (this.retryTimerId !== null) {
|
|
263
|
+
clearTimeout(this.retryTimerId);
|
|
264
|
+
this.retryTimerId = null;
|
|
265
|
+
}
|
|
266
|
+
|
|
260
267
|
const pendingJobs = [...this.pendingQueue];
|
|
261
268
|
for (const jobId of pendingJobs) {
|
|
262
269
|
const job = this.jobs.get(jobId);
|
|
@@ -430,13 +437,14 @@ export class PrintQueue implements IPrintQueue {
|
|
|
430
437
|
job.status = PrintJobStatus.PENDING;
|
|
431
438
|
|
|
432
439
|
// Re-add to queue after delay
|
|
433
|
-
setTimeout(() => {
|
|
440
|
+
this.retryTimerId = setTimeout(() => {
|
|
434
441
|
if (job.status === PrintJobStatus.PENDING) {
|
|
435
442
|
this.insertByPriority(job.id, job.priority);
|
|
436
443
|
if (this.config.autoProcess && !this.isPaused) {
|
|
437
444
|
this.processQueue();
|
|
438
445
|
}
|
|
439
446
|
}
|
|
447
|
+
this.retryTimerId = null;
|
|
440
448
|
}, this.config.retryDelay);
|
|
441
449
|
} else {
|
|
442
450
|
job.status = PrintJobStatus.FAILED;
|
|
@@ -144,8 +144,9 @@ export class ConnectionManager
|
|
|
144
144
|
this.emit('state-change', PrinterState.CONNECTING);
|
|
145
145
|
|
|
146
146
|
const connectPromise = this.adapter.connect(deviceId);
|
|
147
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
147
148
|
const timeoutPromise = new Promise<void>((_, reject) => {
|
|
148
|
-
setTimeout(() => {
|
|
149
|
+
timeoutId = setTimeout(() => {
|
|
149
150
|
reject(
|
|
150
151
|
new BluetoothPrintError(
|
|
151
152
|
ErrorCode.CONNECTION_TIMEOUT,
|
|
@@ -156,6 +157,9 @@ export class ConnectionManager
|
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
await Promise.race([connectPromise, timeoutPromise]);
|
|
160
|
+
if (timeoutId !== null) {
|
|
161
|
+
clearTimeout(timeoutId);
|
|
162
|
+
}
|
|
159
163
|
this.state = PrinterState.CONNECTED;
|
|
160
164
|
this.emit('state-change', PrinterState.CONNECTED);
|
|
161
165
|
this.emit('connected', deviceId);
|
|
@@ -304,13 +308,13 @@ export class ConnectionManager
|
|
|
304
308
|
|
|
305
309
|
this.isReconnecting = true;
|
|
306
310
|
this.reconnectAttempts = 0;
|
|
307
|
-
this.attemptReconnect();
|
|
311
|
+
void this.attemptReconnect();
|
|
308
312
|
}
|
|
309
313
|
|
|
310
314
|
/**
|
|
311
315
|
* Attempt to reconnect
|
|
312
316
|
*/
|
|
313
|
-
private attemptReconnect(): void {
|
|
317
|
+
private async attemptReconnect(): Promise<void> {
|
|
314
318
|
if (!this.deviceId) {
|
|
315
319
|
this.isReconnecting = false;
|
|
316
320
|
return;
|
|
@@ -345,27 +349,25 @@ export class ConnectionManager
|
|
|
345
349
|
this.state = PrinterState.CONNECTING;
|
|
346
350
|
this.emit('state-change', PrinterState.CONNECTING);
|
|
347
351
|
|
|
348
|
-
|
|
349
|
-
.connect(deviceId)
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.emit('reconnected', deviceId);
|
|
352
|
+
try {
|
|
353
|
+
await this.adapter.connect(deviceId);
|
|
354
|
+
this.connLogger.info('Reconnected successfully');
|
|
355
|
+
this.isReconnecting = false;
|
|
356
|
+
this.reconnectAttempts = 0;
|
|
357
|
+
this.state = PrinterState.CONNECTED;
|
|
358
|
+
this.emit('state-change', PrinterState.CONNECTED);
|
|
359
|
+
this.emit('reconnected', deviceId);
|
|
357
360
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
.
|
|
363
|
-
this.connLogger.warn(`Reconnect attempt ${this.reconnectAttempts} failed:`, error);
|
|
361
|
+
if (this.config.heartbeatEnabled) {
|
|
362
|
+
this.startHeartbeat();
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
this.connLogger.warn(`Reconnect attempt ${this.reconnectAttempts} failed:`, error);
|
|
364
366
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
this.reconnectTimer = setTimeout(() => {
|
|
368
|
+
void this.attemptReconnect();
|
|
369
|
+
}, this.config.reconnectInterval);
|
|
370
|
+
}
|
|
369
371
|
}
|
|
370
372
|
|
|
371
373
|
/**
|
|
@@ -107,14 +107,18 @@ export class PrinterStatus {
|
|
|
107
107
|
await writeFunc(queryCmd.buffer);
|
|
108
108
|
|
|
109
109
|
// Set up timeout promise
|
|
110
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
110
111
|
const timeoutPromise = new Promise<ArrayBuffer>((_, reject) => {
|
|
111
|
-
setTimeout(() => {
|
|
112
|
+
timeoutId = setTimeout(() => {
|
|
112
113
|
reject(new BluetoothPrintError(ErrorCode.CONNECTION_TIMEOUT, 'Status query timed out'));
|
|
113
114
|
}, timeout);
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
// Read response with timeout
|
|
117
118
|
const response = await Promise.race([readFunc(), timeoutPromise]);
|
|
119
|
+
if (timeoutId !== null) {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
}
|
|
118
122
|
|
|
119
123
|
return this.parseStatus(new Uint8Array(response), includeRaw);
|
|
120
124
|
} catch (error) {
|
|
@@ -55,4 +55,10 @@ export interface IConnectionManager {
|
|
|
55
55
|
* @returns IPrinterAdapter - Printer adapter
|
|
56
56
|
*/
|
|
57
57
|
getAdapter(): IPrinterAdapter;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Cleanup resources and destroy the connection manager
|
|
61
|
+
* Stops heartbeat, clears timers, and removes event listeners
|
|
62
|
+
*/
|
|
63
|
+
destroy(): void;
|
|
58
64
|
}
|
package/src/types.ts
CHANGED
|
@@ -75,6 +75,12 @@ export interface IPrinterAdapter {
|
|
|
75
75
|
* @param callback - Function to call when the state changes
|
|
76
76
|
*/
|
|
77
77
|
onStateChange?(callback: (state: PrinterState) => void): void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Cleanup resources and destroy the adapter instance
|
|
81
|
+
* Removes all event listeners and releases resources
|
|
82
|
+
*/
|
|
83
|
+
destroy?(): void;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|