@onekeyfe/hd-transport-react-native 1.1.34-alpha.3 → 1.1.34-alpha.4

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,44 +9,33 @@ 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_BLE_FRAME_MAX_BYTES,
16
- PROTOCOL_V2_CHANNEL_BLE_UART,
17
- type ProtocolType,
18
- ProtocolV2FrameAssembler,
19
- ProtocolV2LinkManager,
20
- type TransportCallOptions,
21
- probeProtocolV2 as probeProtocolV2Helper,
22
15
  } from '@onekeyfe/hd-transport';
23
16
  import { ERRORS, HardwareErrorCode, createDeferred, isOnekeyDevice } from '@onekeyfe/hd-shared';
17
+ import { LoggerNames, getLogger } from '@onekeyfe/hd-core';
24
18
 
25
19
  import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
26
- import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
27
20
  import { subscribeBleOn } from './subscribeBleOn';
28
21
  import {
29
22
  ANDROID_PACKET_LENGTH,
30
23
  IOS_PACKET_LENGTH,
31
- getBleUuidKey,
32
24
  getBluetoothServiceUuids,
33
25
  getInfosForServiceUuid,
34
- isSameBleUuid,
35
26
  } from './constants';
36
27
  import { isHeaderChunk } from './utils/validateNotify';
37
28
  import BleTransport from './BleTransport';
38
29
  import timer from './utils/timer';
39
- import { bleLogger, setBleLogger } from './logger';
40
- import { createTransportCallLog } from './transportLog';
41
30
 
42
31
  import type { Deferred } from '@onekeyfe/hd-shared';
43
32
  import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
44
33
  import type EventEmitter from 'events';
45
34
  import type { BleAcquireInput, TransportOptions } from './types';
46
35
 
47
- const { check, ProtocolV1, parseConfigure } = transport;
36
+ const { check, buildBuffers, receiveOne, parseConfigure } = transport;
48
37
 
49
- const Log = bleLogger;
38
+ const Log = getLogger(LoggerNames.HdBleTransport);
50
39
 
51
40
  const transportCache: Record<string, BleTransport> = {};
52
41
  const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
@@ -65,6 +54,11 @@ type ResolvedBleCharacteristics = {
65
54
  notifyCharacteristic: Characteristic;
66
55
  };
67
56
 
57
+ const getBleIdentityName = (device?: { name?: string | null } | null): string | null => {
58
+ const localName = (device as { localName?: string | null } | undefined)?.localName;
59
+ return device?.name ?? localName ?? null;
60
+ };
61
+
68
62
  const delay = (ms: number) =>
69
63
  new Promise<void>(resolve => {
70
64
  setTimeout(resolve, ms);
@@ -103,76 +97,9 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
103
97
 
104
98
  const resolveFirmwareUploadRetryDelay = (attempt: number, baseDelayMs = 200, maxDelayMs = 1200) =>
105
99
  Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
106
- const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
107
- const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
108
- const DEVICE_SCAN_TIMEOUT_MS = 8000;
109
- const IOS_NOTIFY_READY_DELAY_MS = 150;
110
- const ANDROID_NOTIFY_READY_DELAY_MS = 300;
111
- export type ProtocolV2BleTuning = {
112
- iosPacketLength?: number;
113
- androidPacketLength?: number;
114
- };
115
-
116
- type ResolvedProtocolV2BleTuning = Required<ProtocolV2BleTuning>;
117
-
118
- const DEFAULT_PROTOCOL_V2_BLE_TUNING: ResolvedProtocolV2BleTuning = {
119
- iosPacketLength: IOS_PACKET_LENGTH,
120
- androidPacketLength: ANDROID_PACKET_LENGTH,
121
- };
122
-
123
- let protocolV2BleTuning: ResolvedProtocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
124
-
125
- const normalizePositiveInteger = (value: unknown, fallback: number) => {
126
- const normalized = Number(value);
127
- if (!Number.isFinite(normalized) || normalized <= 0) return fallback;
128
- return Math.floor(normalized);
129
- };
130
-
131
- export function configureProtocolV2BleTuning(tuning: ProtocolV2BleTuning = {}) {
132
- protocolV2BleTuning = {
133
- iosPacketLength: normalizePositiveInteger(
134
- tuning.iosPacketLength,
135
- protocolV2BleTuning.iosPacketLength
136
- ),
137
- androidPacketLength: normalizePositiveInteger(
138
- tuning.androidPacketLength,
139
- protocolV2BleTuning.androidPacketLength
140
- ),
141
- };
142
- Log?.debug('[ReactNativeBleTransport] BLE tuning configured', protocolV2BleTuning);
143
- }
144
-
145
- export function resetProtocolV2BleTuning() {
146
- protocolV2BleTuning = { ...DEFAULT_PROTOCOL_V2_BLE_TUNING };
147
- Log?.debug('[ReactNativeBleTransport] BLE tuning reset', protocolV2BleTuning);
148
- }
149
-
150
- export function getProtocolV2BleTuning() {
151
- return { ...protocolV2BleTuning };
152
- }
153
-
154
- function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
155
- return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
156
- }
157
-
158
- function getDeviceDisplayName(device?: Device | null) {
159
- return device?.name || device?.localName || null;
160
- }
161
-
162
- function isGenericBleService(uuid?: string | null) {
163
- return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
164
- }
165
100
 
166
- function hasKnownOneKeyService(device?: Device | null) {
167
- return (device?.serviceUUIDs ?? []).some(serviceUuid =>
168
- getInfosForServiceUuid(serviceUuid, 'classic')
169
- );
170
- }
171
-
172
- const ANDROID_REQUEST_MTU = 256;
173
-
174
- const connectOptions: Record<string, unknown> = {
175
- requestMTU: ANDROID_REQUEST_MTU,
101
+ let connectOptions: Record<string, unknown> = {
102
+ requestMTU: 256,
176
103
  timeout: 3000,
177
104
  refreshGatt: 'OnConnected',
178
105
  };
@@ -181,30 +108,12 @@ export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
181
108
 
182
109
  const tryToGetConfiguration = (device: Device) => {
183
110
  if (!device || !device.serviceUUIDs) return null;
184
- const serviceUUID = device.serviceUUIDs.find(uuid => getInfosForServiceUuid(uuid, 'classic'));
185
- if (!serviceUUID) return null;
111
+ const [serviceUUID] = device.serviceUUIDs;
186
112
  const infos = getInfosForServiceUuid(serviceUUID, 'classic');
187
113
  if (!infos) return null;
188
114
  return infos;
189
115
  };
190
116
 
191
- const requestAndroidMtu = async (device: Device) => {
192
- if (Platform.OS !== 'android') return device;
193
-
194
- try {
195
- const mtuDevice = await device.requestMTU(ANDROID_REQUEST_MTU);
196
- Log?.debug('[ReactNativeBleTransport] MTU configured', {
197
- deviceId: device.id,
198
- requested: ANDROID_REQUEST_MTU,
199
- actual: mtuDevice.mtu,
200
- });
201
- return mtuDevice;
202
- } catch (error) {
203
- Log?.debug('[ReactNativeBleTransport] Android MTU request failed:', error);
204
- return device;
205
- }
206
- };
207
-
208
117
  type IOBleErrorRemap = Error | BleError | null | undefined;
209
118
 
210
119
  function remapError(error: IOBleErrorRemap) {
@@ -242,15 +151,13 @@ export default class ReactNativeBleTransport {
242
151
 
243
152
  _messages: ReturnType<typeof transport.parseConfigure> | undefined;
244
153
 
245
- _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
246
-
247
154
  name = 'ReactNativeBleTransport';
248
155
 
249
156
  configured = false;
250
157
 
251
158
  stopped = false;
252
159
 
253
- scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
160
+ scanTimeout = 3000;
254
161
 
255
162
  runPromise: Deferred<any> | null = null;
256
163
 
@@ -258,48 +165,11 @@ export default class ReactNativeBleTransport {
258
165
 
259
166
  firmwareUploadWriteRecoveryIds = new Set<string>();
260
167
 
261
- /** Per-device protocol type detected by active wire-level probe after connect. */
262
- private deviceProtocol: Map<string, ProtocolType> = new Map();
263
-
264
- private deviceProtocolHints: Map<string, ProtocolType> = new Map();
265
-
266
- private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
267
-
268
- private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
269
-
270
- private protocolV2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
271
-
272
- private protocolV2Links = new ProtocolV2LinkManager<string>({
273
- getSchemas: () => {
274
- if (!this._messages || !this._messagesV2) {
275
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
276
- }
277
- return {
278
- protocolV1: this._messages,
279
- protocolV2: this._messagesV2,
280
- };
281
- },
282
- classifyError: () => 'link-fatal',
283
- onLinkInvalidated: async (uuid, reason) => {
284
- this.protocolV2Assemblers.get(uuid)?.reset();
285
- this.rejectProtocolV2Frames(uuid, new Error(reason));
286
- Log?.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
287
- if (reason.startsWith('Protocol V2 link-fatal error:')) {
288
- await this.release(uuid, true);
289
- }
290
- },
291
- });
292
-
293
- private monitorTokens: Map<string, number> = new Map();
294
-
295
- private nextMonitorToken = 1;
296
-
297
168
  constructor(options: TransportOptions) {
298
- this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
169
+ this.scanTimeout = options.scanTimeout ?? 3000;
299
170
  }
300
171
 
301
- init(logger: any, emitter: EventEmitter) {
302
- setBleLogger(logger);
172
+ init(_logger: any, emitter: EventEmitter) {
303
173
  this.emitter = emitter;
304
174
  }
305
175
 
@@ -309,13 +179,6 @@ export default class ReactNativeBleTransport {
309
179
  this._messages = messages;
310
180
  }
311
181
 
312
- configureProtocolV2(signedData: any) {
313
- this._messagesV2 = parseConfigure(signedData);
314
- this.protocolV2Links
315
- .invalidateAllLinks('Protocol V2 schema reconfigured')
316
- .catch(error => Log?.debug('Protocol V2 schema link cleanup failed:', error));
317
- }
318
-
319
182
  listen() {
320
183
  // empty
321
184
  }
@@ -343,29 +206,7 @@ export default class ReactNativeBleTransport {
343
206
  }
344
207
  }
345
208
 
346
- let fallbackServiceUuid: string | undefined;
347
-
348
209
  if (!infos) {
349
- const services = await device.services();
350
- Log?.debug(
351
- '[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
352
- services?.map(service => service.uuid)
353
- );
354
-
355
- const knownService = services.find(service =>
356
- getInfosForServiceUuid(service.uuid, 'classic')
357
- );
358
- const fallbackService =
359
- knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
360
-
361
- if (fallbackService) {
362
- fallbackServiceUuid = fallbackService.uuid;
363
- characteristics = await device.characteristicsForService(fallbackService.uuid);
364
- Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
365
- }
366
- }
367
-
368
- if (!infos && !fallbackServiceUuid) {
369
210
  try {
370
211
  Log?.debug('cancel connection when service not found');
371
212
  await device.cancelConnection();
@@ -375,13 +216,7 @@ export default class ReactNativeBleTransport {
375
216
  throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
376
217
  }
377
218
 
378
- const serviceUuid = infos?.serviceUuid ?? fallbackServiceUuid;
379
- const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
380
- const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
381
-
382
- if (!serviceUuid) {
383
- throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
384
- }
219
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
385
220
 
386
221
  if (!characteristics) {
387
222
  characteristics = await device.characteristicsForService(serviceUuid);
@@ -394,9 +229,9 @@ export default class ReactNativeBleTransport {
394
229
  let writeCharacteristic;
395
230
  let notifyCharacteristic;
396
231
  for (const c of characteristics) {
397
- if (isSameBleUuid(c.uuid, writeUuid)) {
232
+ if (c.uuid === writeUuid) {
398
233
  writeCharacteristic = c;
399
- } else if (isSameBleUuid(c.uuid, notifyUuid)) {
234
+ } else if (c.uuid === notifyUuid) {
400
235
  notifyCharacteristic = c;
401
236
  }
402
237
  }
@@ -409,7 +244,7 @@ export default class ReactNativeBleTransport {
409
244
  throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
410
245
  }
411
246
 
412
- if (!hasWritableCapability(writeCharacteristic)) {
247
+ if (!writeCharacteristic.isWritableWithResponse) {
413
248
  throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
414
249
  }
415
250
 
@@ -432,10 +267,6 @@ export default class ReactNativeBleTransport {
432
267
  Log?.debug('device disconnect ignored during FirmwareUpload write recovery: ', uuid);
433
268
  return;
434
269
  }
435
- if (transportCache[uuid] !== transport) {
436
- Log?.debug('device disconnect ignored for stale transport: ', device?.id);
437
- return;
438
- }
439
270
 
440
271
  try {
441
272
  Log?.debug('device disconnect: ', device?.id);
@@ -445,14 +276,12 @@ export default class ReactNativeBleTransport {
445
276
  connectId: device?.id,
446
277
  });
447
278
  if (this.runPromise) {
448
- const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
449
- this.runPromise.reject(error);
450
- this.rejectAllProtocolV2Frames(error);
279
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
451
280
  }
452
281
  } catch (e) {
453
282
  Log?.debug('device disconnect error: ', e);
454
283
  } finally {
455
- this.release(uuid, true);
284
+ this.release(uuid);
456
285
  }
457
286
  });
458
287
  }
@@ -475,6 +304,7 @@ export default class ReactNativeBleTransport {
475
304
  e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
476
305
  e.errorCode === BleErrorCode.OperationCancelled
477
306
  ) {
307
+ connectOptions = {};
478
308
  device = await device.connect();
479
309
  } else if (e.errorCode !== BleErrorCode.DeviceAlreadyConnected) {
480
310
  throw e;
@@ -489,18 +319,7 @@ export default class ReactNativeBleTransport {
489
319
  transport.device = device;
490
320
  transport.writeCharacteristic = writeCharacteristic;
491
321
  transport.notifyCharacteristic = notifyCharacteristic;
492
- const monitorToken = this.nextMonitorToken;
493
- this.nextMonitorToken += 1;
494
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
495
- transport.monitorToken = monitorToken;
496
- transport.notifyTransactionId = notifyTransactionId;
497
- this.monitorTokens.set(uuid, monitorToken);
498
- transport.notifySubscription = this._monitorCharacteristic(
499
- notifyCharacteristic,
500
- uuid,
501
- monitorToken,
502
- notifyTransactionId
503
- );
322
+ transport.notifySubscription = this._monitorCharacteristic(notifyCharacteristic, uuid);
504
323
  this.attachDisconnectSubscription(transport, device, uuid);
505
324
  } finally {
506
325
  this.firmwareUploadWriteRecoveryIds.delete(uuid);
@@ -546,11 +365,11 @@ export default class ReactNativeBleTransport {
546
365
  blePlxManager.startDeviceScan(
547
366
  getBluetoothServiceUuids(),
548
367
  {
549
- allowDuplicates: true,
550
368
  scanMode: ScanMode.LowLatency,
551
369
  },
552
370
  (error, device) => {
553
371
  if (error) {
372
+ Log?.debug('ble scan manager: ', blePlxManager);
554
373
  Log?.debug('ble scan error: ', error);
555
374
  if (
556
375
  [BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
@@ -573,20 +392,14 @@ export default class ReactNativeBleTransport {
573
392
  return;
574
393
  }
575
394
 
576
- const displayName = getDeviceDisplayName(device);
577
- const isOneKey =
578
- isOnekeyDevice(device?.name ?? null, device?.id) ||
579
- isOnekeyDevice(device?.localName ?? null, device?.id) ||
580
- hasKnownOneKeyService(device);
581
- if (isOneKey) {
395
+ if (isOnekeyDevice(getBleIdentityName(device), device?.id)) {
396
+ Log?.debug('search device start ======================');
397
+ const { name, localName, id } = device ?? {};
398
+ Log?.debug(
399
+ `device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
400
+ );
582
401
  addDevice(device as unknown as Device);
583
- } else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
584
- Log?.debug('[ReactNativeBleTransport] Pro2-like BLE device was not accepted:', {
585
- name: device?.name,
586
- localName: device?.localName,
587
- id: device?.id,
588
- serviceUUIDs: device?.serviceUUIDs,
589
- });
402
+ Log?.debug('search device end ======================\n');
590
403
  }
591
404
  }
592
405
  );
@@ -598,6 +411,7 @@ export default class ReactNativeBleTransport {
598
411
  const hasCachedServiceUuid = Boolean(serviceUUIDs?.length);
599
412
  const keepDevice = Platform.OS === 'ios' || hasCachedServiceUuid;
600
413
  if (keepDevice) {
414
+ Log?.debug('search connected peripheral: ', device.id);
601
415
  addDevice(device as unknown as Device);
602
416
  }
603
417
  }
@@ -606,22 +420,7 @@ export default class ReactNativeBleTransport {
606
420
 
607
421
  const addDevice = (device: Device) => {
608
422
  if (deviceList.every(d => d.id !== device.id)) {
609
- const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
610
- const protocolHint = inferProtocolHintFromDeviceName(displayName);
611
- if (protocolHint) {
612
- this.deviceProtocolHints.set(device.id, protocolHint);
613
- }
614
- deviceList.push({
615
- ...device,
616
- name: displayName,
617
- commType: 'ble',
618
- } as IOneKeyDevice);
619
- Log?.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
620
- deviceId: device.id,
621
- name: displayName,
622
- serviceUUIDs: device.serviceUUIDs,
623
- protocolHint,
624
- });
423
+ deviceList.push({ ...device, commType: 'ble' } as IOneKeyDevice);
625
424
  }
626
425
  };
627
426
 
@@ -633,40 +432,25 @@ export default class ReactNativeBleTransport {
633
432
  }
634
433
 
635
434
  async acquire(input: BleAcquireInput) {
636
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
435
+ const { uuid, forceCleanRunPromise } = input;
637
436
 
638
437
  if (!uuid) {
639
438
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
640
439
  }
641
440
 
642
- const cachedTransport = transportCache[uuid];
643
- if (cachedTransport) {
644
- const cachedProtocol = this.deviceProtocol.get(uuid);
645
- const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
646
- if (
647
- isCachedDeviceConnected &&
648
- cachedProtocol &&
649
- (!expectedProtocol || cachedProtocol === expectedProtocol)
650
- ) {
651
- Log?.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
652
- return { uuid, protocolType: cachedProtocol };
653
- }
441
+ let device: Device | null = null;
654
442
 
443
+ if (transportCache[uuid]) {
655
444
  /**
656
- * If the transport is not reusable due to a protocol mismatch or stale
657
- * connection, clean it up before creating a new transport instance.
445
+ * If the transport is not released due to an exception operation
446
+ * it will be handled again here
658
447
  */
659
- Log?.debug('transport not reusable, will release: ', uuid);
660
- await this.release(uuid, true);
448
+ Log?.debug('transport not be released, will release: ', uuid);
449
+ await this.release(uuid);
661
450
  }
662
451
 
663
- let device: Device | null = null;
664
-
665
452
  if (forceCleanRunPromise && this.runPromise) {
666
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
667
- this.runPromise.reject(error);
668
- this.rejectAllProtocolV2Frames(error);
669
- this.runPromise = null;
453
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
670
454
  Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
671
455
  }
672
456
 
@@ -678,12 +462,11 @@ export default class ReactNativeBleTransport {
678
462
  throw error;
679
463
  }
680
464
 
465
+ // check device is bonded
681
466
  if (Platform.OS === 'android') {
682
467
  const bondState = await pairDevice(uuid);
683
468
  if (bondState.bonding) {
684
469
  await onDeviceBondState(uuid);
685
- } else if (!bondState.bonded) {
686
- throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
687
470
  }
688
471
  }
689
472
 
@@ -709,6 +492,7 @@ export default class ReactNativeBleTransport {
709
492
  e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
710
493
  e.errorCode === BleErrorCode.OperationCancelled
711
494
  ) {
495
+ connectOptions = {};
712
496
  Log?.debug('first try to reconnect without params');
713
497
  device = await blePlxManager.connectToDevice(uuid);
714
498
  } else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
@@ -728,16 +512,17 @@ export default class ReactNativeBleTransport {
728
512
  Log?.debug('not connected, try to connect to device: ', uuid);
729
513
 
730
514
  try {
731
- device = await device.connect(connectOptions);
515
+ await device.connect(connectOptions);
732
516
  } catch (e) {
733
517
  Log?.debug('not connected, try to connect to device has error: ', e);
734
518
  if (
735
519
  e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
736
520
  e.errorCode === BleErrorCode.OperationCancelled
737
521
  ) {
522
+ connectOptions = {};
738
523
  Log?.debug('second try to reconnect without params');
739
524
  try {
740
- device = await device.connect();
525
+ await device.connect();
741
526
  } catch (e) {
742
527
  Log?.debug('last try to reconnect error: ', e);
743
528
  // last try to reconnect device if this issue exists
@@ -745,7 +530,7 @@ export default class ReactNativeBleTransport {
745
530
  if (e.errorCode === BleErrorCode.OperationCancelled) {
746
531
  Log?.debug('last try to reconnect');
747
532
  await device.cancelConnection();
748
- device = await device.connect();
533
+ await device.connect();
749
534
  }
750
535
  }
751
536
  } else {
@@ -754,50 +539,18 @@ export default class ReactNativeBleTransport {
754
539
  }
755
540
  }
756
541
 
757
- device = await requestAndroidMtu(device);
758
542
  const { writeCharacteristic, notifyCharacteristic } = await this.resolveCharacteristics(device);
759
543
 
760
- const protocolHint = expectedProtocol
761
- ? undefined
762
- : this.deviceProtocolHints.get(uuid) ??
763
- inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
764
-
765
544
  // release transport before new transport instance
766
- await this.release(uuid, true);
767
- if (protocolHint) {
768
- this.deviceProtocolHints.set(uuid, protocolHint);
769
- }
545
+ await this.release(uuid);
770
546
 
771
547
  const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
772
- if (Platform.OS === 'android') {
773
- transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
774
- }
775
- const monitorToken = this.nextMonitorToken;
776
- this.nextMonitorToken += 1;
777
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
778
- transport.monitorToken = monitorToken;
779
- transport.notifyTransactionId = notifyTransactionId;
780
- this.monitorTokens.set(uuid, monitorToken);
781
548
  transport.notifySubscription = this._monitorCharacteristic(
782
549
  transport.notifyCharacteristic,
783
- uuid,
784
- monitorToken,
785
- notifyTransactionId
550
+ uuid
786
551
  );
787
552
  transportCache[uuid] = transport;
788
553
 
789
- this.protocolV2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
790
-
791
- if (Platform.OS === 'ios') {
792
- await new Promise<void>(resolve => {
793
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
794
- });
795
- } else if (Platform.OS === 'android') {
796
- await delay(ANDROID_NOTIFY_READY_DELAY_MS);
797
- }
798
-
799
- const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
800
-
801
554
  this.emitter?.emit('device-connect', {
802
555
  name: device.name,
803
556
  id: device.id,
@@ -806,19 +559,13 @@ export default class ReactNativeBleTransport {
806
559
 
807
560
  this.attachDisconnectSubscription(transport, device, uuid);
808
561
 
809
- return { uuid, protocolType };
562
+ return { uuid };
810
563
  }
811
564
 
812
- _monitorCharacteristic(
813
- characteristic: Characteristic,
814
- uuid: string,
815
- monitorToken: number,
816
- notifyTransactionId: string
817
- ): Subscription {
565
+ _monitorCharacteristic(characteristic: Characteristic, uuid: string): Subscription {
818
566
  let bufferLength = 0;
819
567
  let buffer: any[] = [];
820
568
  const subscription = characteristic.monitor((error, c) => {
821
- const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
822
569
  if (error) {
823
570
  Log?.debug(
824
571
  `error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
@@ -829,33 +576,6 @@ export default class ReactNativeBleTransport {
829
576
  Log?.debug('notify error ignored during FirmwareUpload write recovery: ', uuid);
830
577
  return;
831
578
  }
832
- if (!isCurrentMonitor) {
833
- Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
834
- return;
835
- }
836
- if (this.deviceProtocol.get(uuid) === 'V2') {
837
- let errorCode:
838
- | typeof HardwareErrorCode.BleDeviceBondError
839
- | typeof HardwareErrorCode.BleCharacteristicNotifyError
840
- | typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
841
- | typeof HardwareErrorCode.BleTimeoutError =
842
- HardwareErrorCode.BleCharacteristicNotifyError;
843
- if (error.reason?.includes('The connection has timed out unexpectedly')) {
844
- errorCode = HardwareErrorCode.BleTimeoutError;
845
- } else if (error.reason?.includes('Encryption is insufficient')) {
846
- errorCode = HardwareErrorCode.BleDeviceBondError;
847
- } else if (
848
- error.reason?.includes('Cannot write client characteristic config descriptor') ||
849
- error.reason?.includes('Cannot find client characteristic config descriptor') ||
850
- error.reason?.includes('The handle is invalid') ||
851
- error.reason?.includes('Writing is not permitted') ||
852
- error.reason?.includes('notify change failed for device')
853
- ) {
854
- errorCode = HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
855
- }
856
- this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
857
- return;
858
- }
859
579
  if (this.runPromise) {
860
580
  let ERROR:
861
581
  | typeof HardwareErrorCode.BleDeviceBondError
@@ -875,45 +595,27 @@ export default class ReactNativeBleTransport {
875
595
  error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
876
596
  error.reason?.includes('notify change failed for device')
877
597
  ) {
878
- const notifyError = ERRORS.TypedError(
879
- HardwareErrorCode.BleCharacteristicNotifyChangeFailure
598
+ this.runPromise.reject(
599
+ ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotifyChangeFailure)
880
600
  );
881
- this.runPromise.reject(notifyError);
882
- this.rejectAllProtocolV2Frames(notifyError);
883
601
  Log?.debug(
884
602
  `${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
885
603
  );
886
604
  return;
887
605
  }
888
- const notifyError = ERRORS.TypedError(ERROR);
889
- this.runPromise.reject(notifyError);
890
- this.rejectAllProtocolV2Frames(notifyError);
606
+ this.runPromise.reject(ERRORS.TypedError(ERROR));
891
607
  Log?.debug(': monitor notify error, and has unreleased Promise', Error);
892
608
  }
893
609
 
894
610
  return;
895
611
  }
896
612
 
897
- if (!isCurrentMonitor) {
898
- Log?.debug('monitor data ignored for stale transport: ', uuid, notifyTransactionId);
899
- return;
900
- }
901
-
902
613
  if (!c) {
903
614
  throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
904
615
  }
905
616
 
906
617
  try {
907
618
  const data = Buffer.from(c.value as string, 'base64');
908
- const protocol = this.deviceProtocol.get(uuid);
909
- if (!protocol) {
910
- Log?.debug('monitor data ignored before protocol detection: ', uuid);
911
- return;
912
- }
913
- if (protocol === 'V2') {
914
- this.handleProtocolV2Notification(uuid, monitorToken, new Uint8Array(data));
915
- return;
916
- }
917
619
  // console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
918
620
  if (isHeaderChunk(data)) {
919
621
  bufferLength = data.readInt32BE(5);
@@ -922,7 +624,7 @@ export default class ReactNativeBleTransport {
922
624
  buffer = buffer.concat([...data]);
923
625
  }
924
626
 
925
- if (buffer.length - PROTOCOL_V1_MESSAGE_HEADER_SIZE >= bufferLength) {
627
+ if (buffer.length - COMMON_HEADER_SIZE >= bufferLength) {
926
628
  const value = Buffer.from(buffer);
927
629
  // console.log(
928
630
  // '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
@@ -936,41 +638,17 @@ export default class ReactNativeBleTransport {
936
638
  }
937
639
  } catch (error) {
938
640
  Log?.debug('monitor data error: ', error);
939
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
940
- if (this.deviceProtocol.get(uuid) === 'V2') {
941
- this.rejectProtocolV2Frames(uuid, notifyError);
942
- } else {
943
- this.runPromise?.reject(notifyError);
944
- }
641
+ this.runPromise?.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
945
642
  }
946
- }, notifyTransactionId);
643
+ }, uuid);
947
644
 
948
645
  return subscription;
949
646
  }
950
647
 
951
- async release(uuid: string, onclose = false) {
648
+ async release(uuid: string) {
952
649
  const transport = transportCache[uuid];
953
- await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
954
- if (this.runPromise) {
955
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
956
- this.runPromise.reject(error);
957
- this.runPromise = null;
958
- this.rejectAllProtocolV2Frames(error);
959
- } else {
960
- this.resetProtocolV2Frames(uuid);
961
- }
962
-
963
- if (Platform.OS === 'android' && !onclose && transport) {
964
- this.protocolV2Assemblers.get(uuid)?.reset();
965
- this.resetProtocolV2Frames(uuid);
966
- return Promise.resolve(true);
967
- }
968
650
 
969
651
  if (transport) {
970
- if (this.monitorTokens.get(uuid) === transport.monitorToken) {
971
- this.monitorTokens.delete(uuid);
972
- }
973
-
974
652
  // Clean up disconnect subscription first to prevent callbacks on released transport
975
653
  Log?.debug('release: removing disconnect subscription for device: ', uuid);
976
654
  transport.disconnectSubscription?.remove();
@@ -984,27 +662,12 @@ export default class ReactNativeBleTransport {
984
662
  transport.notifySubscription?.remove();
985
663
  transport.notifySubscription = undefined;
986
664
 
987
- if (transport.notifyTransactionId) {
988
- try {
989
- await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
990
- } catch (e) {
991
- Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
992
- }
993
- }
994
-
995
665
  delete transportCache[uuid];
996
- }
997
666
 
998
- this.deviceProtocol.delete(uuid);
999
- // Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
1000
- this.protocolV2Assemblers.get(uuid)?.reset();
1001
- this.protocolV2Assemblers.delete(uuid);
1002
- this.resetProtocolV2Frames(uuid);
1003
-
1004
- try {
1005
- await this.blePlxManager?.cancelTransaction(uuid);
1006
- } catch (e) {
1007
- Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
667
+ // Temporary close the Android disconnect after each request
668
+ if (Platform.OS === 'android') {
669
+ // await this.blePlxManager?.cancelDeviceConnection(uuid);
670
+ }
1008
671
  }
1009
672
 
1010
673
  return Promise.resolve(true);
@@ -1014,12 +677,7 @@ export default class ReactNativeBleTransport {
1014
677
  await this.call(session, name, data);
1015
678
  }
1016
679
 
1017
- async call(
1018
- uuid: string,
1019
- name: string,
1020
- data: Record<string, unknown>,
1021
- options?: TransportCallOptions
1022
- ) {
680
+ async call(uuid: string, name: string, data: Record<string, unknown>) {
1023
681
  if (this.stopped) {
1024
682
  // eslint-disable-next-line prefer-promise-reject-errors
1025
683
  return Promise.reject(ERRORS.TypedError('Transport stopped.'));
@@ -1028,44 +686,33 @@ export default class ReactNativeBleTransport {
1028
686
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
1029
687
  }
1030
688
 
1031
- const protocol = this.getProtocolType(uuid);
1032
- if (!protocol) {
1033
- throw ERRORS.TypedError(
1034
- HardwareErrorCode.RuntimeError,
1035
- `Device protocol has not been detected for ${uuid}`
1036
- );
1037
- }
1038
- Log?.debug('transport call', createTransportCallLog(name, protocol, data));
1039
-
1040
- if (protocol === 'V2') {
1041
- return this.callProtocolV2(uuid, name, data, options);
1042
- }
1043
-
1044
689
  const forceRun = name === 'Initialize' || name === 'Cancel';
690
+
691
+ Log?.debug('transport-react-native call this.runPromise', this.runPromise);
1045
692
  if (this.runPromise && !forceRun) {
1046
693
  throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
1047
694
  }
1048
695
 
1049
- return this.callProtocolV1(uuid, name, data, options);
1050
- }
1051
-
1052
- private async callProtocolV1(
1053
- uuid: string,
1054
- name: string,
1055
- data: Record<string, unknown>,
1056
- options?: TransportCallOptions
1057
- ) {
1058
- if (!this._messages) {
1059
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
696
+ const transport = transportCache[uuid];
697
+ if (!transport) {
698
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
1060
699
  }
1061
700
 
1062
- const transport = this.getCachedTransport(uuid);
1063
- const runPromise = createDeferred<string>();
1064
- runPromise.promise.catch(() => undefined);
1065
- this.runPromise = runPromise;
701
+ this.runPromise = createDeferred();
1066
702
  const messages = this._messages;
1067
- const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1068
- let timeout: ReturnType<typeof setTimeout> | undefined;
703
+ // Upload resources on low-end phones may OOM
704
+ if (name === 'ResourceUpdate' || name === 'ResourceAck') {
705
+ Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
706
+ file_name: data?.file_name,
707
+ hash: data?.hash,
708
+ });
709
+ } else if (LogBlockCommand.has(name)) {
710
+ Log?.debug('transport-react-native', 'call-', ' name: ', name);
711
+ } else {
712
+ Log?.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data);
713
+ }
714
+
715
+ const buffers = buildBuffers(messages, name, data);
1069
716
 
1070
717
  async function writeChunkedData(
1071
718
  buffers: ByteBuffer[],
@@ -1139,13 +786,14 @@ export default class ReactNativeBleTransport {
1139
786
  }
1140
787
  );
1141
788
  } else if (name === 'FirmwareUpload') {
1142
- Log?.debug('[ReactNativeBleTransport] Firmware upload transport configured', {
789
+ Log?.debug('[ReactNativeBleTransport] FirmwareUpload write uses throttled BLE packets:', {
1143
790
  packetCapacity: FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY,
1144
791
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1145
792
  pauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
1146
793
  flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
1147
794
  maxRetries: FIRMWARE_UPLOAD_WRITE_MAX_RETRIES,
1148
795
  });
796
+
1149
797
  await writeFirmwareUploadChunkedData(
1150
798
  buffers,
1151
799
  async data => {
@@ -1199,6 +847,7 @@ export default class ReactNativeBleTransport {
1199
847
  for (const o of buffers) {
1200
848
  const outData = o.toString('base64');
1201
849
  // Upload resources on low-end phones may OOM
850
+ // this.Log.debug('send hex strting: ', o.toString('hex'));
1202
851
  try {
1203
852
  await transport.writeCharacteristic.writeWithoutResponse(outData);
1204
853
  } catch (e) {
@@ -1216,40 +865,20 @@ export default class ReactNativeBleTransport {
1216
865
  }
1217
866
 
1218
867
  try {
1219
- const response = await Promise.race([
1220
- runPromise.promise,
1221
- new Promise<never>((_, reject) => {
1222
- if (options?.timeoutMs) {
1223
- timeout = setTimeout(() => {
1224
- const error = ERRORS.TypedError(
1225
- HardwareErrorCode.BleTimeoutError,
1226
- `BLE response timeout after ${options.timeoutMs}ms for ${name}`
1227
- );
1228
- runPromise.reject(error);
1229
- reject(error);
1230
- }, options.timeoutMs);
1231
- }
1232
- }),
1233
- ]);
868
+ const response = await this.runPromise.promise;
1234
869
 
1235
870
  if (typeof response !== 'string') {
1236
871
  throw new Error('Returning data is not string.');
1237
872
  }
1238
873
 
1239
- const jsonData = ProtocolV1.decodeMessage(messages, response);
874
+ Log?.debug('receive data: ', response);
875
+ const jsonData = receiveOne(messages, response);
1240
876
  return check.call(jsonData);
1241
877
  } catch (e) {
1242
- if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
1243
- Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe call failed:', e);
1244
- } else {
1245
- Log?.error('call error: ', e);
1246
- }
878
+ Log?.error('call error: ', e);
1247
879
  throw e;
1248
880
  } finally {
1249
- if (timeout) clearTimeout(timeout);
1250
- if (this.runPromise === runPromise) {
1251
- this.runPromise = null;
1252
- }
881
+ this.runPromise = null;
1253
882
  }
1254
883
  }
1255
884
 
@@ -1258,7 +887,7 @@ export default class ReactNativeBleTransport {
1258
887
  }
1259
888
 
1260
889
  async disconnect(session: string) {
1261
- await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
890
+ Log?.debug('transport-react-native transport resetSession: ', session);
1262
891
  const transport = transportCache[session];
1263
892
 
1264
893
  // Clean up disconnect subscription first to prevent onDisconnected callback
@@ -1316,10 +945,6 @@ export default class ReactNativeBleTransport {
1316
945
  if (transportCache[session]) {
1317
946
  delete transportCache[session];
1318
947
  }
1319
- this.deviceProtocol.delete(session);
1320
- this.deviceProtocolHints.delete(session);
1321
- this.protocolV2Assemblers.delete(session);
1322
- this.resetProtocolV2Frames(session);
1323
948
 
1324
949
  // emit the disconnect event
1325
950
  try {
@@ -1342,374 +967,4 @@ export default class ReactNativeBleTransport {
1342
967
  }
1343
968
  this.runPromise = null;
1344
969
  }
1345
-
1346
- private getCachedTransport(uuid: string) {
1347
- const transport = transportCache[uuid];
1348
- if (!transport) {
1349
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
1350
- }
1351
- return transport;
1352
- }
1353
-
1354
- private createProtocolMismatchError(expected: ProtocolType) {
1355
- return ERRORS.TypedError(
1356
- HardwareErrorCode.RuntimeError,
1357
- `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
1358
- );
1359
- }
1360
-
1361
- private createProtocolDetectionError() {
1362
- return ERRORS.TypedError(
1363
- HardwareErrorCode.BleTimeoutError,
1364
- 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
1365
- );
1366
- }
1367
-
1368
- private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
1369
- if (this.deviceProtocol.get(uuid) === protocol) {
1370
- this.deviceProtocol.delete(uuid);
1371
- }
1372
- }
1373
-
1374
- private async detectProtocol(
1375
- uuid: string,
1376
- expectedProtocol?: ProtocolType,
1377
- protocolHint?: ProtocolType
1378
- ): Promise<ProtocolType> {
1379
- if (expectedProtocol === 'V1') {
1380
- if (await this.probeProtocolV1(uuid)) {
1381
- this.deviceProtocol.set(uuid, 'V1');
1382
- Log?.debug('[ReactNativeBleTransport] protocol detected', {
1383
- deviceId: uuid,
1384
- protocol: 'V1',
1385
- source: 'expected',
1386
- });
1387
- return 'V1';
1388
- }
1389
- throw this.createProtocolMismatchError(expectedProtocol);
1390
- }
1391
-
1392
- if (expectedProtocol === 'V2') {
1393
- // Skip probing when the caller explicitly confirms V2, such as reconnect after a
1394
- // firmware reboot where expectedProtocol carries the previously probed result.
1395
- this.deviceProtocol.set(uuid, 'V2');
1396
- Log?.debug('[ReactNativeBleTransport] protocol detected', {
1397
- deviceId: uuid,
1398
- protocol: 'V2',
1399
- source: 'expected',
1400
- });
1401
- return 'V2';
1402
- }
1403
-
1404
- // Protocol must be actively probed after connection. Name, PID, and descriptors only
1405
- // influence probe order; a V2 hint probes V2 first and falls back to V1.
1406
- const probeOrder: ProtocolType[] =
1407
- protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1408
-
1409
- for (let i = 0; i < probeOrder.length; i += 1) {
1410
- const protocol = probeOrder[i];
1411
- if (i > 0) {
1412
- // Reset subscriptions and buffers after a failed probe before trying another protocol.
1413
- await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
1414
- }
1415
- const detected =
1416
- protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
1417
- if (detected) {
1418
- this.deviceProtocol.set(uuid, protocol);
1419
- Log?.debug('[ReactNativeBleTransport] protocol detected', {
1420
- deviceId: uuid,
1421
- protocol,
1422
- source: 'probe',
1423
- });
1424
- return protocol;
1425
- }
1426
- }
1427
-
1428
- this.deviceProtocol.delete(uuid);
1429
- throw this.createProtocolDetectionError();
1430
- }
1431
-
1432
- private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
1433
- const transport = transportCache[uuid];
1434
- await this.protocolV2Links.invalidateLink(
1435
- uuid,
1436
- `Reset notify state after Protocol ${protocol} probe`
1437
- );
1438
- this.protocolV2Assemblers.get(uuid)?.reset();
1439
- this.resetProtocolV2Frames(uuid);
1440
- if (this.runPromise) {
1441
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
1442
- this.runPromise.reject(error);
1443
- this.runPromise = null;
1444
- }
1445
-
1446
- if (!transport) return;
1447
-
1448
- const previousNotifyTransactionId = transport.notifyTransactionId;
1449
- if (this.monitorTokens.get(uuid) === transport.monitorToken) {
1450
- this.monitorTokens.delete(uuid);
1451
- }
1452
- transport.notifySubscription?.remove();
1453
- transport.notifySubscription = undefined;
1454
- if (previousNotifyTransactionId) {
1455
- try {
1456
- await this.blePlxManager?.cancelTransaction(previousNotifyTransactionId);
1457
- } catch (error) {
1458
- Log?.debug(
1459
- `[ReactNativeBleTransport] cancel notify after Protocol ${protocol} probe failed:`,
1460
- error?.message || error
1461
- );
1462
- }
1463
- }
1464
-
1465
- const monitorToken = this.nextMonitorToken;
1466
- this.nextMonitorToken += 1;
1467
- const notifyTransactionId = `${uuid}:notify:${monitorToken}`;
1468
- transport.monitorToken = monitorToken;
1469
- transport.notifyTransactionId = notifyTransactionId;
1470
- this.monitorTokens.set(uuid, monitorToken);
1471
- transport.notifySubscription = this._monitorCharacteristic(
1472
- transport.notifyCharacteristic,
1473
- uuid,
1474
- monitorToken,
1475
- notifyTransactionId
1476
- );
1477
- if (Platform.OS === 'ios') {
1478
- await new Promise<void>(resolve => {
1479
- setTimeout(resolve, IOS_NOTIFY_READY_DELAY_MS);
1480
- });
1481
- }
1482
- }
1483
-
1484
- private async probeProtocolV1(uuid: string) {
1485
- if (!this._messages) {
1486
- return false;
1487
- }
1488
-
1489
- try {
1490
- this.deviceProtocol.set(uuid, 'V1');
1491
- await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1492
- return true;
1493
- } catch (error) {
1494
- this.clearProbeProtocol(uuid, 'V1');
1495
- Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize probe failed:', error);
1496
- return false;
1497
- }
1498
- }
1499
-
1500
- private async probeProtocolV2(uuid: string) {
1501
- if (!this._messages || !this._messagesV2) {
1502
- return false;
1503
- }
1504
-
1505
- this.deviceProtocol.set(uuid, 'V2');
1506
- this.protocolV2Assemblers.get(uuid)?.reset();
1507
- const detected = await probeProtocolV2Helper({
1508
- call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
1509
- this.callProtocolV2(uuid, name, data, options),
1510
- timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
1511
- logger: Log,
1512
- logPrefix: 'ProtocolV2 RN-BLE',
1513
- onProbeFailed: () => {
1514
- this.protocolV2Assemblers.get(uuid)?.reset();
1515
- this.resetProtocolV2Frames(uuid);
1516
- },
1517
- });
1518
- if (!detected) {
1519
- this.clearProbeProtocol(uuid, 'V2');
1520
- }
1521
- return detected;
1522
- }
1523
-
1524
- private handleProtocolV2Notification(uuid: string, monitorToken: number, data: Uint8Array) {
1525
- try {
1526
- if (this.monitorTokens.get(uuid) !== monitorToken) return;
1527
-
1528
- if (data.length === 0) return;
1529
-
1530
- const assembler = this.protocolV2Assemblers.get(uuid);
1531
- if (!assembler) return;
1532
-
1533
- for (const frameData of assembler.drain(data)) {
1534
- this.resolveProtocolV2Frame(uuid, frameData);
1535
- }
1536
- } catch (error) {
1537
- Log?.debug('[ReactNativeBleTransport] Protocol V2 notification error:', error);
1538
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
1539
- this.rejectProtocolV2Frames(uuid, notifyError);
1540
- this.protocolV2Links
1541
- .invalidateLink(uuid, `Protocol V2 notification error: ${error}`)
1542
- .catch(invalidateError =>
1543
- Log?.debug(
1544
- '[ReactNativeBleTransport] Protocol V2 notify cleanup failed:',
1545
- invalidateError
1546
- )
1547
- );
1548
- }
1549
- }
1550
-
1551
- private getProtocolV2FrameQueue(uuid: string) {
1552
- let queue = this.protocolV2FrameQueues.get(uuid);
1553
- if (!queue) {
1554
- queue = [];
1555
- this.protocolV2FrameQueues.set(uuid, queue);
1556
- }
1557
- return queue;
1558
- }
1559
-
1560
- private resolveProtocolV2Frame(uuid: string, frame: Uint8Array) {
1561
- const framePromise = this.protocolV2FramePromises.get(uuid);
1562
- if (framePromise) {
1563
- framePromise.resolve(frame);
1564
- this.protocolV2FramePromises.delete(uuid);
1565
- return;
1566
- }
1567
- this.getProtocolV2FrameQueue(uuid).push(frame);
1568
- }
1569
-
1570
- private rejectAllProtocolV2Frames(error: Error) {
1571
- this.protocolV2FrameQueues.clear();
1572
- for (const framePromise of this.protocolV2FramePromises.values()) {
1573
- framePromise.reject(error);
1574
- }
1575
- this.protocolV2FramePromises.clear();
1576
- }
1577
-
1578
- private resetProtocolV2Frames(uuid: string) {
1579
- this.protocolV2FrameQueues.delete(uuid);
1580
- this.protocolV2FramePromises.delete(uuid);
1581
- }
1582
-
1583
- private rejectProtocolV2Frames(uuid: string, error: Error) {
1584
- this.protocolV2FrameQueues.delete(uuid);
1585
- const framePromise = this.protocolV2FramePromises.get(uuid);
1586
- if (framePromise) {
1587
- this.protocolV2FramePromises.delete(uuid);
1588
- framePromise.reject(error);
1589
- }
1590
- }
1591
-
1592
- private async readProtocolV2Frame(uuid: string) {
1593
- const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
1594
- if (queuedFrame) {
1595
- return queuedFrame;
1596
- }
1597
-
1598
- const framePromise = createDeferred<Uint8Array>();
1599
- this.protocolV2FramePromises.set(uuid, framePromise);
1600
- try {
1601
- return await framePromise.promise;
1602
- } finally {
1603
- if (this.protocolV2FramePromises.get(uuid) === framePromise) {
1604
- this.protocolV2FramePromises.delete(uuid);
1605
- }
1606
- }
1607
- }
1608
-
1609
- private async writeProtocolV2Frame(transport: BleTransport, frame: Uint8Array) {
1610
- const tuning = getProtocolV2BleTuning();
1611
- const packetCapacity = resolveProtocolV2PacketCapacity({
1612
- platform: Platform.OS,
1613
- iosPacketLength: tuning.iosPacketLength,
1614
- androidPacketLength: tuning.androidPacketLength,
1615
- mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
1616
- });
1617
- for (let offset = 0; offset < frame.length; offset += packetCapacity) {
1618
- const chunk = frame.slice(offset, offset + packetCapacity);
1619
- const base64 = Buffer.from(chunk).toString('base64');
1620
- await transport.writeCharacteristic.writeWithoutResponse(base64);
1621
- }
1622
- }
1623
-
1624
- private async callProtocolV2(
1625
- uuid: string,
1626
- name: string,
1627
- data: Record<string, unknown>,
1628
- options?: TransportCallOptions
1629
- ) {
1630
- if (!this._messages || !this._messagesV2) {
1631
- throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
1632
- }
1633
-
1634
- const callOptions = {
1635
- ...options,
1636
- // Align with V1 BLE and the USB V2 base transport: only arm a watchdog when the caller
1637
- // explicitly passes timeoutMs. Interactive acks (ButtonAck/PinMatrixAck/PassphraseAck)
1638
- // have their timeoutMs removed by DeviceCommands.stripInteractiveAckTimeout, so we must
1639
- // not fill in a 30s default here — that would hard-cap the time a user spends confirming
1640
- // a transaction or entering a PIN/passphrase on the device and tear down the BLE link.
1641
- timeoutMs: options?.timeoutMs,
1642
- };
1643
- const highVolumeWrite = LogBlockCommand.has(name);
1644
-
1645
- if (highVolumeWrite) {
1646
- const tuning = getProtocolV2BleTuning();
1647
- Log?.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1648
- name,
1649
- writeMode: 'withoutResponse',
1650
- packetCapacity: Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1651
- });
1652
- }
1653
-
1654
- try {
1655
- return await this.protocolV2Links.call(
1656
- uuid,
1657
- () => this.createProtocolV2Adapter(uuid),
1658
- name,
1659
- data,
1660
- callOptions
1661
- );
1662
- } catch (e) {
1663
- Log?.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
1664
- throw e;
1665
- }
1666
- }
1667
-
1668
- private createProtocolV2Adapter(uuid: string) {
1669
- const generation = this.monitorTokens.get(uuid) ?? 0;
1670
- const assertCurrentGeneration = () => {
1671
- if (this.monitorTokens.get(uuid) !== generation) {
1672
- throw new Error(`Protocol V2 monitor generation changed for ${uuid}`);
1673
- }
1674
- };
1675
-
1676
- return {
1677
- router: PROTOCOL_V2_CHANNEL_BLE_UART,
1678
- maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
1679
- generation,
1680
- prepareCall: () => {
1681
- assertCurrentGeneration();
1682
- this.protocolV2Assemblers.get(uuid)?.reset();
1683
- this.resetProtocolV2Frames(uuid);
1684
- },
1685
- writeFrame: async (frame: Uint8Array) => {
1686
- assertCurrentGeneration();
1687
- const currentTransport = this.getCachedTransport(uuid);
1688
- await this.writeProtocolV2Frame(currentTransport, frame);
1689
- },
1690
- readFrame: async () => {
1691
- assertCurrentGeneration();
1692
- const rxFrame = await this.readProtocolV2Frame(uuid);
1693
- if (!(rxFrame instanceof Uint8Array)) {
1694
- throw new Error('Protocol V2 response is not Uint8Array');
1695
- }
1696
- return rxFrame;
1697
- },
1698
- reset: (reason: string) => {
1699
- this.protocolV2Assemblers.get(uuid)?.reset();
1700
- this.rejectProtocolV2Frames(uuid, new Error(reason));
1701
- },
1702
- logger: Log,
1703
- logPrefix: 'ProtocolV2 RN-BLE',
1704
- createTimeoutError: (messageName: string, timeout: number) =>
1705
- ERRORS.TypedError(
1706
- HardwareErrorCode.BleTimeoutError,
1707
- `BLE response timeout after ${timeout}ms for ${messageName}`
1708
- ),
1709
- };
1710
- }
1711
-
1712
- getProtocolType(path: string): ProtocolType | undefined {
1713
- return this.deviceProtocol.get(path);
1714
- }
1715
970
  }