taro-bluetooth-print 2.10.2 → 2.11.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.
@@ -75,6 +75,18 @@ export declare abstract class ChunkWriteStrategy<TOptions = void> {
75
75
  * Default: no-op. Override for platforms that support connection state polling.
76
76
  */
77
77
  protected checkConnection(_deviceId: string): Promise<void>;
78
+ /** Step size for increasing chunk size on success (bytes) */
79
+ private static readonly CHUNK_SIZE_STEP;
80
+ /** Delay backoff multiplier on failure */
81
+ private static readonly DELAY_BACKOFF_FACTOR;
82
+ /** Delay recovery divisor on success */
83
+ private static readonly DELAY_RECOVERY_FACTOR;
84
+ /** Timeout base (ms) for computeTimeoutMs */
85
+ private static readonly TIMEOUT_BASE_MS;
86
+ /** Timeout per-byte factor (ms) for computeTimeoutMs */
87
+ private static readonly TIMEOUT_PER_BYTE_MS;
88
+ /** Maximum timeout (ms) for computeTimeoutMs */
89
+ private static readonly TIMEOUT_MAX_MS;
78
90
  /**
79
91
  * Compute chunk write timeout based on chunk size.
80
92
  * Override to customize timeout formula per platform.
@@ -46,7 +46,7 @@ export declare class ReactNativeAdapter extends BaseAdapter implements IPrinterA
46
46
  *
47
47
  * @param options - Configuration options
48
48
  * @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
49
- * @throws {Error} If bleManager is not provided or not supported
49
+ * @throws {BluetoothPrintError} If bleManager is not provided or not supported
50
50
  */
51
51
  constructor(options: {
52
52
  bleManager: BLEManager;
@@ -18,7 +18,10 @@ export declare enum ErrorCode {
18
18
  ENCODING_NOT_SUPPORTED = "ENCODING_NOT_SUPPORTED",
19
19
  INVALID_IMAGE_DATA = "INVALID_IMAGE_DATA",
20
20
  INVALID_QR_DATA = "INVALID_QR_DATA",
21
- PLATFORM_NOT_SUPPORTED = "PLATFORM_NOT_SUPPORTED"
21
+ PLATFORM_NOT_SUPPORTED = "PLATFORM_NOT_SUPPORTED",
22
+ QUEUE_FULL = "QUEUE_FULL",
23
+ QUEUE_JOB_NOT_FOUND = "QUEUE_JOB_NOT_FOUND",
24
+ PREVIEW_FAILED = "PREVIEW_FAILED"
22
25
  }
23
26
  /**
24
27
  * Custom error class for Bluetooth printing operations
@@ -73,6 +73,8 @@ export declare class PrintHistory {
73
73
  private readonly logger;
74
74
  private readonly entries;
75
75
  private counter;
76
+ /** Default maximum number of history entries */
77
+ private static readonly DEFAULT_MAX_ENTRIES;
76
78
  private readonly maxEntries;
77
79
  /**
78
80
  * Creates a new PrintHistory instance
@@ -268,6 +268,8 @@ export declare class TemplateEngine implements ITemplateEngine {
268
268
  private readonly parser;
269
269
  private readonly renderer;
270
270
  private readonly templates;
271
+ /** Default paper width in characters (58mm paper ≈ 48 chars) */
272
+ private static readonly DEFAULT_PAPER_WIDTH;
271
273
  /**
272
274
  * Creates a new TemplateEngine instance
273
275
  */
@@ -11,6 +11,8 @@ export declare class TemplateRenderer {
11
11
  private readonly driver;
12
12
  private readonly parser;
13
13
  private readonly paperWidth;
14
+ /** Default paper width in characters (58mm paper ≈ 48 chars) */
15
+ private static readonly DEFAULT_PAPER_WIDTH;
14
16
  constructor(paperWidth?: number);
15
17
  /**
16
18
  * Render a receipt template
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taro-bluetooth-print",
3
- "version": "2.10.2",
3
+ "version": "2.11.0",
4
4
  "description": "Taro 蓝牙打印库 v2.6 - 轻量级、高性能、跨平台支持微信、支付宝、百度、字节跳动小程序及H5 Web Bluetooth",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -116,12 +116,31 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
116
116
  // default: no connection check
117
117
  }
118
118
 
119
+ /** Step size for increasing chunk size on success (bytes) */
120
+ private static readonly CHUNK_SIZE_STEP = 5;
121
+ /** Delay backoff multiplier on failure */
122
+ private static readonly DELAY_BACKOFF_FACTOR = 1.5;
123
+ /** Delay recovery divisor on success */
124
+ private static readonly DELAY_RECOVERY_FACTOR = 1.2;
125
+ /** Timeout base (ms) for computeTimeoutMs */
126
+ private static readonly TIMEOUT_BASE_MS = 1000;
127
+ /** Timeout per-byte factor (ms) for computeTimeoutMs */
128
+ private static readonly TIMEOUT_PER_BYTE_MS = 5;
129
+ /** Maximum timeout (ms) for computeTimeoutMs */
130
+ private static readonly TIMEOUT_MAX_MS = 10000;
131
+
119
132
  /**
120
133
  * Compute chunk write timeout based on chunk size.
121
134
  * Override to customize timeout formula per platform.
122
135
  */
123
136
  protected computeTimeoutMs(chunkLength: number): number {
124
- return Math.max(1000, Math.min(10000, 1000 + chunkLength * 5));
137
+ return Math.max(
138
+ ChunkWriteStrategy.TIMEOUT_BASE_MS,
139
+ Math.min(
140
+ ChunkWriteStrategy.TIMEOUT_MAX_MS,
141
+ ChunkWriteStrategy.TIMEOUT_BASE_MS + chunkLength * ChunkWriteStrategy.TIMEOUT_PER_BYTE_MS
142
+ )
143
+ );
125
144
  }
126
145
 
127
146
  /**
@@ -218,8 +237,11 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
218
237
  consecutiveFailures = 0;
219
238
 
220
239
  if (successCount % successThreshold === 0 && currentChunkSize < maxChunkSize) {
221
- currentChunkSize = Math.min(maxChunkSize, currentChunkSize + 5);
222
- baseDelay = Math.max(baseDelay / 1.2, delay);
240
+ currentChunkSize = Math.min(
241
+ maxChunkSize,
242
+ currentChunkSize + ChunkWriteStrategy.CHUNK_SIZE_STEP
243
+ );
244
+ baseDelay = Math.max(baseDelay / ChunkWriteStrategy.DELAY_RECOVERY_FACTOR, delay);
223
245
  totalChunks =
224
246
  Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
225
247
  this.logger.debug(`Increased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
@@ -228,8 +250,11 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
228
250
  consecutiveFailures++;
229
251
 
230
252
  if (consecutiveFailures >= failureThreshold && currentChunkSize > minChunkSize) {
231
- currentChunkSize = Math.max(minChunkSize, currentChunkSize - 5);
232
- baseDelay = Math.min(baseDelay * 1.5, maxDelay);
253
+ currentChunkSize = Math.max(
254
+ minChunkSize,
255
+ currentChunkSize - ChunkWriteStrategy.CHUNK_SIZE_STEP
256
+ );
257
+ baseDelay = Math.min(baseDelay * ChunkWriteStrategy.DELAY_BACKOFF_FACTOR, maxDelay);
233
258
  totalChunks =
234
259
  Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
235
260
  this.logger.debug(`Decreased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
@@ -193,13 +193,14 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
193
193
  *
194
194
  * @param options - Configuration options
195
195
  * @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
196
- * @throws {Error} If bleManager is not provided or not supported
196
+ * @throws {BluetoothPrintError} If bleManager is not provided or not supported
197
197
  */
198
198
  constructor(options: { bleManager: BLEManager }) {
199
199
  super();
200
200
 
201
201
  if (!options?.bleManager) {
202
- throw new Error(
202
+ throw new BluetoothPrintError(
203
+ ErrorCode.INVALID_CONFIGURATION,
203
204
  'ReactNativeAdapter requires a bleManager instance (e.g., react-native-ble-plx). ' +
204
205
  'Please pass { bleManager: yourBleManager } in the constructor.'
205
206
  );
@@ -29,6 +29,7 @@
29
29
  */
30
30
 
31
31
  import { Logger } from '@/utils/logger';
32
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
32
33
 
33
34
  /**
34
35
  * Print configuration for a device
@@ -338,7 +339,7 @@ export class PrinterConfigManager {
338
339
  setDefaultPrinter(id: string): void {
339
340
  const printer = this.printers.get(id);
340
341
  if (!printer) {
341
- throw new Error(`Printer not found: ${id}`);
342
+ throw new BluetoothPrintError(ErrorCode.DEVICE_NOT_FOUND, `Printer not found: ${id}`);
342
343
  }
343
344
 
344
345
  // Unset other defaults
@@ -13,6 +13,8 @@
13
13
  */
14
14
 
15
15
  import Taro from '@tarojs/taro';
16
+
17
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
16
18
  import { Logger } from '@/utils/logger';
17
19
  import { emitAndThrow } from '@/utils/normalizeError';
18
20
  import { EventEmitter } from '@/core/EventEmitter';
@@ -214,7 +216,7 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> implements
214
216
  const services = servicesRes.services;
215
217
 
216
218
  if (services.length === 0) {
217
- throw new Error('No services found on device');
219
+ throw new BluetoothPrintError(ErrorCode.SERVICE_NOT_FOUND, 'No services found on device');
218
220
  }
219
221
 
220
222
  let serviceId: string | undefined;
@@ -238,7 +240,10 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> implements
238
240
  }
239
241
 
240
242
  if (!serviceId || !characteristicId) {
241
- throw new Error('No writable characteristic found');
243
+ throw new BluetoothPrintError(
244
+ ErrorCode.CHARACTERISTIC_NOT_FOUND,
245
+ 'No writable characteristic found'
246
+ );
242
247
  }
243
248
 
244
249
  const device = this.discoveredDevices.get(deviceId);
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
2
1
  /**
3
2
  * @fileoverview DiscoveryService uses dynamic adapter loading for platform-specific Bluetooth operations.
4
3
  * The platformAdapter field is typed as `BaseAdapter | undefined` because different adapters
@@ -216,9 +215,6 @@ export class DiscoveryService extends EventEmitter<DiscoveryEvents> {
216
215
  }
217
216
  }
218
217
 
219
- // 监听 platform adapter 的设备发现回调(预留扩展点)
220
- // private processDevice(deviceInfo: any): void { ... }
221
-
222
218
  /**
223
219
  * 获取过滤和排序后的设备列表
224
220
  */
@@ -32,6 +32,13 @@ export enum ErrorCode {
32
32
 
33
33
  // Platform errors
34
34
  PLATFORM_NOT_SUPPORTED = 'PLATFORM_NOT_SUPPORTED',
35
+
36
+ // Queue errors
37
+ QUEUE_FULL = 'QUEUE_FULL',
38
+ QUEUE_JOB_NOT_FOUND = 'QUEUE_JOB_NOT_FOUND',
39
+
40
+ // Preview errors
41
+ PREVIEW_FAILED = 'PREVIEW_FAILED',
35
42
  }
36
43
 
37
44
  /**
@@ -12,6 +12,7 @@ import { PrintJobManager } from '@/services/PrintJobManager';
12
12
  import { CommandBuilder } from '@/services/CommandBuilder';
13
13
  import type { IPrinterAdapter } from '@/types';
14
14
  import type { IConnectionManager, IPrintJobManager, ICommandBuilder } from '@/services/interfaces';
15
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
15
16
 
16
17
  /**
17
18
  * Options for creating a BluetoothPrinter via the factory
@@ -127,7 +128,8 @@ export async function createWebBluetoothPrinter(): Promise<BluetoothPrinter> {
127
128
  const adapter = new WebBluetoothAdapter();
128
129
  return createBluetoothPrinter({ adapter });
129
130
  } catch {
130
- throw new Error(
131
+ throw new BluetoothPrintError(
132
+ ErrorCode.PLATFORM_NOT_SUPPORTED,
131
133
  'Failed to dynamically import WebBluetoothAdapter. ' +
132
134
  'This adapter is only available in browser environments that support the Web Bluetooth API.'
133
135
  );
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import { Logger } from '@/utils/logger';
16
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
16
17
 
17
18
  /**
18
19
  * Preview options
@@ -141,7 +142,7 @@ export class PreviewRenderer implements IPreviewRenderer {
141
142
 
142
143
  const ctx = canvas.getContext('2d');
143
144
  if (!ctx) {
144
- throw new Error('Failed to get canvas context');
145
+ throw new BluetoothPrintError(ErrorCode.PREVIEW_FAILED, 'Failed to get canvas context');
145
146
  }
146
147
 
147
148
  this.drawToContext(ctx, lines, pixelWidth, estimatedHeight, opts);
@@ -415,7 +416,7 @@ export class PreviewRenderer implements IPreviewRenderer {
415
416
 
416
417
  const ctx = canvas.getContext('2d');
417
418
  if (!ctx) {
418
- throw new Error('Failed to get canvas context');
419
+ throw new BluetoothPrintError(ErrorCode.PREVIEW_FAILED, 'Failed to get canvas context');
419
420
  }
420
421
 
421
422
  this.drawToContext(ctx, lines, width, height, opts);
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import { Logger } from '@/utils/logger';
16
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
16
17
  import { normalizeError } from '@/utils/normalizeError';
17
18
  import { EventEmitter } from '@/core/EventEmitter';
18
19
 
@@ -170,7 +171,7 @@ export class PrintQueue extends EventEmitter<PrintQueueEvents> implements IPrint
170
171
  */
171
172
  add(data: Uint8Array, options?: Partial<PrintJob>): string {
172
173
  if (this.jobs.size >= this.config.maxSize) {
173
- throw new Error('Queue is full');
174
+ throw new BluetoothPrintError(ErrorCode.QUEUE_FULL, 'Queue is full');
174
175
  }
175
176
 
176
177
  const id = this.generateJobId();
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { EventEmitter } from '@/core/EventEmitter';
14
14
  import { Logger } from '@/utils/logger';
15
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
15
16
  import { normalizeError } from '@/utils/normalizeError';
16
17
 
17
18
  export interface CloudPrintOptions {
@@ -233,7 +234,7 @@ export class CloudPrintManager extends EventEmitter<CloudPrintEvents> {
233
234
  */
234
235
  print(job: PrintJob): void {
235
236
  if (!this.isConnected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
236
- throw new Error('Not connected to server');
237
+ throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Not connected to server');
237
238
  }
238
239
 
239
240
  const message = {
@@ -92,13 +92,16 @@ export class PrintHistory {
92
92
  private readonly logger = Logger.scope('PrintHistory');
93
93
  private readonly entries: Map<string, PrintHistoryEntry> = new Map();
94
94
  private counter = 0;
95
+ /** Default maximum number of history entries */
96
+ private static readonly DEFAULT_MAX_ENTRIES = 1000;
97
+
95
98
  private readonly maxEntries: number;
96
99
 
97
100
  /**
98
101
  * Creates a new PrintHistory instance
99
102
  * @param maxEntries - Maximum number of entries to keep (default: 1000)
100
103
  */
101
- constructor(maxEntries = 1000) {
104
+ constructor(maxEntries = PrintHistory.DEFAULT_MAX_ENTRIES) {
102
105
  this.maxEntries = maxEntries;
103
106
  }
104
107
 
@@ -297,7 +297,10 @@ export class PrintJobManager implements IPrintJobManager {
297
297
  `Loaded job ${this.jobId}: offset=${this.jobOffset}/${this.jobBuffer.length}`
298
298
  );
299
299
  } else {
300
- throw new Error(`Job state not found for ${jobId}`);
300
+ throw new BluetoothPrintError(
301
+ ErrorCode.QUEUE_JOB_NOT_FOUND,
302
+ `Job state not found for ${jobId}`
303
+ );
301
304
  }
302
305
  } catch (error) {
303
306
  this.logger.error(`Failed to load job state for ${jobId}:`, error);
@@ -6,6 +6,7 @@
6
6
  import { EventEmitter } from '../core/EventEmitter';
7
7
  import { generateUUID } from '../utils/uuid';
8
8
  import { normalizeError } from '../utils/normalizeError';
9
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
9
10
 
10
11
  export interface ScheduledPrint {
11
12
  id: string;
@@ -57,7 +58,7 @@ export function parseCronExpression(cron: string): {
57
58
  } {
58
59
  const parts = cron.trim().split(/\s+/);
59
60
  if (parts.length !== 5) {
60
- throw new Error('Invalid cron expression');
61
+ throw new BluetoothPrintError(ErrorCode.INVALID_CONFIGURATION, 'Invalid cron expression');
61
62
  }
62
63
 
63
64
  const [minPart, hourPart, dayPart, monthPart, dowPart] = parts as [
@@ -143,7 +144,10 @@ export function getNextCronRun(cron: string, fromTime: number = Date.now()): num
143
144
  date.setMinutes(date.getMinutes() + 1);
144
145
  }
145
146
 
146
- throw new Error('Cannot find next run time within one year');
147
+ throw new BluetoothPrintError(
148
+ ErrorCode.INVALID_CONFIGURATION,
149
+ 'Cannot find next run time within one year'
150
+ );
147
151
  }
148
152
 
149
153
  export function getNextIntervalRun(interval: number, fromTime: number = Date.now()): number {
@@ -173,7 +177,10 @@ export class PrintScheduler extends EventEmitter<ScheduleEvents> {
173
177
  const executeAt = onceAt instanceof Date ? onceAt.getTime() : onceAt;
174
178
 
175
179
  if (!executeAt || executeAt <= Date.now()) {
176
- throw new Error('onceAt must be a future date');
180
+ throw new BluetoothPrintError(
181
+ ErrorCode.INVALID_CONFIGURATION,
182
+ 'onceAt must be a future date'
183
+ );
177
184
  }
178
185
 
179
186
  const job: ScheduledPrint = {
@@ -198,7 +205,10 @@ export class PrintScheduler extends EventEmitter<ScheduleEvents> {
198
205
  const { cronExpression, repeatInterval, ...rest } = options;
199
206
 
200
207
  if (!cronExpression && !repeatInterval) {
201
- throw new Error('Either cronExpression or repeatInterval is required');
208
+ throw new BluetoothPrintError(
209
+ ErrorCode.INVALID_CONFIGURATION,
210
+ 'Either cronExpression or repeatInterval is required'
211
+ );
202
212
  }
203
213
 
204
214
  const job: ScheduledPrint = {
@@ -293,10 +293,13 @@ export class TemplateEngine implements ITemplateEngine {
293
293
  private readonly renderer: TemplateRenderer;
294
294
  private readonly templates: Map<string, TemplateDefinition> = new Map();
295
295
 
296
+ /** Default paper width in characters (58mm paper ≈ 48 chars) */
297
+ private static readonly DEFAULT_PAPER_WIDTH = 48;
298
+
296
299
  /**
297
300
  * Creates a new TemplateEngine instance
298
301
  */
299
- constructor(paperWidth = 48) {
302
+ constructor(paperWidth = TemplateEngine.DEFAULT_PAPER_WIDTH) {
300
303
  this.parser = new TemplateParser();
301
304
  this.renderer = new TemplateRenderer(paperWidth);
302
305
  }
@@ -118,7 +118,10 @@ export class TemplateRenderer {
118
118
  private readonly parser: TemplateParser;
119
119
  private readonly paperWidth: number;
120
120
 
121
- constructor(paperWidth = 48) {
121
+ /** Default paper width in characters (58mm paper 48 chars) */
122
+ private static readonly DEFAULT_PAPER_WIDTH = 48;
123
+
124
+ constructor(paperWidth = TemplateRenderer.DEFAULT_PAPER_WIDTH) {
122
125
  this.paperWidth = paperWidth;
123
126
  this.formatter = new TextFormatter();
124
127
  this.barcodeGenerator = new BarcodeGenerator();
@@ -363,21 +366,16 @@ export class TemplateRenderer {
363
366
  }
364
367
 
365
368
  for (let i = 0; i < items.length; i++) {
366
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
367
- const itemData: any = items[i];
369
+ const itemData: Record<string, unknown> = items[i] as Record<string, unknown>;
368
370
 
369
371
  // Create iteration context with item and optionally index
370
372
  const context: Record<string, unknown> = {
371
373
  ...data,
372
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
373
374
  [loop.itemVar]: itemData,
374
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
375
375
  };
376
376
 
377
377
  if (loop.indexVar) {
378
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
379
378
  context[loop.indexVar] = i;
380
- /* eslint-enable @typescript-eslint/no-unsafe-assignment */
381
379
  }
382
380
 
383
381
  // Render each element in the loop
@@ -5,6 +5,8 @@
5
5
  * Supports multiple dithering algorithms and image preprocessing.
6
6
  */
7
7
 
8
+ import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
9
+
8
10
  export class ImageProcessing {
9
11
  // Pre-computed Bayer matrices for ordered dithering
10
12
  private static readonly BAYER_MATRIX_2: ReadonlyArray<ReadonlyArray<number>> = [
@@ -84,7 +86,8 @@ export class ImageProcessing {
84
86
  }
85
87
 
86
88
  if (data.length !== width * height * 4) {
87
- throw new Error(
89
+ throw new BluetoothPrintError(
90
+ ErrorCode.INVALID_IMAGE_DATA,
88
91
  `Invalid image data length: expected ${width * height * 4}, got ${data.length}`
89
92
  );
90
93
  }
@@ -151,10 +151,13 @@ export async function batchProcess<T, R>(
151
151
  const batchResults = await processor(batch, batchIndex);
152
152
  results.push(...batchResults);
153
153
  } catch (error) {
154
- // Log the error and continue processing subsequent batches
155
- console.error(`[batchProcess] Error processing batch ${batchIndex}/${totalBatches}:`, error);
156
- // Re-throw so the caller can handle the failure
157
- throw error;
154
+ // Log the error and re-throw so the caller can handle the failure
155
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
156
+ console.error(
157
+ `[batchProcess] Error processing batch ${batchIndex}/${totalBatches}:`,
158
+ normalizedError.message
159
+ );
160
+ throw normalizedError;
158
161
  }
159
162
 
160
163
  // 添加小延迟,避免阻塞