@onekeyfe/hd-transport-react-native 1.1.27-alpha.40 → 1.1.27-alpha.5

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.
package/src/index.ts CHANGED
@@ -9,150 +9,38 @@ import {
9
9
  } from 'react-native-ble-plx';
10
10
  import ByteBuffer from 'bytebuffer';
11
11
  import transport, {
12
+ COMMON_HEADER_SIZE,
12
13
  LogBlockCommand,
13
14
  type OneKeyDeviceInfoBase,
14
- PROTOCOL_V1_MESSAGE_HEADER_SIZE,
15
- PROTOCOL_V2_CHANNEL_BLE_UART,
16
- type ProtocolType,
17
- ProtocolV2FrameAssembler,
18
- ProtocolV2Session,
19
- type TransportCallOptions,
20
- probeProtocolV2 as probeProtocolV2Helper,
21
15
  } from '@onekeyfe/hd-transport';
22
16
  import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
17
+ import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
23
18
 
24
19
  import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
25
- import {
26
- hasWritableCapability,
27
- resolveBleWriteMode,
28
- resolveProtocolV2PacketCapacity,
29
- } from './bleStrategy';
30
20
  import { subscribeBleOn } from './subscribeBleOn';
31
21
  import {
32
22
  ANDROID_PACKET_LENGTH,
33
23
  IOS_PACKET_LENGTH,
34
- getBleUuidKey,
35
24
  getBluetoothServiceUuids,
36
25
  getInfosForServiceUuid,
37
- isSameBleUuid,
38
26
  } from './constants';
39
27
  import { isHeaderChunk } from './utils/validateNotify';
40
28
  import BleTransport from './BleTransport';
41
29
  import timer from './utils/timer';
42
- import { bleLogger, setBleLogger } from './logger';
43
30
 
44
31
  import type { Deferred } from '@onekeyfe/hd-shared';
45
32
  import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
46
33
  import type EventEmitter from 'events';
47
34
  import type { BleAcquireInput, TransportOptions } from './types';
48
35
 
49
- const { check, ProtocolV1, parseConfigure } = transport;
36
+ const { check, buildBuffers, receiveOne, parseConfigure } = transport;
50
37
 
51
- const Log = bleLogger;
38
+ const Log = getLogger(LoggerNames.HdBleTransport);
52
39
 
53
40
  const transportCache: Record<string, BleTransport> = {};
54
- const BLE_RESPONSE_TIMEOUT_MS = 30_000;
55
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
56
- const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
57
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
58
- const IOS_NOTIFY_READY_DELAY_MS = 150;
59
- const ANDROID_NOTIFY_READY_DELAY_MS = 300;
60
- const HIGH_VOLUME_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 6;
61
- const HIGH_VOLUME_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 6 : 2;
62
- const HIGH_VOLUME_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 20 : 8;
63
-
64
- const delay = (ms: number) =>
65
- new Promise<void>(resolve => {
66
- setTimeout(resolve, ms);
67
- });
68
-
69
- export type ProtocolV2BleTuning = {
70
- iosPacketLength?: number;
71
- androidPacketLength?: number;
72
- highVolumeWriteBurstSize?: number;
73
- highVolumeWritePauseMs?: number;
74
- highVolumeWriteFlushDelayMs?: number;
75
- highVolumeWriteWithResponse?: boolean;
76
- };
77
-
78
- type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
79
-
80
- const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
81
- iosPacketLength: IOS_PACKET_LENGTH,
82
- androidPacketLength: ANDROID_PACKET_LENGTH,
83
- highVolumeWriteBurstSize: HIGH_VOLUME_WRITE_BURST_SIZE,
84
- highVolumeWritePauseMs: HIGH_VOLUME_WRITE_PAUSE_MS,
85
- highVolumeWriteFlushDelayMs: HIGH_VOLUME_WRITE_FLUSH_DELAY_MS,
86
- highVolumeWriteWithResponse: false,
87
- };
88
-
89
- let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
90
-
91
- const normalizePositiveInteger = (value: unknown, fallback: number) => {
92
- const normalized = Number(value);
93
- if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
94
- return Math.floor(normalized);
95
- };
96
-
97
- export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
98
- protocolV2BleTuning = {
99
- iosPacketLength: normalizePositiveInteger(
100
- tuning.iosPacketLength,
101
- protocolV2BleTuning.iosPacketLength
102
- ),
103
- androidPacketLength: normalizePositiveInteger(
104
- tuning.androidPacketLength,
105
- protocolV2BleTuning.androidPacketLength
106
- ),
107
- highVolumeWriteBurstSize: normalizePositiveInteger(
108
- tuning.highVolumeWriteBurstSize,
109
- protocolV2BleTuning.highVolumeWriteBurstSize
110
- ),
111
- highVolumeWritePauseMs: normalizePositiveInteger(
112
- tuning.highVolumeWritePauseMs,
113
- protocolV2BleTuning.highVolumeWritePauseMs
114
- ),
115
- highVolumeWriteFlushDelayMs: normalizePositiveInteger(
116
- tuning.highVolumeWriteFlushDelayMs,
117
- protocolV2BleTuning.highVolumeWriteFlushDelayMs
118
- ),
119
- highVolumeWriteWithResponse:
120
- tuning.highVolumeWriteWithResponse ?? protocolV2BleTuning.highVolumeWriteWithResponse,
121
- };
122
- Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning configured:', protocolV2BleTuning);
123
- }
124
-
125
- export function resetProtocolV2BleTuning() {
126
- protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
127
- Log?.debug('[ReactNativeBleTransport] Protocol V2 BLE tuning reset:', protocolV2BleTuning);
128
- }
129
-
130
- export function getProtocolV2BleTuning() {
131
- return { ...protocolV2BleTuning };
132
- }
133
-
134
- function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
135
- return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
136
- }
137
-
138
- function getDeviceDisplayName(device?: Device | null) {
139
- return device?.name || device?.localName || null;
140
- }
141
41
 
142
- function isGenericBleService(uuid?: string | null) {
143
- return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
144
- }
145
-
146
- function hasKnownOneKeyService(device?: Device | null) {
147
- return (device?.serviceUUIDs ?? []).some(serviceUuid =>
148
- getInfosForServiceUuid(serviceUuid, 'classic')
149
- );
150
- }
151
-
152
- const ANDROID_REQUEST_MTU = 256;
153
-
154
- const connectOptions: Record<string, unknown> = {
155
- requestMTU: ANDROID_REQUEST_MTU,
42
+ let connectOptions: Record<string, unknown> = {
43
+ requestMTU: 256,
156
44
  timeout: 3000,
157
45
  refreshGatt: 'OnConnected',
158
46
  };
@@ -161,29 +49,12 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
161
49
 
162
50
  const tryToGetConfiguration = (device: Device) => {
163
51
  if (!device || !device.serviceUUIDs) return null;
164
- const serviceUUID = device.serviceUUIDs.find(uuid => getInfosForServiceUuid(uuid, 'classic'));
165
- if (!serviceUUID) return null;
52
+ const [serviceUUID] = device.serviceUUIDs;
166
53
  const infos = getInfosForServiceUuid(serviceUUID, 'classic');
167
54
  if (!infos) return null;
168
55
  return infos;
169
56
  };
170
57
 
171
- const requestAndroidMtu = async (device: Device) => {
172
- if (Platform.OS !== 'android') return device;
173
-
174
- try {
175
- const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
176
- Log?.debug('[ReactNativeBleTransport] Android MTU requested:', {
177
- requested: ANDROID_REQUEST_MTU,
178
- mtu: mtuDevice.mtu,
179
- });
180
- return mtuDevice;
181
- } catch (error) {
182
- Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
183
- return device;
184
- }
185
- };
186
-
187
58
  type IOBleErrorRemap = Error | BleError | null | undefined;
188
59
 
189
60
  function remapError(error: IOBleErrorRemap) {
@@ -221,45 +92,23 @@ export default class ReactNativeBleTransport {
221
92
 
222
93
  _messages: ReturnType<typeof transport.parseConfigure> | undefined;
223
94
 
224
- _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
225
-
226
95
  name = 'ReactNativeBleTransport';
227
96
 
228
97
  configured = false;
229
98
 
230
99
  stopped = false;
231
100
 
232
- scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
101
+ scanTimeout = 3000;
233
102
 
234
103
  runPromise: Deferred<any> | null = null;
235
104
 
236
105
  emitter?: EventEmitter;
237
106
 
238
- /** Per-device protocol type detected by active wire-level probe after connect. */
239
- private deviceProtocol: Map<string, ProtocolType> = new Map();
240
-
241
- private deviceProtocolHints: Map<string, ProtocolType> = new Map();
242
-
243
- private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
244
-
245
- private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
246
-
247
- private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
248
-
249
- private activeProtocolV2Call: { uuid: string; token: number } | null = null;
250
-
251
- private nextProtocolV2CallToken = 1;
252
-
253
- private monitorTokens: Map<string, number> = new Map();
254
-
255
- private nextMonitorToken = 1;
256
-
257
107
  constructor(options: TransportOptions) {
258
- this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
108
+ this.scanTimeout = options.scanTimeout ?? 3000;
259
109
  }
260
110
 
261
- init(logger: any, emitter: EventEmitter) {
262
- setBleLogger(logger);
111
+ init(_logger: any, emitter: EventEmitter) {
263
112
  this.emitter = emitter;
264
113
  }
265
114
 
@@ -269,11 +118,6 @@ export default class ReactNativeBleTransport {
269
118
  this._messages = messages;
270
119
  }
271
120
 
272
- configureProtocolV2(signedData: any) {
273
- this._messagesV2 = parseConfigure(signedData);
274
- Log?.debug('[ReactNativeBleTransport] Protocol V2 schema configured');
275
- }
276
-
277
121
  listen() {
278
122
  // empty
279
123
  }
@@ -323,7 +167,6 @@ export default class ReactNativeBleTransport {
323
167
  blePlxManager.startDeviceScan(
324
168
  null,
325
169
  {
326
- allowDuplicates: true,
327
170
  scanMode: ScanMode.LowLatency,
328
171
  },
329
172
  (error, device) => {
@@ -351,41 +194,14 @@ export default class ReactNativeBleTransport {
351
194
  return;
352
195
  }
353
196
 
354
- const displayName = getDeviceDisplayName(device);
355
- const isOneKey =
356
- isOnekeyDevice(device?.name ?? null, device?.id) ||
357
- isOnekeyDevice(device?.localName ?? null, device?.id) ||
358
- hasKnownOneKeyService(device);
359
- const shouldTraceCandidate =
360
- !!displayName && /onekey|bixinkey|pro\s*2|pro\b|touch|^k\d|^t\d/i.test(displayName);
361
-
362
- if (shouldTraceCandidate) {
363
- Log?.debug('[ReactNativeBleTransport] scan candidate', {
364
- name: device?.name,
365
- localName: device?.localName,
366
- id: device?.id,
367
- serviceUUIDs: device?.serviceUUIDs,
368
- accepted: isOneKey,
369
- });
370
- }
371
-
372
- if (isOneKey) {
197
+ if (isOnekeyDevice(device?.name ?? null, device?.id)) {
373
198
  Log?.debug('search device start ======================');
374
- const { name, localName, id, serviceUUIDs } = device ?? {};
199
+ const { name, localName, id } = device ?? {};
375
200
  Log?.debug(
376
- `device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${
377
- id ?? ''
378
- }\nserviceUUIDs: ${(serviceUUIDs ?? []).join(',')}`
201
+ `device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
379
202
  );
380
203
  addDevice(device as unknown as Device);
381
204
  Log?.debug('search device end ======================\n');
382
- } else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
383
- Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
384
- name: device?.name,
385
- localName: device?.localName,
386
- id: device?.id,
387
- serviceUUIDs: device?.serviceUUIDs,
388
- });
389
205
  }
390
206
  }
391
207
  );
@@ -399,16 +215,7 @@ export default class ReactNativeBleTransport {
399
215
 
400
216
  const addDevice = (device: Device) => {
401
217
  if (deviceList.every(d => d.id !== device.id)) {
402
- const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
403
- const protocolHint = inferProtocolHintFromDeviceName(displayName);
404
- if (protocolHint) {
405
- this.deviceProtocolHints.set(device.id, protocolHint);
406
- }
407
- deviceList.push({
408
- ...device,
409
- name: displayName,
410
- commType: 'ble',
411
- } as IOneKeyDevice);
218
+ deviceList.push({ ...device, commType: 'ble' } as IOneKeyDevice);
412
219
  }
413
220
  };
414
221
 
@@ -420,45 +227,25 @@ export default class ReactNativeBleTransport {
420
227
  }
421
228
 
422
229
  async acquire(input: BleAcquireInput) {
423
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
230
+ const { uuid, forceCleanRunPromise } = input;
424
231
 
425
232
  if (!uuid) {
426
233
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
427
234
  }
428
235
 
429
- const cachedTransport = transportCache[uuid];
430
- if (cachedTransport) {
431
- const cachedProtocol = this.deviceProtocol.get(uuid);
432
- const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
433
- if (
434
- isCachedDeviceConnected &&
435
- cachedProtocol &&
436
- (!expectedProtocol || cachedProtocol === expectedProtocol)
437
- ) {
438
- Log?.debug(
439
- '[ReactNativeBleTransport] reuse cached BLE transport:',
440
- uuid,
441
- cachedProtocol
442
- );
443
- return { uuid, protocolType: cachedProtocol };
444
- }
236
+ let device: Device | null = null;
445
237
 
238
+ if (transportCache[uuid]) {
446
239
  /**
447
- * If the transport is not reusable due to a protocol mismatch or stale
448
- * connection, clean it up before creating a new transport instance.
240
+ * If the transport is not released due to an exception operation
241
+ * it will be handled again here
449
242
  */
450
- Log?.debug('transport not reusable, will release: ', uuid);
451
- await this.release(uuid, true);
243
+ Log?.debug('transport not be released, will release: ', uuid);
244
+ await this.release(uuid);
452
245
  }
453
246
 
454
- let device: Device | null = null;
455
-
456
247
  if (forceCleanRunPromise && this.runPromise) {
457
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
458
- this.runPromise.reject(error);
459
- this.rejectAllProtocolV2Frames(error);
460
- this.runPromise = null;
461
- this.activeProtocolV2Call = null;
248
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
462
249
  Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
463
250
  }
464
251
 
@@ -470,12 +257,11 @@ export default class ReactNativeBleTransport {
470
257
  throw error;
471
258
  }
472
259
 
260
+ // check device is bonded
473
261
  if (Platform.OS === 'android') {
474
262
  const bondState = await pairDevice(uuid);
475
263
  if (bondState.bonding) {
476
264
  await onDeviceBondState(uuid);
477
- } else if (!bondState.bonded) {
478
- throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
479
265
  }
480
266
  }
481
267
 
@@ -501,6 +287,7 @@ export default class ReactNativeBleTransport {
501
287
  e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
502
288
  e.errorCode === BleErrorCode.OperationCancelled
503
289
  ) {
290
+ connectOptions = {};
504
291
  Log?.debug('first try to reconnect without params');
505
292
  device = await blePlxManager.connectToDevice(uuid);
506
293
  } else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
@@ -520,16 +307,17 @@ export default class ReactNativeBleTransport {
520
307
  Log?.debug('not connected, try to connect to device: ', uuid);
521
308
 
522
309
  try {
523
- device = await device.connect(connectOptions);
310
+ await device.connect(connectOptions);
524
311
  } catch (e) {
525
312
  Log?.debug('not connected, try to connect to device has error: ', e);
526
313
  if (
527
314
  e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
528
315
  e.errorCode === BleErrorCode.OperationCancelled
529
316
  ) {
317
+ connectOptions = {};
530
318
  Log?.debug('second try to reconnect without params');
531
319
  try {
532
- device = await device.connect();
320
+ await device.connect();
533
321
  } catch (e) {
534
322
  Log?.debug('last try to reconnect error: ', e);
535
323
  // last try to reconnect device if this issue exists
@@ -537,7 +325,7 @@ export default class ReactNativeBleTransport {
537
325
  if (e.errorCode === BleErrorCode.OperationCancelled) {
538
326
  Log?.debug('last try to reconnect');
539
327
  await device.cancelConnection();
540
- device = await device.connect();
328
+ await device.connect();
541
329
  }
542
330
  }
543
331
  } else {
@@ -546,12 +334,9 @@ export default class ReactNativeBleTransport {
546
334
  }
547
335
  }
548
336
 
549
- device = await requestAndroidMtu(device);
550
-
551
337
  await device.discoverAllServicesAndCharacteristics();
552
338
  let infos = tryToGetConfiguration(device);
553
339
  let characteristics;
554
- let fallbackServiceUuid: string | undefined;
555
340
 
556
341
  if (!infos) {
557
342
  for (const serviceUuid of getBluetoothServiceUuids()) {
@@ -566,45 +351,17 @@ export default class ReactNativeBleTransport {
566
351
  }
567
352
 
568
353
  if (!infos) {
569
- const services = await device.services();
570
- Log?.debug(
571
- '[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
572
- services?.map(service => service.uuid)
573
- );
574
-
575
- const knownService = services.find(service =>
576
- getInfosForServiceUuid(service.uuid, 'classic')
577
- );
578
- const fallbackService =
579
- knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
580
-
581
- if (fallbackService) {
582
- fallbackServiceUuid = fallbackService.uuid;
583
- characteristics = await device.characteristicsForService(fallbackService.uuid);
584
- Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
585
- }
586
- }
587
-
588
- if (!infos) {
589
- if (!fallbackServiceUuid) {
590
- try {
591
- Log?.debug('cancel connection when service not found');
592
- await device.cancelConnection();
593
- } catch (e) {
594
- Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
595
- }
596
- throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
354
+ try {
355
+ Log?.debug('cancel connection when service not found');
356
+ await device.cancelConnection();
357
+ } catch (e) {
358
+ Log?.debug('cancel connection error when service not found: ', e.message || e.reason);
597
359
  }
598
- }
599
-
600
- const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
601
- const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
602
- const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
603
-
604
- if (!serviceUuid) {
605
360
  throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
606
361
  }
607
362
 
363
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
364
+
608
365
  if (!characteristics) {
609
366
  characteristics = await device.characteristicsForService(serviceUuid);
610
367
  }
@@ -616,9 +373,9 @@ export default class ReactNativeBleTransport {
616
373
  let writeCharacteristic;
617
374
  let notifyCharacteristic;
618
375
  for (const c of characteristics) {
619
- if (isSameBleUuid(c.uuid, writeUuid)) {
376
+ if (c.uuid === writeUuid) {
620
377
  writeCharacteristic = c;
621
- } else if (isSameBleUuid(c.uuid, notifyUuid)) {
378
+ } else if (c.uuid === notifyUuid) {
622
379
  notifyCharacteristic = c;
623
380
  }
624
381
  }
@@ -631,7 +388,7 @@ export default class ReactNativeBleTransport {
631
388
  throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
632
389
  }
633
390
 
634
- if (!hasWritableCapability(writeCharacteristic)) {
391
+ if (!writeCharacteristic.isWritableWithResponse) {
635
392
  throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
636
393
  }
637
394
 
@@ -641,47 +398,16 @@ export default class ReactNativeBleTransport {
641
398
  );
642
399
  }
643
400
 
644
- const protocolHint = expectedProtocol
645
- ? undefined
646
- : this.deviceProtocolHints.get(uuid) ??
647
- inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
648
-
649
401
  // release transport before new transport instance
650
- await this.release(uuid, true);
651
- if (protocolHint) {
652
- this.deviceProtocolHints.set(uuid, protocolHint);
653
- }
402
+ await this.release(uuid);
654
403
 
655
404
  const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
656
- if (Platform.OS === 'android') {
657
- transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
658
- }
659
- const monitorToken = this.nextMonitorToken;
660
- this.nextMonitorToken += 1;
661
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
662
- transport.monitorToken = monitorToken;
663
- transport.notifyTransactionId = notifyTransactionId;
664
- this.monitorTokens.set(uuid, monitorToken);
665
405
  transport.notifySubscription = this._monitorCharacteristic(
666
406
  transport.notifyCharacteristic,
667
- uuid,
668
- monitorToken,
669
- notifyTransactionId
407
+ uuid
670
408
  );
671
409
  transportCache[uuid] = transport;
672
410
 
673
- this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
674
-
675
- if (Platform.OS === 'ios') {
676
- await new Promise<void>(resolve => {
677
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
678
- });
679
- } else if (Platform.OS === 'android') {
680
- await delay(ANDROID_NOTIFY_READY_DELAY_MS);
681
- }
682
-
683
- const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
684
-
685
411
  this.emitter?.emit('device-connect', {
686
412
  name: device.name,
687
413
  id: device.id,
@@ -689,11 +415,6 @@ export default class ReactNativeBleTransport {
689
415
  });
690
416
 
691
417
  transport.disconnectSubscription = device.onDisconnected(() => {
692
- if (transportCache[uuid] !== transport) {
693
- Log?.debug('device disconnect ignored for stale transport: ', device?.id);
694
- return;
695
- }
696
-
697
418
  try {
698
419
  Log?.debug('device disconnect: ', device?.id);
699
420
  this.emitter?.emit('device-disconnect', {
@@ -702,40 +423,28 @@ export default class ReactNativeBleTransport {
702
423
  connectId: device?.id,
703
424
  });
704
425
  if (this.runPromise) {
705
- const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
706
- this.runPromise.reject(error);
707
- this.rejectAllProtocolV2Frames(error);
426
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
708
427
  }
709
428
  } catch (e) {
710
429
  Log?.debug('device disconnect error: ', e);
711
430
  } finally {
712
- this.release(uuid, true);
431
+ this.release(uuid);
713
432
  }
714
433
  });
715
434
 
716
- return { uuid, protocolType };
435
+ return { uuid };
717
436
  }
718
437
 
719
- _monitorCharacteristic(
720
- characteristic: Characteristic,
721
- uuid: string,
722
- monitorToken: number,
723
- notifyTransactionId: string
724
- ): Subscription {
438
+ _monitorCharacteristic(characteristic: Characteristic, uuid: string): Subscription {
725
439
  let bufferLength = 0;
726
440
  let buffer: any[] = [];
727
441
  const subscription = characteristic.monitor((error, c) => {
728
- const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
729
442
  if (error) {
730
443
  Log?.debug(
731
444
  `error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
732
445
  error as unknown as string
733
446
  }`
734
447
  );
735
- if (!isCurrentMonitor) {
736
- Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
737
- return;
738
- }
739
448
  if (this.runPromise) {
740
449
  let ERROR:
741
450
  | typeof HardwareErrorCode.BleDeviceBondError
@@ -755,45 +464,27 @@ export default class ReactNativeBleTransport {
755
464
  error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
756
465
  error.reason?.includes('notify change failed for device')
757
466
  ) {
758
- const notifyError = ERRORS.TypedError(
759
- HardwareErrorCode.BleCharacteristicNotifyChangeFailure
467
+ this.runPromise.reject(
468
+ ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotifyChangeFailure)
760
469
  );
761
- this.runPromise.reject(notifyError);
762
- this.rejectAllProtocolV2Frames(notifyError);
763
470
  Log?.debug(
764
471
  `${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
765
472
  );
766
473
  return;
767
474
  }
768
- const notifyError = ERRORS.TypedError(ERROR);
769
- this.runPromise.reject(notifyError);
770
- this.rejectAllProtocolV2Frames(notifyError);
475
+ this.runPromise.reject(ERRORS.TypedError(ERROR));
771
476
  Log?.debug(': monitor notify error, and has unreleased Promise', Error);
772
477
  }
773
478
 
774
479
  return;
775
480
  }
776
481
 
777
- if (!isCurrentMonitor) {
778
- Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
779
- return;
780
- }
781
-
782
482
  if (!c) {
783
483
  throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
784
484
  }
785
485
 
786
486
  try {
787
487
  const data = Buffer.from(c.value as string, 'base64');
788
- const protocol = this.deviceProtocol.get(uuid);
789
- if (!protocol) {
790
- Log?.debug('monitor data ignored before protocol detection: ', uuid);
791
- return;
792
- }
793
- if (protocol === 'V2') {
794
- this.handleProtocolV2Notification(uuid, new Uint8Array(data));
795
- return;
796
- }
797
488
  // console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
798
489
  if (isHeaderChunk(data)) {
799
490
  bufferLength = data.readInt32BE(5);
@@ -802,7 +493,7 @@ export default class ReactNativeBleTransport {
802
493
  buffer = buffer.concat([...data]);
803
494
  }
804
495
 
805
- if (buffer.length - PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferLength) {
496
+ if (buffer.length - COMMON_HEADER_SIZE >= bufferLength) {
806
497
  const value = Buffer.from(buffer);
807
498
  // console.log(
808
499
  // '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
@@ -816,41 +507,17 @@ export default class ReactNativeBleTransport {
816
507
  }
817
508
  } catch (error) {
818
509
  Log?.debug('monitor data error: ', error);
819
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
820
- this.runPromise?.reject(notifyError);
821
- this.rejectAllProtocolV2Frames(notifyError);
510
+ this.runPromise?.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
822
511
  }
823
- }, notifyTransactionId);
512
+ }, uuid);
824
513
 
825
514
  return subscription;
826
515
  }
827
516
 
828
- async release(uuid: string, onclose = false) {
517
+ async release(uuid: string) {
829
518
  const transport = transportCache[uuid];
830
- if (this.runPromise) {
831
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
832
- this.runPromise.reject(error);
833
- this.runPromise = null;
834
- this.rejectAllProtocolV2Frames(error);
835
- this.activeProtocolV2Call = null;
836
- } else {
837
- this.resetProtocolV2Frames(uuid);
838
- }
839
-
840
- if (Platform.OS === 'android' && !onclose && transport) {
841
- this.protocolV2Assemblers.get(uuid)?.reset();
842
- this.resetProtocolV2Frames(uuid);
843
- if (this.activeProtocolV2Call?.uuid === uuid) {
844
- this.activeProtocolV2Call = null;
845
- }
846
- return Promise.resolve(true);
847
- }
848
519
 
849
520
  if (transport) {
850
- if (this.monitorTokens.get(uuid) === transport.monitorToken) {
851
- this.monitorTokens.delete(uuid);
852
- }
853
-
854
521
  // Clean up disconnect subscription first to prevent callbacks on released transport
855
522
  Log?.debug('release: removing disconnect subscription for device: ', uuid);
856
523
  transport.disconnectSubscription?.remove();
@@ -864,27 +531,12 @@ export default class ReactNativeBleTransport {
864
531
  transport.notifySubscription?.remove();
865
532
  transport.notifySubscription = undefined;
866
533
 
867
- if (transport.notifyTransactionId) {
868
- try {
869
- await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
870
- } catch (e) {
871
- Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
872
- }
873
- }
874
-
875
534
  delete transportCache[uuid];
876
- }
877
-
878
- this.deviceProtocol.delete(uuid);
879
- this.deviceProtocolHints.delete(uuid);
880
- this.protocolV2Assemblers.get(uuid)?.reset();
881
- this.protocolV2Assemblers.delete(uuid);
882
- this.resetProtocolV2Frames(uuid);
883
535
 
884
- try {
885
- await this.blePlxManager?.cancelTransaction(uuid);
886
- } catch (e) {
887
- Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
536
+ // Temporary close the Android disconnect after each request
537
+ if (Platform.OS === 'android') {
538
+ // await this.blePlxManager?.cancelDeviceConnection(uuid);
539
+ }
888
540
  }
889
541
 
890
542
  return Promise.resolve(true);
@@ -894,12 +546,7 @@ export default class ReactNativeBleTransport {
894
546
  await this.call(session, name, data);
895
547
  }
896
548
 
897
- async call(
898
- uuid: string,
899
- name: string,
900
- data: Record<string, unknown>,
901
- options?: TransportCallOptions
902
- ) {
549
+ async call(uuid: string, name: string, data: Record<string, unknown>) {
903
550
  if (this.stopped) {
904
551
  // eslint-disable-next-line prefer-promise-reject-errors
905
552
  return Promise.reject(ERRORS.TypedError('Transport stopped.'));
@@ -915,13 +562,13 @@ export default class ReactNativeBleTransport {
915
562
  throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
916
563
  }
917
564
 
918
- const protocol = this.getProtocolType(uuid);
919
- if (!protocol) {
920
- throw ERRORS.TypedError(
921
- HardwareErrorCode.RuntimeError,
922
- `Device protocol has not been detected for ${uuid}`
923
- );
565
+ const transport = transportCache[uuid];
566
+ if (!transport) {
567
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
924
568
  }
569
+
570
+ this.runPromise = createDeferred();
571
+ const messages = this._messages;
925
572
  // Upload resources on low-end phones may OOM
926
573
  if (name === 'ResourceUpdate' || name === 'ResourceAck') {
927
574
  Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
@@ -929,44 +576,12 @@ export default class ReactNativeBleTransport {
929
576
  hash: data?.hash,
930
577
  });
931
578
  } else if (LogBlockCommand.has(name)) {
932
- Log?.debug('transport-react-native', 'call-', ' name: ', name, ' protocol: ', protocol);
579
+ Log?.debug('transport-react-native', 'call-', ' name: ', name);
933
580
  } else {
934
- Log?.debug(
935
- 'transport-react-native',
936
- 'call-',
937
- ' name: ',
938
- name,
939
- ' data: ',
940
- data,
941
- ' protocol: ',
942
- protocol
943
- );
944
- }
945
-
946
- if (protocol === 'V2') {
947
- return this.callProtocolV2(uuid, name, data, options);
948
- }
949
-
950
- return this.callProtocolV1(uuid, name, data, options);
951
- }
952
-
953
- private async callProtocolV1(
954
- uuid: string,
955
- name: string,
956
- data: Record<string, unknown>,
957
- options?: TransportCallOptions
958
- ) {
959
- if (!this._messages) {
960
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
581
+ Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data);
961
582
  }
962
583
 
963
- const transport = this.getCachedTransport(uuid);
964
- const runPromise = createDeferred<string>();
965
- runPromise.promise.catch(() => undefined);
966
- this.runPromise = runPromise;
967
- const messages = this._messages;
968
- const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
969
- let timeout: ReturnType<typeof setTimeout> | undefined;
584
+ const buffers = buildBuffers(messages, name, data);
970
585
 
971
586
  async function writeChunkedData(
972
587
  buffers: ByteBuffer[],
@@ -1037,41 +652,20 @@ export default class ReactNativeBleTransport {
1037
652
  }
1038
653
 
1039
654
  try {
1040
- const response = await Promise.race([
1041
- runPromise.promise,
1042
- new Promise<never>((_, reject) => {
1043
- if (options?.timeoutMs) {
1044
- timeout = setTimeout(() => {
1045
- const error = ERRORS.TypedError(
1046
- HardwareErrorCode.BleTimeoutError,
1047
- `BLE response timeout after ${options.timeoutMs}ms for ${name}`
1048
- );
1049
- runPromise.reject(error);
1050
- reject(error);
1051
- }, options.timeoutMs);
1052
- }
1053
- }),
1054
- ]);
655
+ const response = await this.runPromise.promise;
1055
656
 
1056
657
  if (typeof response !== 'string') {
1057
658
  throw new Error('Returning data is not string.');
1058
659
  }
1059
660
 
1060
661
  Log?.debug('receive data: ', response);
1061
- const jsonData = ProtocolV1.decodeMessage(messages, response);
662
+ const jsonData = receiveOne(messages, response);
1062
663
  return check.call(jsonData);
1063
664
  } catch (e) {
1064
- if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
1065
- Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
1066
- } else {
1067
- Log?.error('call error: ', e);
1068
- }
665
+ Log?.error('call error: ', e);
1069
666
  throw e;
1070
667
  } finally {
1071
- if (timeout) clearTimeout(timeout);
1072
- if (this.runPromise === runPromise) {
1073
- this.runPromise = null;
1074
- }
668
+ this.runPromise = null;
1075
669
  }
1076
670
  }
1077
671
 
@@ -1138,13 +732,6 @@ export default class ReactNativeBleTransport {
1138
732
  if (transportCache[session]) {
1139
733
  delete transportCache[session];
1140
734
  }
1141
- this.deviceProtocol.delete(session);
1142
- this.deviceProtocolHints.delete(session);
1143
- this.protocolV2Assemblers.delete(session);
1144
- this.resetProtocolV2Frames(session);
1145
- if (this.activeProtocolV2Call?.uuid === session) {
1146
- this.activeProtocolV2Call = null;
1147
- }
1148
735
 
1149
736
  // emit the disconnect event
1150
737
  try {
@@ -1167,421 +754,4 @@ export default class ReactNativeBleTransport {
1167
754
  }
1168
755
  this.runPromise = null;
1169
756
  }
1170
-
1171
- private getCachedTransport(uuid: string) {
1172
- const transport = transportCache[uuid];
1173
- if (!transport) {
1174
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
1175
- }
1176
- return transport;
1177
- }
1178
-
1179
- private createProtocolMismatchError(expected: ProtocolType) {
1180
- return ERRORS.TypedError(
1181
- HardwareErrorCode.RuntimeError,
1182
- `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
1183
- );
1184
- }
1185
-
1186
- private createProtocolDetectionError() {
1187
- return ERRORS.TypedError(
1188
- HardwareErrorCode.BleTimeoutError,
1189
- 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
1190
- );
1191
- }
1192
-
1193
- private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
1194
- if (this.deviceProtocol.get(uuid) === protocol) {
1195
- this.deviceProtocol.delete(uuid);
1196
- }
1197
- }
1198
-
1199
- private async detectProtocol(
1200
- uuid: string,
1201
- expectedProtocol?: ProtocolType,
1202
- protocolHint?: ProtocolType
1203
- ): Promise<ProtocolType> {
1204
- if (expectedProtocol === 'V1') {
1205
- if (await this.probeProtocolV1(uuid)) {
1206
- this.deviceProtocol.set(uuid, 'V1');
1207
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1 (expected)`);
1208
- return 'V1';
1209
- }
1210
- throw this.createProtocolMismatchError(expectedProtocol);
1211
- }
1212
-
1213
- if (expectedProtocol === 'V2') {
1214
- this.deviceProtocol.set(uuid, 'V2');
1215
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1216
- return 'V2';
1217
- }
1218
-
1219
- if (protocolHint === 'V2') {
1220
- this.deviceProtocol.set(uuid, 'V2');
1221
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
1222
- return 'V2';
1223
- }
1224
-
1225
- if (this.deviceProtocol.get(uuid) === 'V2') {
1226
- this.deviceProtocol.set(uuid, 'V2');
1227
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
1228
- return 'V2';
1229
- }
1230
-
1231
- const protocolV1Detected = await this.probeProtocolV1(uuid);
1232
- if (protocolV1Detected) {
1233
- this.deviceProtocol.set(uuid, 'V1');
1234
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V1`);
1235
- return 'V1';
1236
- }
1237
-
1238
- await this.resetProbeStateAfterProtocolProbe(uuid, 'V1');
1239
- if (await this.probeProtocolV2(uuid)) {
1240
- this.deviceProtocol.set(uuid, 'V2');
1241
- Log?.debug(`[ReactNativeBleTransport] detectProtocol: uuid=${uuid} -> V2`);
1242
- return 'V2';
1243
- }
1244
-
1245
- this.deviceProtocol.delete(uuid);
1246
- throw this.createProtocolDetectionError();
1247
- }
1248
-
1249
- private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
1250
- const transport = transportCache[uuid];
1251
- this.protocolV2Assemblers.get(uuid)?.reset();
1252
- this.resetProtocolV2Frames(uuid);
1253
- if (this.activeProtocolV2Call?.uuid === uuid) {
1254
- this.activeProtocolV2Call = null;
1255
- }
1256
- if (this.runPromise) {
1257
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
1258
- this.runPromise.reject(error);
1259
- this.runPromise = null;
1260
- }
1261
-
1262
- if (!transport) return;
1263
-
1264
- const previousNotifyTransactionId = transport.notifyTransactionId;
1265
- if (this.monitorTokens.get(uuid) === transport.monitorToken) {
1266
- this.monitorTokens.delete(uuid);
1267
- }
1268
- transport.notifySubscription?.remove();
1269
- transport.notifySubscription = undefined;
1270
- if (previousNotifyTransactionId) {
1271
- try {
1272
- await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
1273
- } catch (error) {
1274
- Log?.debug(
1275
- `[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
1276
- error?.message || error
1277
- );
1278
- }
1279
- }
1280
-
1281
- const monitorToken = this.nextMonitorToken;
1282
- this.nextMonitorToken += 1;
1283
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
1284
- transport.monitorToken = monitorToken;
1285
- transport.notifyTransactionId = notifyTransactionId;
1286
- this.monitorTokens.set(uuid, monitorToken);
1287
- transport.notifySubscription = this._monitorCharacteristic(
1288
- transport.notifyCharacteristic,
1289
- uuid,
1290
- monitorToken,
1291
- notifyTransactionId
1292
- );
1293
- if (Platform.OS === 'ios') {
1294
- await new Promise<void>(resolve => {
1295
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
1296
- });
1297
- }
1298
- }
1299
-
1300
- private async probeProtocolV1(uuid: string) {
1301
- if (!this._messages) {
1302
- return false;
1303
- }
1304
-
1305
- try {
1306
- this.deviceProtocol.set(uuid, 'V1');
1307
- await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1308
- return true;
1309
- } catch (error) {
1310
- this.clearProbeProtocol(uuid, 'V1');
1311
- Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1312
- return false;
1313
- }
1314
- }
1315
-
1316
- private async probeProtocolV2(uuid: string) {
1317
- if (!this._messages || !this._messagesV2) {
1318
- return false;
1319
- }
1320
-
1321
- this.deviceProtocol.set(uuid, 'V2');
1322
- this.protocolV2Assemblers.get(uuid)?.reset();
1323
- const detected = await probeProtocolV2Helper({
1324
- call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
1325
- this.callProtocolV2(uuid, name, data, options),
1326
- timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
1327
- logger: Log,
1328
- logPrefix: 'ProtocolV2 RN-BLE',
1329
- onProbeFailed: () => {
1330
- this.protocolV2Assemblers.get(uuid)?.reset();
1331
- this.resetProtocolV2Frames(uuid);
1332
- },
1333
- });
1334
- if (!detected) {
1335
- this.clearProbeProtocol(uuid, 'V2');
1336
- }
1337
- return detected;
1338
- }
1339
-
1340
- private handleProtocolV2Notification(uuid: string, data: Uint8Array) {
1341
- try {
1342
- if (!this.runPromise || this.activeProtocolV2Call?.uuid !== uuid) {
1343
- this.protocolV2Assemblers.get(uuid)?.reset();
1344
- this.resetProtocolV2Frames(uuid);
1345
- return;
1346
- }
1347
-
1348
- if (data.length === 0) return;
1349
-
1350
- const assembler = this.protocolV2Assemblers.get(uuid);
1351
- if (!assembler) return;
1352
-
1353
- let frameData = assembler.push(data);
1354
- while (frameData) {
1355
- this.resolveProtocolV2Frame(uuid, frameData);
1356
- frameData = assembler.push(new Uint8Array(0));
1357
- }
1358
- } catch (error) {
1359
- Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
1360
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
1361
- this.runPromise?.reject(notifyError);
1362
- this.rejectAllProtocolV2Frames(notifyError);
1363
- }
1364
- }
1365
-
1366
- private getProtocolV2FrameQueue(uuid: string) {
1367
- let queue = this.protocolV2FrameQueues.get(uuid);
1368
- if (!queue) {
1369
- queue = [];
1370
- this.protocolV2FrameQueues.set(uuid, queue);
1371
- }
1372
- return queue;
1373
- }
1374
-
1375
- private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
1376
- const framePromise = this.protocolV2FramePromises.get(uuid);
1377
- if (framePromise) {
1378
- framePromise.resolve(frame);
1379
- this.protocolV2FramePromises.delete(uuid);
1380
- return;
1381
- }
1382
- this.getProtocolV2FrameQueue(uuid).push(frame);
1383
- }
1384
-
1385
- private rejectAllProtocolV2Frames(error: Error) {
1386
- this.protocolV2FrameQueues.clear();
1387
- for (const framePromise of this.protocolV2FramePromises.values()) {
1388
- framePromise.reject(error);
1389
- }
1390
- this.protocolV2FramePromises.clear();
1391
- }
1392
-
1393
- private resetProtocolV2Frames(uuid: string) {
1394
- this.protocolV2FrameQueues.delete(uuid);
1395
- this.protocolV2FramePromises.delete(uuid);
1396
- }
1397
-
1398
- private isActiveProtocolV2Call(uuid: string, token: number) {
1399
- return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
1400
- }
1401
-
1402
- private async readProtocolV2Frame(uuid: string) {
1403
- const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
1404
- if (queuedFrame) {
1405
- return queuedFrame;
1406
- }
1407
-
1408
- const framePromise = createDeferred<Uint8Array>();
1409
- this.protocolV2FramePromises.set(uuid, framePromise);
1410
- try {
1411
- return await framePromise.promise;
1412
- } finally {
1413
- if (this.protocolV2FramePromises.get(uuid) === framePromise) {
1414
- this.protocolV2FramePromises.delete(uuid);
1415
- }
1416
- }
1417
- }
1418
-
1419
- private async writeProtocolV2Frame(
1420
- transport: BleTransport,
1421
- frame: Uint8Array,
1422
- options?: { highVolume?: boolean; writeWithResponse?: boolean }
1423
- ) {
1424
- const tuning = getProtocolV2BleTuning();
1425
- const packetCapacity = resolveProtocolV2PacketCapacity({
1426
- platform: Platform.OS,
1427
- iosPacketLength: tuning.iosPacketLength,
1428
- androidPacketLength: tuning.androidPacketLength,
1429
- mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
1430
- });
1431
- const writeWithResponse =
1432
- !!options?.writeWithResponse || (!!options?.highVolume && tuning.highVolumeWriteWithResponse);
1433
- const writeMode = resolveBleWriteMode(
1434
- transport.writeCharacteristic,
1435
- writeWithResponse ? 'withResponse' : 'withoutResponse'
1436
- );
1437
- const shouldThrottle = !!options?.highVolume && writeMode === 'withoutResponse';
1438
- let packetsWritten = 0;
1439
-
1440
- try {
1441
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1442
- const chunk = frame.slice(offset, offset + packetCapacity);
1443
- const base64 = Buffer.from(chunk).toString('base64');
1444
- if (writeMode === 'withResponse') {
1445
- await transport.writeCharacteristic.writeWithResponse(base64);
1446
- } else {
1447
- await transport.writeCharacteristic.writeWithoutResponse(base64);
1448
- }
1449
- packetsWritten += 1;
1450
-
1451
- if (
1452
- shouldThrottle &&
1453
- packetsWritten % tuning.highVolumeWriteBurstSize === 0 &&
1454
- offset + packetCapacity < frame.length
1455
- ) {
1456
- await delay(tuning.highVolumeWritePauseMs);
1457
- }
1458
- }
1459
-
1460
- if (shouldThrottle) {
1461
- await delay(tuning.highVolumeWriteFlushDelayMs);
1462
- }
1463
- } catch (error) {
1464
- if (options?.highVolume && !writeWithResponse && packetsWritten === 0) {
1465
- Log?.debug(
1466
- '[ReactNativeBleTransport] Protocol V2 high-volume writeWithoutResponse failed before data was sent, fallback to writeWithResponse:',
1467
- error
1468
- );
1469
- await this.writeProtocolV2Frame(transport, frame, {
1470
- highVolume: true,
1471
- writeWithResponse: true,
1472
- });
1473
- return;
1474
- }
1475
- throw error;
1476
- }
1477
- }
1478
-
1479
- private async callProtocolV2(
1480
- uuid: string,
1481
- name: string,
1482
- data: Record<string, unknown>,
1483
- options?: TransportCallOptions
1484
- ) {
1485
- if (!this._messages || !this._messagesV2) {
1486
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
1487
- }
1488
-
1489
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'GetProtoVersion';
1490
- if (this.runPromise) {
1491
- if (!forceRun) {
1492
- throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
1493
- }
1494
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
1495
- this.runPromise.reject(error);
1496
- this.rejectAllProtocolV2Frames(error);
1497
- this.runPromise = null;
1498
- this.activeProtocolV2Call = null;
1499
- }
1500
-
1501
- const transport = this.getCachedTransport(uuid);
1502
- const runPromise = createDeferred<Uint8Array>();
1503
- runPromise.promise.catch(() => undefined);
1504
- this.runPromise = runPromise;
1505
- const callToken = this.nextProtocolV2CallToken++;
1506
- this.activeProtocolV2Call = { uuid, token: callToken };
1507
- this.protocolV2Assemblers.get(uuid)?.reset();
1508
- this.resetProtocolV2Frames(uuid);
1509
- let completed = false;
1510
- const callOptions = {
1511
- ...options,
1512
- timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
1513
- };
1514
- const highVolumeWrite = LogBlockCommand.has(name);
1515
-
1516
- if (highVolumeWrite) {
1517
- const tuning = getProtocolV2BleTuning();
1518
- Log?.debug(
1519
- '[ReactNativeBleTransport] Protocol V2 high-volume write uses throttled writeWithoutResponse:',
1520
- name,
1521
- {
1522
- packetCapacity:
1523
- Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1524
- burstSize: tuning.highVolumeWriteBurstSize,
1525
- pauseMs: tuning.highVolumeWritePauseMs,
1526
- flushDelayMs: tuning.highVolumeWriteFlushDelayMs,
1527
- writeWithResponse: tuning.highVolumeWriteWithResponse,
1528
- }
1529
- );
1530
- }
1531
-
1532
- try {
1533
- const session = new ProtocolV2Session({
1534
- schemas: {
1535
- protocolV1: this._messages,
1536
- protocolV2: this._messagesV2,
1537
- },
1538
- router: PROTOCOL_V2_CHANNEL_BLE_UART,
1539
- writeFrame: async (frame: Uint8Array) => {
1540
- await this.writeProtocolV2Frame(transport, frame, {
1541
- highVolume: highVolumeWrite,
1542
- });
1543
- },
1544
- readFrame: async () => {
1545
- const rxFrame = await this.readProtocolV2Frame(uuid);
1546
- if (!(rxFrame instanceof Uint8Array)) {
1547
- throw new Error('Protocol V2 response is not Uint8Array');
1548
- }
1549
- return rxFrame;
1550
- },
1551
- logger: Log,
1552
- logPrefix: 'ProtocolV2 RN-BLE',
1553
- createTimeoutError: (_messageName: string, timeout: number) =>
1554
- ERRORS.TypedError(
1555
- HardwareErrorCode.BleTimeoutError,
1556
- `BLE response timeout after ${timeout}ms for ${name}`
1557
- ),
1558
- });
1559
-
1560
- const result = await session.call(name, data, callOptions);
1561
- completed = true;
1562
- return result;
1563
- } catch (e) {
1564
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1565
- this.protocolV2Assemblers.get(uuid)?.reset();
1566
- this.resetProtocolV2Frames(uuid);
1567
- }
1568
- Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1569
- throw e;
1570
- } finally {
1571
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1572
- if (!completed) {
1573
- this.protocolV2Assemblers.get(uuid)?.reset();
1574
- }
1575
- this.resetProtocolV2Frames(uuid);
1576
- this.activeProtocolV2Call = null;
1577
- }
1578
- if (this.runPromise === runPromise) {
1579
- this.runPromise = null;
1580
- }
1581
- }
1582
- }
1583
-
1584
- getProtocolType(path: string): ProtocolType | undefined {
1585
- return this.deviceProtocol.get(path);
1586
- }
1587
757
  }