taro-bluetooth-print 2.9.4 → 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.
@@ -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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taro-bluetooth-print",
3
- "version": "2.9.4",
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
  }
@@ -218,8 +218,11 @@ export class WebBluetoothAdapter extends BaseAdapter {
218
218
  // Get RSSI if available (may not be available on all devices)
219
219
  let rssi: number | undefined;
220
220
  try {
221
- const deviceWithRssi = characteristic.service.device as BluetoothDeviceWithRssi;
222
- if ('readRemoteRssi' in deviceWithRssi && typeof deviceWithRssi.readRemoteRssi === 'function') {
221
+ const deviceWithRssi = characteristic.service.device as unknown as BluetoothDeviceWithRssi;
222
+ if (
223
+ 'readRemoteRssi' in deviceWithRssi &&
224
+ typeof deviceWithRssi.readRemoteRssi === 'function'
225
+ ) {
223
226
  rssi = await deviceWithRssi.readRemoteRssi();
224
227
  }
225
228
  } catch {
@@ -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,7 +308,7 @@ 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
  /**
@@ -361,7 +365,7 @@ export class ConnectionManager
361
365
  this.connLogger.warn(`Reconnect attempt ${this.reconnectAttempts} failed:`, error);
362
366
 
363
367
  this.reconnectTimer = setTimeout(() => {
364
- this.attemptReconnect();
368
+ void this.attemptReconnect();
365
369
  }, this.config.reconnectInterval);
366
370
  }
367
371
  }
@@ -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) {