taro-bluetooth-print 2.2.0 → 2.3.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 (49) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +128 -22
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.es.js +1 -6995
  5. package/dist/index.umd.js +1 -1
  6. package/dist/types/adapters/AdapterFactory.d.ts +0 -1
  7. package/dist/types/adapters/AlipayAdapter.d.ts +6 -34
  8. package/dist/types/adapters/BaiduAdapter.d.ts +6 -34
  9. package/dist/types/adapters/BaseAdapter.d.ts +112 -1
  10. package/dist/types/adapters/ByteDanceAdapter.d.ts +6 -34
  11. package/dist/types/adapters/TaroAdapter.d.ts +6 -34
  12. package/dist/types/adapters/WebBluetoothAdapter.d.ts +0 -1
  13. package/dist/types/config/PrinterConfig.d.ts +0 -1
  14. package/dist/types/core/BluetoothPrinter.d.ts +0 -1
  15. package/dist/types/drivers/EscPos.d.ts +0 -1
  16. package/dist/types/drivers/TsplDriver.d.ts +251 -0
  17. package/dist/types/encoding/gbk-data.d.ts +12 -0
  18. package/dist/types/encoding/gbk-table.d.ts +5 -1
  19. package/dist/types/index.d.ts +5 -0
  20. package/dist/types/plugins/PluginManager.d.ts +87 -0
  21. package/dist/types/plugins/builtin/LoggingPlugin.d.ts +14 -0
  22. package/dist/types/plugins/builtin/RetryPlugin.d.ts +18 -0
  23. package/dist/types/plugins/index.d.ts +7 -0
  24. package/dist/types/plugins/types.d.ts +97 -0
  25. package/dist/types/services/CommandBuilder.d.ts +6 -1
  26. package/dist/types/services/ConnectionManager.d.ts +0 -1
  27. package/dist/types/services/PrintJobManager.d.ts +6 -2
  28. package/dist/types/services/interfaces/index.d.ts +0 -1
  29. package/dist/types/template/TemplateEngine.d.ts +0 -1
  30. package/package.json +16 -18
  31. package/src/adapters/AlipayAdapter.ts +8 -314
  32. package/src/adapters/BaiduAdapter.ts +8 -312
  33. package/src/adapters/BaseAdapter.ts +366 -0
  34. package/src/adapters/ByteDanceAdapter.ts +8 -316
  35. package/src/adapters/TaroAdapter.ts +8 -367
  36. package/src/core/EventEmitter.ts +9 -6
  37. package/src/drivers/TsplDriver.ts +417 -0
  38. package/src/encoding/gbk-data.ts +1911 -0
  39. package/src/encoding/gbk-table.ts +22 -498
  40. package/src/index.ts +14 -0
  41. package/src/plugins/PluginManager.ts +193 -0
  42. package/src/plugins/builtin/LoggingPlugin.ts +97 -0
  43. package/src/plugins/builtin/RetryPlugin.ts +109 -0
  44. package/src/plugins/index.ts +10 -0
  45. package/src/plugins/types.ts +119 -0
  46. package/src/preview/PreviewRenderer.ts +7 -1
  47. package/src/queue/PrintQueue.ts +10 -6
  48. package/src/services/CommandBuilder.ts +30 -0
  49. package/src/services/PrintJobManager.ts +51 -35
@@ -3,73 +3,19 @@
3
3
  * Implements the IPrinterAdapter interface for Taro framework
4
4
  */
5
5
 
6
- import { IAdapterOptions, PrinterState } from '@/types';
7
- import { BaseAdapter } from './BaseAdapter';
8
- import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
6
+ import { MiniProgramAdapter, MiniProgramBLEApi } from './BaseAdapter';
9
7
 
10
8
  // Declare Taro global for TypeScript
11
- interface TaroBLEService {
12
- uuid: string;
13
- }
14
-
15
- interface TaroBLECharacteristicProperties {
16
- write?: boolean;
17
- writeWithoutResponse?: boolean;
18
- read?: boolean;
19
- notify?: boolean;
20
- indicate?: boolean;
21
- }
22
-
23
- interface TaroBLECharacteristic {
24
- uuid: string;
25
- properties: TaroBLECharacteristicProperties;
26
- }
27
-
28
- interface TaroBLEDeviceServicesResult {
29
- services: TaroBLEService[];
30
- }
31
-
32
- interface TaroBLEDeviceCharacteristicsResult {
33
- characteristics: TaroBLECharacteristic[];
34
- }
35
-
36
- interface TaroBLEConnectionStateResult {
37
- connected: boolean;
38
- }
39
-
40
- interface TaroBLEConnectionOptions {
41
- deviceId: string;
42
- }
43
-
44
- interface TaroBLEWriteOptions {
45
- deviceId: string;
46
- serviceId: string;
47
- characteristicId: string;
48
- value: ArrayBuffer;
49
- }
50
-
51
- interface TaroBLEConnectionStateChangeCallback {
52
- (res: { deviceId: string; connected: boolean }): void;
53
- }
54
-
55
- interface TaroGlobal {
56
- createBLEConnection(options: TaroBLEConnectionOptions): Promise<void>;
57
- closeBLEConnection(options: TaroBLEConnectionOptions): Promise<void>;
58
- getBLEConnectionState(options: TaroBLEConnectionOptions): Promise<TaroBLEConnectionStateResult>;
59
- writeBLECharacteristicValue(options: TaroBLEWriteOptions): Promise<void>;
60
- getBLEDeviceServices(options: TaroBLEConnectionOptions): Promise<TaroBLEDeviceServicesResult>;
61
- getBLEDeviceCharacteristics(options: {
62
- deviceId: string;
63
- serviceId: string;
64
- }): Promise<TaroBLEDeviceCharacteristicsResult>;
65
- onBLEConnectionStateChange(callback: TaroBLEConnectionStateChangeCallback): void;
66
- }
9
+ interface TaroGlobal extends MiniProgramBLEApi {}
67
10
 
68
11
  declare const Taro: TaroGlobal;
69
12
 
70
13
  /**
71
14
  * Taro Bluetooth Low Energy adapter
72
15
  *
16
+ * Uses the Taro framework's BLE APIs (compatible with WeChat mini-program).
17
+ * All connection, write, and service discovery logic is inherited from MiniProgramAdapter.
18
+ *
73
19
  * @example
74
20
  * ```typescript
75
21
  * const adapter = new TaroAdapter();
@@ -78,313 +24,8 @@ declare const Taro: TaroGlobal;
78
24
  * await adapter.disconnect('device-id-123');
79
25
  * ```
80
26
  */
81
- export class TaroAdapter extends BaseAdapter {
82
- /**
83
- * Connects to a Bluetooth device and discovers services
84
- *
85
- * @param deviceId - Bluetooth device ID
86
- * @throws {BluetoothPrintError} When connection fails
87
- */
88
- async connect(deviceId: string): Promise<void> {
89
- this.validateDeviceId(deviceId);
90
-
91
- // 检查是否已连接
92
- if (this.isDeviceConnected(deviceId)) {
93
- this.logger.warn('Device already connected:', deviceId);
94
- this.updateState(PrinterState.CONNECTED);
95
- return;
96
- }
97
-
98
- this.updateState(PrinterState.CONNECTING);
99
- this.logger.debug('Connecting to device:', deviceId);
100
-
101
- try {
102
- // 添加连接超时处理
103
- let timeoutId: NodeJS.Timeout | undefined;
104
- const connectionPromise = Taro.createBLEConnection({ deviceId });
105
- const timeoutPromise = new Promise((_, reject) => {
106
- timeoutId = setTimeout(() => {
107
- reject(new Error('Connection timeout after 10 seconds'));
108
- }, 10000);
109
- });
110
-
111
- await Promise.race([connectionPromise, timeoutPromise]);
112
- if (timeoutId) {
113
- clearTimeout(timeoutId); // Clear timeout after successful connection
114
- }
115
- this.logger.info('BLE connection established');
116
-
117
- // Discover and cache services
118
- await this.discoverServices(deviceId);
119
-
120
- this.updateState(PrinterState.CONNECTED);
121
- this.logger.info('Device connected successfully');
122
-
123
- // Listen for connection state changes
124
- Taro.onBLEConnectionStateChange((res: { deviceId: string; connected: boolean }) => {
125
- if (res.deviceId === deviceId && !res.connected) {
126
- this.logger.warn('Device disconnected unexpectedly');
127
- this.updateState(PrinterState.DISCONNECTED);
128
- this.cleanupDevice(deviceId);
129
- }
130
- });
131
- } catch (error) {
132
- this.updateState(PrinterState.DISCONNECTED);
133
- this.logger.error('Connection failed:', error);
134
-
135
- // 根据错误类型返回更具体的错误代码
136
- const errorMessage = (error as Error).message || '';
137
- if (errorMessage.includes('timeout')) {
138
- throw new BluetoothPrintError(
139
- ErrorCode.CONNECTION_TIMEOUT,
140
- `Connection to device ${deviceId} timed out`,
141
- error as Error
142
- );
143
- } else if (errorMessage.includes('not found')) {
144
- throw new BluetoothPrintError(
145
- ErrorCode.DEVICE_NOT_FOUND,
146
- `Device ${deviceId} not found`,
147
- error as Error
148
- );
149
- }
150
-
151
- throw new BluetoothPrintError(
152
- ErrorCode.CONNECTION_FAILED,
153
- `Failed to connect to device ${deviceId}`,
154
- error as Error
155
- );
156
- }
157
- }
158
-
159
- /**
160
- * Disconnects from a Bluetooth device
161
- *
162
- * @param deviceId - Bluetooth device ID
163
- */
164
- async disconnect(deviceId: string): Promise<void> {
165
- this.validateDeviceId(deviceId);
166
- this.updateState(PrinterState.DISCONNECTING);
167
- this.logger.debug('Disconnecting from device:', deviceId);
168
-
169
- try {
170
- await Taro.closeBLEConnection({ deviceId });
171
- this.cleanupDevice(deviceId);
172
- this.updateState(PrinterState.DISCONNECTED);
173
- this.logger.info('Device disconnected successfully');
174
- } catch (error) {
175
- // Ignore error on disconnect, but log it
176
- this.logger.warn('Disconnect error (ignored):', error);
177
- this.cleanupDevice(deviceId);
178
- this.updateState(PrinterState.DISCONNECTED);
179
- }
180
- }
181
-
182
- /**
183
- * Writes data to the Bluetooth device in chunks with retry logic
184
- *
185
- * @param deviceId - Bluetooth device ID
186
- * @param buffer - Data to write as ArrayBuffer
187
- * @param options - Write options (chunk size, delay, retries)
188
- * @throws {BluetoothPrintError} When write fails after retries
189
- */
190
- async write(deviceId: string, buffer: ArrayBuffer, options?: IAdapterOptions): Promise<void> {
191
- this.validateDeviceId(deviceId);
192
- this.validateBuffer(buffer);
193
- const serviceInfo = this.getServiceInfo(deviceId);
194
- const validatedOptions = this.validateOptions(options);
195
-
196
- // 验证设备是否仍处于连接状态
197
- try {
198
- const state = await Taro.getBLEConnectionState({ deviceId });
199
- if (!state.connected) {
200
- this.cleanupDevice(deviceId);
201
- throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Device disconnected');
202
- }
203
- } catch (error) {
204
- this.cleanupDevice(deviceId);
205
- throw new BluetoothPrintError(
206
- ErrorCode.DEVICE_DISCONNECTED,
207
- 'Device disconnected',
208
- error as Error
209
- );
210
- }
211
-
212
- let { chunkSize } = validatedOptions;
213
- const { delay, retries } = validatedOptions;
214
- const data = new Uint8Array(buffer);
215
- let totalChunks = Math.ceil(data.length / chunkSize);
216
-
217
- this.logger.debug(`Writing ${data.length} bytes in ${totalChunks} chunks`);
218
-
219
- // 如果没有数据要写入,直接返回
220
- if (data.length === 0) {
221
- this.logger.warn('No data to write');
222
- return;
223
- }
224
-
225
- // 网络状况监控变量
226
- let successCount = 0;
227
- let failureCount = 0;
228
- let consecutiveFailures = 0;
229
- const minChunkSize = 10; // 最小块大小
230
- const maxChunkSize = 256; // 最大块大小
231
- let baseDelay = delay; // 基础延迟
232
- const maxDelay = 200; // 最大延迟
233
- const connectionCheckInterval = 5; // 每5个块检查一次连接状态
234
-
235
- for (let i = 0; i < data.length; i += chunkSize) {
236
- // 定期检查连接状态
237
- if (i > 0 && Math.floor(i / chunkSize) % connectionCheckInterval === 0) {
238
- try {
239
- const state = await Taro.getBLEConnectionState({ deviceId });
240
- if (!state.connected) {
241
- this.cleanupDevice(deviceId);
242
- throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Device disconnected');
243
- }
244
- } catch (error) {
245
- this.cleanupDevice(deviceId);
246
- throw new BluetoothPrintError(
247
- ErrorCode.DEVICE_DISCONNECTED,
248
- 'Device disconnected',
249
- error as Error
250
- );
251
- }
252
- }
253
-
254
- const chunk = data.slice(i, i + chunkSize);
255
- const chunkNum = Math.floor(i / chunkSize) + 1;
256
- let attempt = 0;
257
- let writeSuccess = false;
258
-
259
- while (attempt <= retries) {
260
- try {
261
- // 根据块大小动态调整超时时间 (1秒基础时间 + 每字节5ms)
262
- const timeoutMs = Math.max(1000, Math.min(10000, 1000 + chunk.length * 5));
263
-
264
- // 添加写入超时处理
265
- const writePromise = Taro.writeBLECharacteristicValue({
266
- deviceId,
267
- serviceId: serviceInfo.serviceId,
268
- characteristicId: serviceInfo.characteristicId,
269
- value: chunk.buffer,
270
- });
271
-
272
- const timeoutPromise = new Promise((_, reject) => {
273
- setTimeout(() => {
274
- reject(new Error(`Write timeout after ${timeoutMs} milliseconds`));
275
- }, timeoutMs);
276
- });
277
-
278
- await Promise.race([writePromise, timeoutPromise]);
279
-
280
- this.logger.debug(`Chunk ${chunkNum}/${totalChunks} written successfully`);
281
- writeSuccess = true;
282
- break; // Success
283
- } catch (error) {
284
- attempt++;
285
- if (attempt > retries) {
286
- this.logger.error(`Chunk ${chunkNum} failed after ${retries} retries`);
287
- throw new BluetoothPrintError(
288
- ErrorCode.WRITE_FAILED,
289
- `Failed to write chunk ${chunkNum}/${totalChunks}`,
290
- error as Error
291
- );
292
- }
293
- this.logger.warn(`Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`);
294
-
295
- // 使用指数退避算法,重试间隔随重试次数增加而增加
296
- const retryDelay = baseDelay * Math.pow(2, attempt - 1);
297
- await new Promise(r => setTimeout(r, Math.min(retryDelay, maxDelay)));
298
- }
299
- }
300
-
301
- // 动态调整块大小和延迟
302
- if (writeSuccess) {
303
- successCount++;
304
- consecutiveFailures = 0;
305
- failureCount = Math.max(0, failureCount - 1);
306
-
307
- // 网络状况改善,增加块大小,减少延迟
308
- if (successCount % 3 === 0 && chunkSize < maxChunkSize) {
309
- chunkSize = Math.min(maxChunkSize, chunkSize + 5);
310
- baseDelay = Math.max(baseDelay / 1.2, validatedOptions.delay);
311
- totalChunks = Math.ceil((data.length - i - chunkSize) / chunkSize) + chunkNum;
312
- this.logger.debug(`Increased chunk size to ${chunkSize}, delay to ${baseDelay}`);
313
- }
314
- } else {
315
- failureCount++;
316
- consecutiveFailures++;
317
- successCount = Math.max(0, successCount - 1);
318
-
319
- // 网络状况恶化,减少块大小,增加延迟
320
- if (consecutiveFailures >= 2 && chunkSize > minChunkSize) {
321
- chunkSize = Math.max(minChunkSize, chunkSize - 5);
322
- baseDelay = Math.min(baseDelay * 1.5, maxDelay);
323
- totalChunks = Math.ceil((data.length - i - chunkSize) / chunkSize) + chunkNum;
324
- this.logger.debug(`Decreased chunk size to ${chunkSize}, delay to ${baseDelay}`);
325
- consecutiveFailures = 0;
326
- }
327
- }
328
-
329
- // Small delay to prevent congestion
330
- if (i + chunkSize < data.length) {
331
- await new Promise(r => setTimeout(r, baseDelay));
332
- }
333
- }
334
-
335
- this.logger.info(`Successfully wrote ${data.length} bytes`);
336
- }
337
-
338
- /**
339
- * Discovers services and characteristics for a device
340
- * Caches the writeable characteristic for future writes
341
- *
342
- * @param deviceId - Bluetooth device ID
343
- * @throws {BluetoothPrintError} When no writeable characteristic is found
344
- */
345
- private async discoverServices(deviceId: string): Promise<void> {
346
- this.logger.debug('Discovering services for device:', deviceId);
347
-
348
- try {
349
- const services = await Taro.getBLEDeviceServices({ deviceId });
350
-
351
- for (const service of services.services) {
352
- const chars = await Taro.getBLEDeviceCharacteristics({
353
- deviceId,
354
- serviceId: service.uuid,
355
- });
356
-
357
- const writeChar = chars.characteristics.find(
358
- (c: TaroBLECharacteristic) => c.properties.write || c.properties.writeWithoutResponse
359
- );
360
-
361
- if (writeChar) {
362
- this.serviceCache.set(deviceId, {
363
- serviceId: service.uuid,
364
- characteristicId: writeChar.uuid,
365
- });
366
- this.logger.info('Found writeable characteristic:', {
367
- service: service.uuid,
368
- characteristic: writeChar.uuid,
369
- });
370
- return;
371
- }
372
- }
373
-
374
- // No writeable characteristic found
375
- throw new BluetoothPrintError(
376
- ErrorCode.CHARACTERISTIC_NOT_FOUND,
377
- 'No writeable characteristic found. Make sure the device is a supported printer.'
378
- );
379
- } catch (error) {
380
- if (error instanceof BluetoothPrintError) {
381
- throw error;
382
- }
383
- throw new BluetoothPrintError(
384
- ErrorCode.SERVICE_DISCOVERY_FAILED,
385
- 'Failed to discover device services',
386
- error as Error
387
- );
388
- }
27
+ export class TaroAdapter extends MiniProgramAdapter {
28
+ protected getApi(): MiniProgramBLEApi {
29
+ return Taro;
389
30
  }
390
31
  }
@@ -222,22 +222,25 @@ export class EventEmitter<T> {
222
222
 
223
223
  handlersCopy.forEach(handler => {
224
224
  promises.push(
225
- new Promise<void>(resolve => {
225
+ (async () => {
226
226
  try {
227
227
  // 根据事件类型决定是否传递数据
228
+ let result: unknown;
228
229
  if (data === undefined || data === null) {
229
230
  // @ts-expect-error - 类型安全由调用方保证
230
- handler();
231
+ result = handler();
231
232
  } else {
232
- handler(data);
233
+ result = handler(data);
234
+ }
235
+ // 等待异步 handler 完成
236
+ if (result instanceof Promise) {
237
+ await result;
233
238
  }
234
239
  } catch (error) {
235
240
  // 捕获并处理事件处理程序中的错误,避免影响其他监听器
236
241
  console.error(`Error in event handler for "${String(event)}":`, error);
237
- } finally {
238
- resolve();
239
242
  }
240
- })
243
+ })()
241
244
  );
242
245
  });
243
246