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.
@@ -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 (store in memory)
268
- * Note: This is a placeholder for future implementation
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 _bitmap - Logo bitmap data (placeholder)
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, _bitmap: Uint8Array): this;
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
- * Note: For best results, use pre-processed images. This is a placeholder for future implementation.
272
- * @param _x - X position (placeholder)
273
- * @param _y - Y position (placeholder)
274
- * @param _width - Image width (placeholder)
275
- * @param _height - Image height (placeholder)
276
- * @param _bitmap - Binary bitmap data (placeholder)
277
- */
278
- image(_x: number, _y: number, _width: number, _height: number, _bitmap: Uint8Array): this;
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)
@@ -127,6 +127,7 @@ export declare class PrintQueue implements IPrintQueue {
127
127
  private activeJobs;
128
128
  private jobCounter;
129
129
  private executor;
130
+ private retryTimerId;
130
131
  /**
131
132
  * Creates a new PrintQueue instance
132
133
  */
@@ -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
  }
@@ -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",
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
- if ('readRemoteRssi' in characteristic.service.device) {
214
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment */
215
- rssi = await (characteristic.service.device as any).readRemoteRssi();
216
- /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment */
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
- // Safe fallbacks for mock objects in tests
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 (store in memory)
482
- * Note: This is a placeholder for future implementation
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 _bitmap - Logo bitmap data (placeholder)
485
- */
486
- downloadLogo(logoName: string, _bitmap: Uint8Array): this {
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
- // TODO: Encode bitmap to CPCL format
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
 
@@ -428,18 +428,37 @@ export class ZplDriver {
428
428
  }
429
429
 
430
430
  /**
431
- * Add image from raw bitmap
432
- * Note: For best results, use pre-processed images. This is a placeholder for future implementation.
433
- * @param _x - X position (placeholder)
434
- * @param _y - Y position (placeholder)
435
- * @param _width - Image width (placeholder)
436
- * @param _height - Image height (placeholder)
437
- * @param _bitmap - Binary bitmap data (placeholder)
438
- */
439
- image(_x: number, _y: number, _width: number, _height: number, _bitmap: Uint8Array): this {
440
- // TODO: Implement proper ZPL image encoding using ^GFA or ^XG commands
441
- // For now, this is a placeholder
442
- this.logger.debug('ZPL image encoding not fully implemented');
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
 
@@ -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
- this.adapter
349
- .connect(deviceId)
350
- .then(() => {
351
- this.connLogger.info('Reconnected successfully');
352
- this.isReconnecting = false;
353
- this.reconnectAttempts = 0;
354
- this.state = PrinterState.CONNECTED;
355
- this.emit('state-change', PrinterState.CONNECTED);
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
- if (this.config.heartbeatEnabled) {
359
- this.startHeartbeat();
360
- }
361
- })
362
- .catch(error => {
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
- this.reconnectTimer = setTimeout(() => {
366
- this.attemptReconnect();
367
- }, this.config.reconnectInterval);
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
  /**