taro-bluetooth-print 2.10.2 → 2.12.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 (69) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.es.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/types/adapters/BaseAdapter.d.ts +29 -2
  6. package/dist/types/adapters/ChunkWriteStrategy.d.ts +33 -0
  7. package/dist/types/adapters/ReactNativeAdapter.d.ts +1 -1
  8. package/dist/types/cache/OfflineCache.d.ts +41 -9
  9. package/dist/types/constants/strings.d.ts +136 -0
  10. package/dist/types/core/EventEmitter.d.ts +8 -1
  11. package/dist/types/core/event/EventBus.d.ts +1 -1
  12. package/dist/types/encoding/EncodingService.d.ts +17 -0
  13. package/dist/types/errors/baseError.d.ts +4 -1
  14. package/dist/types/preview/PreviewRenderer.d.ts +27 -0
  15. package/dist/types/queue/PrintQueue.d.ts +33 -1
  16. package/dist/types/services/BatchPrintManager.d.ts +22 -27
  17. package/dist/types/services/PrintHistory.d.ts +16 -1
  18. package/dist/types/services/PrintJobManager.d.ts +10 -0
  19. package/dist/types/services/PrintScheduler.d.ts +3 -0
  20. package/dist/types/template/TemplateEngine.d.ts +2 -0
  21. package/dist/types/template/engines/TemplateRenderer.d.ts +2 -0
  22. package/dist/types/utils/bitmap.d.ts +17 -0
  23. package/dist/types/utils/errorHandler.d.ts +78 -0
  24. package/dist/types/utils/image.d.ts +62 -10
  25. package/package.json +2 -1
  26. package/src/adapters/AdapterFactory.ts +3 -29
  27. package/src/adapters/AlipayAdapter.ts +2 -3
  28. package/src/adapters/BaiduAdapter.ts +2 -3
  29. package/src/adapters/BaseAdapter.ts +102 -58
  30. package/src/adapters/ByteDanceAdapter.ts +2 -3
  31. package/src/adapters/ChunkWriteStrategy.ts +236 -80
  32. package/src/adapters/QQAdapter.ts +2 -3
  33. package/src/adapters/ReactNativeAdapter.ts +13 -58
  34. package/src/adapters/TaroAdapter.ts +2 -3
  35. package/src/adapters/WebBluetoothAdapter.ts +1 -3
  36. package/src/barcode/BarcodeGenerator.ts +1 -2
  37. package/src/cache/OfflineCache.ts +180 -27
  38. package/src/config/PrinterConfigManager.ts +2 -1
  39. package/src/constants/strings.ts +186 -0
  40. package/src/core/EventEmitter.ts +33 -32
  41. package/src/core/di/Container.ts +8 -19
  42. package/src/core/event/EventBus.ts +9 -13
  43. package/src/core/plugin/PluginManager.ts +0 -4
  44. package/src/device/DeviceManager.ts +7 -2
  45. package/src/device/DiscoveryService.ts +0 -4
  46. package/src/device/MultiPrinterManager.ts +8 -7
  47. package/src/drivers/CpclDriver.ts +3 -7
  48. package/src/drivers/XprinterDriver.ts +1 -2
  49. package/src/drivers/ZplDriver.ts +3 -7
  50. package/src/encoding/EncodingService.ts +106 -202
  51. package/src/errors/baseError.ts +7 -0
  52. package/src/factory/PrinterFactory.ts +3 -1
  53. package/src/preview/PreviewRenderer.ts +324 -173
  54. package/src/providers/ServiceProvider.ts +2 -6
  55. package/src/queue/PrintQueue.ts +135 -32
  56. package/src/services/BatchPrintManager.ts +153 -130
  57. package/src/services/CloudPrintManager.ts +2 -1
  58. package/src/services/ConnectionManager.ts +5 -1
  59. package/src/services/PrintHistory.ts +60 -10
  60. package/src/services/PrintJobManager.ts +58 -19
  61. package/src/services/PrintScheduler.ts +154 -26
  62. package/src/services/PrintStatistics.ts +3 -4
  63. package/src/template/TemplateEngine.ts +4 -1
  64. package/src/template/engines/TemplateRenderer.ts +5 -7
  65. package/src/template/parsers/TemplateParser.ts +7 -2
  66. package/src/utils/bitmap.ts +25 -0
  67. package/src/utils/errorHandler.ts +196 -0
  68. package/src/utils/image.ts +375 -270
  69. package/src/utils/outputLimiter.ts +7 -4
@@ -54,7 +54,7 @@ export interface AdaptiveWriteConfig {
54
54
  * Default adaptive write configuration
55
55
  */
56
56
  export const DEFAULT_ADAPTIVE_CONFIG: AdaptiveWriteConfig = {
57
- defaultChunkSize: 20,
57
+ defaultChunkSize: 50, // 增加初始 chunk size,减少 BLE 通信开销
58
58
  defaultDelay: 20,
59
59
  defaultRetries: 3,
60
60
  minChunkSize: 10,
@@ -62,7 +62,7 @@ export const DEFAULT_ADAPTIVE_CONFIG: AdaptiveWriteConfig = {
62
62
  maxDelay: 200,
63
63
  successThreshold: 3,
64
64
  failureThreshold: 2,
65
- connectionCheckInterval: 5,
65
+ connectionCheckInterval: 10, // 增加连接检查间隔,减少不必要的检查
66
66
  };
67
67
 
68
68
  /**
@@ -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
  /**
@@ -150,99 +169,236 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
150
169
  ): Promise<void> {
151
170
  const data = new Uint8Array(buffer);
152
171
 
153
- const chunkSize = Math.max(
154
- 1,
155
- Math.min(256, adapterOptions.chunkSize ?? this.config.defaultChunkSize)
156
- );
157
- const delay = Math.max(10, Math.min(100, adapterOptions.delay ?? this.config.defaultDelay));
158
- const retries = Math.max(1, Math.min(10, adapterOptions.retries ?? this.config.defaultRetries));
159
-
160
- const maxChunkSize = this.getMaxChunkSize(context.deviceId);
161
-
162
- let currentChunkSize = chunkSize;
163
- let baseDelay = delay;
164
- let totalChunks = Math.ceil(data.length / currentChunkSize);
165
-
166
- this.logger.debug(`Writing ${data.length} bytes in ${totalChunks} chunks`);
167
-
168
172
  if (data.length === 0) {
169
173
  this.logger.warn('No data to write');
170
174
  return;
171
175
  }
172
176
 
177
+ // Initialize transmission parameters
178
+ const params = this.initializeTransmissionParams(data, adapterOptions);
179
+ const maxChunkSize = this.getMaxChunkSize(context.deviceId);
180
+
173
181
  // Adaptive transmission state
174
- let successCount = 0;
175
- let consecutiveFailures = 0;
176
- const { minChunkSize, maxDelay, successThreshold, failureThreshold, connectionCheckInterval } =
177
- this.config;
182
+ const adaptiveState = {
183
+ successCount: 0,
184
+ consecutiveFailures: 0,
185
+ currentChunkSize: params.chunkSize,
186
+ baseDelay: params.delay,
187
+ };
178
188
 
179
- for (let i = 0; i < data.length; i += currentChunkSize) {
189
+ this.logger.debug(
190
+ `Writing ${data.length} bytes with initial chunkSize=${adaptiveState.currentChunkSize}, delay=${adaptiveState.baseDelay}ms`
191
+ );
192
+
193
+ // Main transmission loop
194
+ for (let i = 0; i < data.length; i += adaptiveState.currentChunkSize) {
180
195
  // Periodic connection state check
181
- if (i > 0 && Math.floor(i / currentChunkSize) % connectionCheckInterval === 0) {
182
- await this.checkConnection(context.deviceId);
196
+ await this.maybeCheckConnection(i, adaptiveState.currentChunkSize, context.deviceId);
197
+
198
+ // Write current chunk with retries
199
+ const chunkNum = Math.floor(i / adaptiveState.currentChunkSize) + 1;
200
+ const writeSuccess = await this.writeChunkWithRetries(
201
+ data,
202
+ i,
203
+ adaptiveState.currentChunkSize,
204
+ context,
205
+ chunkNum,
206
+ params.retries,
207
+ adaptiveState.baseDelay,
208
+ platformOptions
209
+ );
210
+
211
+ if (!writeSuccess) {
212
+ // All retries exhausted - error already thrown
213
+ return;
183
214
  }
184
215
 
185
- const chunk = data.slice(i, i + currentChunkSize);
186
- const chunkNum = Math.floor(i / currentChunkSize) + 1;
187
- let attempt = 0;
188
- let writeSuccess = false;
189
-
190
- while (attempt <= retries) {
191
- const writeResult = await this.writeSingleChunk(chunk, context, platformOptions);
192
-
193
- if (writeResult.success) {
194
- this.logger.debug(`Chunk ${chunkNum}/${totalChunks} written successfully`);
195
- writeSuccess = true;
196
- break;
197
- }
198
-
199
- attempt++;
200
- if (attempt > retries) {
201
- this.logger.error(`Chunk ${chunkNum} failed after ${retries} retries`);
202
- throw new BluetoothPrintError(
203
- ErrorCode.WRITE_FAILED,
204
- `Failed to write chunk ${chunkNum}/${totalChunks}`,
205
- writeResult.error ?? new Error('Unknown write error')
206
- );
207
- }
208
-
209
- this.logger.warn(`Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`);
210
-
211
- const retryDelay = baseDelay * Math.pow(2, attempt - 1);
212
- await new Promise(r => setTimeout(r, Math.min(retryDelay, maxDelay)));
216
+ // Adaptive adjustment
217
+ this.adjustTransmissionParams(
218
+ adaptiveState,
219
+ writeSuccess,
220
+ maxChunkSize,
221
+ params.minChunkSize,
222
+ params.maxDelay,
223
+ params.successThreshold,
224
+ params.failureThreshold,
225
+ chunkNum,
226
+ i,
227
+ data.length
228
+ );
229
+
230
+ // Inter-chunk delay
231
+ await this.maybeDelay(
232
+ i,
233
+ adaptiveState.currentChunkSize,
234
+ data.length,
235
+ adaptiveState.baseDelay
236
+ );
237
+ }
238
+
239
+ this.logger.info(`Successfully wrote ${data.length} bytes`);
240
+ }
241
+
242
+ /**
243
+ * Initialize transmission parameters from adapter options
244
+ */
245
+ private initializeTransmissionParams(
246
+ _data: Uint8Array,
247
+ options: IAdapterOptions
248
+ ): {
249
+ chunkSize: number;
250
+ delay: number;
251
+ retries: number;
252
+ minChunkSize: number;
253
+ maxDelay: number;
254
+ successThreshold: number;
255
+ failureThreshold: number;
256
+ } {
257
+ const chunkSize = Math.max(1, Math.min(256, options.chunkSize ?? this.config.defaultChunkSize));
258
+ const delay = Math.max(10, Math.min(100, options.delay ?? this.config.defaultDelay));
259
+ const retries = Math.max(1, Math.min(10, options.retries ?? this.config.defaultRetries));
260
+
261
+ return {
262
+ chunkSize,
263
+ delay,
264
+ retries,
265
+ minChunkSize: this.config.minChunkSize,
266
+ maxDelay: this.config.maxDelay,
267
+ successThreshold: this.config.successThreshold,
268
+ failureThreshold: this.config.failureThreshold,
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Check connection periodically based on interval
274
+ */
275
+ private async maybeCheckConnection(
276
+ currentIndex: number,
277
+ chunkSize: number,
278
+ deviceId: string
279
+ ): Promise<void> {
280
+ if (
281
+ currentIndex > 0 &&
282
+ Math.floor(currentIndex / chunkSize) % this.config.connectionCheckInterval === 0
283
+ ) {
284
+ await this.checkConnection(deviceId);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Write a single chunk with retry logic
290
+ * @returns true if successful, false if all retries exhausted (error thrown)
291
+ */
292
+ private async writeChunkWithRetries(
293
+ data: Uint8Array,
294
+ startIndex: number,
295
+ chunkSize: number,
296
+ context: ChunkWriteContext,
297
+ chunkNum: number,
298
+ maxRetries: number,
299
+ baseDelay: number,
300
+ platformOptions?: TOptions
301
+ ): Promise<boolean> {
302
+ const chunk = data.slice(startIndex, startIndex + chunkSize);
303
+ const totalChunks = Math.ceil(data.length / chunkSize);
304
+
305
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
306
+ const writeResult = await this.writeSingleChunk(chunk, context, platformOptions);
307
+
308
+ if (writeResult.success) {
309
+ this.logger.debug(`Chunk ${chunkNum}/${totalChunks} written successfully`);
310
+ return true;
213
311
  }
214
312
 
215
- // Adaptive chunk size and delay adjustment
216
- if (writeSuccess) {
217
- successCount++;
218
- consecutiveFailures = 0;
219
-
220
- if (successCount % successThreshold === 0 && currentChunkSize < maxChunkSize) {
221
- currentChunkSize = Math.min(maxChunkSize, currentChunkSize + 5);
222
- baseDelay = Math.max(baseDelay / 1.2, delay);
223
- totalChunks =
224
- Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
225
- this.logger.debug(`Increased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
226
- }
227
- } else {
228
- consecutiveFailures++;
229
-
230
- if (consecutiveFailures >= failureThreshold && currentChunkSize > minChunkSize) {
231
- currentChunkSize = Math.max(minChunkSize, currentChunkSize - 5);
232
- baseDelay = Math.min(baseDelay * 1.5, maxDelay);
233
- totalChunks =
234
- Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
235
- this.logger.debug(`Decreased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
236
- consecutiveFailures = 0;
237
- }
313
+ if (attempt >= maxRetries) {
314
+ this.logger.error(`Chunk ${chunkNum} failed after ${maxRetries} retries`);
315
+ throw new BluetoothPrintError(
316
+ ErrorCode.WRITE_FAILED,
317
+ `Failed to write chunk ${chunkNum}/${totalChunks}`,
318
+ writeResult.error ?? new Error('Unknown write error')
319
+ );
320
+ }
321
+
322
+ this.logger.warn(`Chunk ${chunkNum} write failed, retry ${attempt + 1}/${maxRetries}`);
323
+
324
+ // Exponential backoff
325
+ const retryDelay = baseDelay * Math.pow(2, attempt);
326
+ await new Promise(r => setTimeout(r, Math.min(retryDelay, this.config.maxDelay)));
327
+ }
328
+
329
+ return false; // Unreachable, but satisfies TypeScript
330
+ }
331
+
332
+ /**
333
+ * Adjust chunk size and delay based on success/failure
334
+ */
335
+ private adjustTransmissionParams(
336
+ state: {
337
+ successCount: number;
338
+ consecutiveFailures: number;
339
+ currentChunkSize: number;
340
+ baseDelay: number;
341
+ },
342
+ writeSuccess: boolean,
343
+ maxChunkSize: number,
344
+ minChunkSize: number,
345
+ maxDelay: number,
346
+ successThreshold: number,
347
+ failureThreshold: number,
348
+ _chunkNum: number,
349
+ _currentIndex: number,
350
+ _totalLength: number
351
+ ): void {
352
+ if (writeSuccess) {
353
+ state.successCount++;
354
+ state.consecutiveFailures = 0;
355
+
356
+ // Increase chunk size on sustained success
357
+ if (state.successCount % successThreshold === 0 && state.currentChunkSize < maxChunkSize) {
358
+ state.currentChunkSize = Math.min(
359
+ maxChunkSize,
360
+ state.currentChunkSize + ChunkWriteStrategy.CHUNK_SIZE_STEP
361
+ );
362
+ state.baseDelay = Math.max(
363
+ state.baseDelay / ChunkWriteStrategy.DELAY_RECOVERY_FACTOR,
364
+ this.config.defaultDelay
365
+ );
366
+ this.logger.debug(
367
+ `Increased chunk size to ${state.currentChunkSize}, delay to ${state.baseDelay}ms`
368
+ );
238
369
  }
370
+ } else {
371
+ state.consecutiveFailures++;
239
372
 
240
- // Inter-chunk delay to prevent BLE congestion
241
- if (i + currentChunkSize < data.length) {
242
- await new Promise(r => setTimeout(r, baseDelay));
373
+ // Decrease chunk size on sustained failure
374
+ if (state.consecutiveFailures >= failureThreshold && state.currentChunkSize > minChunkSize) {
375
+ state.currentChunkSize = Math.max(
376
+ minChunkSize,
377
+ state.currentChunkSize - ChunkWriteStrategy.CHUNK_SIZE_STEP
378
+ );
379
+ state.baseDelay = Math.min(
380
+ state.baseDelay * ChunkWriteStrategy.DELAY_BACKOFF_FACTOR,
381
+ maxDelay
382
+ );
383
+ this.logger.debug(
384
+ `Decreased chunk size to ${state.currentChunkSize}, delay to ${state.baseDelay}ms`
385
+ );
386
+ state.consecutiveFailures = 0;
243
387
  }
244
388
  }
389
+ }
245
390
 
246
- this.logger.info(`Successfully wrote ${data.length} bytes`);
391
+ /**
392
+ * Apply inter-chunk delay to prevent BLE congestion
393
+ */
394
+ private async maybeDelay(
395
+ currentIndex: number,
396
+ chunkSize: number,
397
+ totalLength: number,
398
+ delay: number
399
+ ): Promise<void> {
400
+ if (currentIndex + chunkSize < totalLength) {
401
+ await new Promise(r => setTimeout(r, delay));
402
+ }
247
403
  }
248
404
  }
@@ -5,9 +5,8 @@
5
5
 
6
6
  import { MiniProgramAdapter, MiniProgramBLEApi } from './BaseAdapter';
7
7
 
8
- // Declare QQ global for TypeScript
9
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
10
- interface QQGlobal extends MiniProgramBLEApi {}
8
+ // QQ global type for TypeScript
9
+ type QQGlobal = MiniProgramBLEApi;
11
10
 
12
11
  declare const qq: QQGlobal;
13
12
 
@@ -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
  );
@@ -251,26 +252,7 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
251
252
  this.updateState(PrinterState.DISCONNECTED);
252
253
  this.cleanupDevice(deviceId);
253
254
 
254
- const errorMsg = normalizeError(error).message;
255
- if (errorMsg.includes('timeout')) {
256
- throw new BluetoothPrintError(
257
- ErrorCode.CONNECTION_TIMEOUT,
258
- `Connection to device ${deviceId} timed out`,
259
- normalizeError(error)
260
- );
261
- } else if (errorMsg.includes('not found') || errorMsg.includes('not exist')) {
262
- throw new BluetoothPrintError(
263
- ErrorCode.DEVICE_NOT_FOUND,
264
- `Device ${deviceId} not found`,
265
- normalizeError(error)
266
- );
267
- }
268
-
269
- throw new BluetoothPrintError(
270
- ErrorCode.CONNECTION_FAILED,
271
- `Failed to connect to device ${deviceId}`,
272
- normalizeError(error)
273
- );
255
+ throw this.classifyConnectionError(error, deviceId);
274
256
  }
275
257
  }
276
258
 
@@ -370,7 +352,6 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
370
352
  try {
371
353
  this.bleManager.startDeviceScan(null, { allowDuplicates: false }, (error: unknown) => {
372
354
  if (error) {
373
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
374
355
  reject(normalizeError(error));
375
356
  }
376
357
  });
@@ -398,47 +379,21 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
398
379
  * @param device - Connected device object
399
380
  */
400
381
  private async discoverServices(deviceId: string, _device: RNDevice): Promise<void> {
401
- Logger.scope('ReactNativeAdapter').debug('Discovering services for device:', deviceId);
402
-
403
- try {
382
+ await this.discoverAndCacheServices(deviceId, async () => {
404
383
  const servicesResult = await (this.bleManager.discoverAllServicesAndCharacteristicsForDevice(
405
384
  deviceId
406
385
  ) as Promise<{ services: RNService[] }>);
407
386
 
408
387
  const services = servicesResult.services || [];
409
388
 
410
- for (const service of services) {
411
- const writeChar = service.characteristics.find(
412
- (c: RNCharacteristic) => c.isWritableWithResponse || c.isWritableWithoutResponse
413
- );
414
-
415
- if (writeChar) {
416
- this.serviceCache.set(deviceId, {
417
- serviceId: service.uuid,
418
- characteristicId: writeChar.uuid,
419
- });
420
- Logger.scope('ReactNativeAdapter').info('Found writeable characteristic:', {
421
- service: service.uuid,
422
- characteristic: writeChar.uuid,
423
- });
424
- return;
425
- }
426
- }
427
-
428
- throw new BluetoothPrintError(
429
- ErrorCode.CHARACTERISTIC_NOT_FOUND,
430
- 'No writeable characteristic found. Make sure the device is a supported printer.'
431
- );
432
- } catch (error) {
433
- if (error instanceof BluetoothPrintError) {
434
- throw error;
435
- }
436
- throw new BluetoothPrintError(
437
- ErrorCode.SERVICE_DISCOVERY_FAILED,
438
- 'Failed to discover device services',
439
- normalizeError(error)
440
- );
441
- }
389
+ return services.map(service => ({
390
+ serviceId: service.uuid,
391
+ characteristics: service.characteristics.map((c: RNCharacteristic) => ({
392
+ characteristicId: c.uuid,
393
+ isWritable: c.isWritableWithResponse || c.isWritableWithoutResponse,
394
+ })),
395
+ }));
396
+ });
442
397
  }
443
398
 
444
399
  /**
@@ -5,9 +5,8 @@
5
5
 
6
6
  import { MiniProgramAdapter, MiniProgramBLEApi } from './BaseAdapter';
7
7
 
8
- // Declare Taro global for TypeScript
9
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
10
- interface TaroGlobal extends MiniProgramBLEApi {}
8
+ // Taro global type for TypeScript
9
+ type TaroGlobal = MiniProgramBLEApi;
11
10
 
12
11
  declare const Taro: TaroGlobal;
13
12
 
@@ -413,9 +413,7 @@ export class WebBluetoothAdapter extends BaseAdapter {
413
413
  }
414
414
 
415
415
  // Use device.id if available, otherwise fall back to generated ID
416
- const deviceId = device.id || this.generateFallbackDeviceId(device);
417
-
418
- return deviceId;
416
+ return device.id || this.generateFallbackDeviceId(device);
419
417
  }
420
418
 
421
419
  /**
@@ -566,8 +566,7 @@ export class BarcodeGenerator implements IBarcodeGenerator {
566
566
  */
567
567
  private validateCode39(content: string, errors: ValidationResult['errors']): void {
568
568
  // Code 39 supports: 0-9, A-Z, space, - . $ / + %
569
- // eslint-disable-next-line no-useless-escape
570
- const validChars = /^[0-9A-Z\s\-.$\/+%]+$/;
569
+ const validChars = /^[0-9A-Z\s\-.$/+%]+$/;
571
570
  if (!validChars.test(content.toUpperCase())) {
572
571
  errors.push({
573
572
  field: 'content',