@onekeyfe/hd-transport-react-native 1.2.2-alpha.11 → 1.2.2-alpha.110

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/dist/index.js CHANGED
@@ -57,36 +57,73 @@ const bleLogger = {
57
57
  };
58
58
 
59
59
  const Logger = bleLogger;
60
+ const bondFailureReasons = {
61
+ 1: 'authentication_failed',
62
+ 2: 'rejected',
63
+ 3: 'canceled',
64
+ 4: 'device_unreachable',
65
+ 5: 'discovery_in_progress',
66
+ 6: 'timeout',
67
+ 7: 'repeated_attempts',
68
+ 8: 'remote_canceled',
69
+ 9: 'removed',
70
+ };
60
71
  const getConnectedDeviceIds = (serviceUuids) => BleUtils__default["default"].getConnectedPeripherals(serviceUuids);
61
72
  const pairDevice = (macAddress) => BleUtils__default["default"].pairDevice(macAddress);
62
- const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
73
+ const onDeviceBondState = (bleMacAddress, signal) => new Promise((resolve, reject) => {
74
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
75
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
76
+ return;
77
+ }
63
78
  const cleanup = () => {
64
79
  if (timeout) {
65
80
  clearTimeout(timeout);
66
81
  }
67
82
  if (cleanupListener)
68
83
  cleanupListener();
84
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
85
+ };
86
+ const onAbort = () => {
87
+ cleanup();
88
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
69
89
  };
90
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
70
91
  const timeout = setTimeout(() => {
71
92
  cleanup();
72
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
93
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', {
94
+ phase: 'bond',
95
+ reason: 'timeout',
96
+ }));
73
97
  }, 60 * 1000);
74
98
  const cleanupListener = BleUtils__default["default"].onDeviceBondState(peripheral => {
75
- var _a;
99
+ var _a, _b;
76
100
  if (((_a = peripheral.id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== bleMacAddress.toLowerCase()) {
77
101
  return;
78
102
  }
79
103
  const { bondState } = peripheral;
80
104
  const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
81
- const hasCanceled = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
105
+ const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
82
106
  Logger.debug('onDeviceBondState bondState:', bondState);
83
107
  if (hasBonded) {
84
108
  cleanup();
85
109
  resolve(peripheral);
86
110
  }
87
- else if (hasCanceled) {
111
+ else if (hasFailed) {
88
112
  cleanup();
89
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'bonding canceled'));
113
+ const nativeReason = 'reason' in bondState && typeof bondState.reason === 'number'
114
+ ? bondState.reason
115
+ : undefined;
116
+ const reason = nativeReason === undefined ? 'unknown' : (_b = bondFailureReasons[nativeReason]) !== null && _b !== void 0 ? _b : 'unknown';
117
+ const params = Object.assign({ phase: 'bond', reason }, (nativeReason === undefined ? {} : { nativeReason }));
118
+ if (reason === 'timeout') {
119
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', params));
120
+ }
121
+ else if (reason === 'canceled') {
122
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'Bluetooth pairing canceled', params));
123
+ }
124
+ else {
125
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed', params));
126
+ }
90
127
  }
91
128
  });
92
129
  });
@@ -194,6 +231,27 @@ const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject)
194
231
  }, ms);
195
232
  });
196
233
 
234
+ const BLE_PLX_DEVICE_DISCONNECTED = 201;
235
+ const IOS_PERIPHERAL_DISCONNECTED = 7;
236
+ const nativeErrorText$1 = (error) => [error.reason, error.message]
237
+ .filter((value) => typeof value === 'string')
238
+ .join(' ');
239
+ const isNativeBleDisconnectError = (error) => {
240
+ if (!error || typeof error !== 'object') {
241
+ return false;
242
+ }
243
+ const nativeError = error;
244
+ return (nativeError.errorCode === BLE_PLX_DEVICE_DISCONNECTED ||
245
+ nativeError.iosErrorCode === IOS_PERIPHERAL_DISCONNECTED);
246
+ };
247
+ const toBleDisconnectHardwareError = (error) => {
248
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
249
+ return error;
250
+ }
251
+ const nativeError = (error !== null && error !== void 0 ? error : {});
252
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, nativeErrorText$1(nativeError) || undefined);
253
+ };
254
+
197
255
  const ATT_INSUFFICIENT_AUTHENTICATION = 5;
198
256
  const ATT_INSUFFICIENT_ENCRYPTION = 15;
199
257
  const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
@@ -207,6 +265,8 @@ const isNativeBleStaleBondError = (error) => {
207
265
  const nativeError = error;
208
266
  if (nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
209
267
  nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
268
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
269
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
210
270
  nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION) {
211
271
  return true;
212
272
  }
@@ -256,6 +316,7 @@ class BleTransport {
256
316
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
257
317
  const Log = bleLogger;
258
318
  const transportCache = {};
319
+ let bleManagerResetPromise;
259
320
  const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
260
321
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
261
322
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
@@ -314,6 +375,17 @@ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
314
375
  const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
315
376
  typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
316
377
  error.message.startsWith(WEDGED_WRITE_MESSAGE);
378
+ const shouldRethrowProtocolProbeError = (error) => {
379
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
380
+ return (hdShared.isBleStaleBondHardwareError(error) ||
381
+ isNativeBleDisconnectError(error) ||
382
+ code === hdShared.HardwareErrorCode.BleDeviceNotBonded ||
383
+ code === hdShared.HardwareErrorCode.BleDeviceBondedCanceled ||
384
+ code === hdShared.HardwareErrorCode.BleDeviceDisconnected ||
385
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyError ||
386
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure ||
387
+ code === hdShared.HardwareErrorCode.BleWriteCharacteristicError);
388
+ };
317
389
  const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
318
390
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
319
391
  const IOS_NOTIFY_READY_DELAY_MS = 150;
@@ -404,10 +476,10 @@ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void
404
476
  }
405
477
  });
406
478
  const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
407
- function remapError(error, mapProtocolV2StaleBond) {
479
+ function remapError(error) {
408
480
  var _a;
409
481
  if (error instanceof reactNativeBlePlx.BleError) {
410
- if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
482
+ if (isNativeBleStaleBondError(error)) {
411
483
  throw toBleStaleBondHardwareError(error);
412
484
  }
413
485
  if ((error === null || error === void 0 ? void 0 : error.attErrorCode) === 22) {
@@ -427,6 +499,7 @@ class ReactNativeBleTransport {
427
499
  this.name = 'ReactNativeBleTransport';
428
500
  this.configured = false;
429
501
  this.stopped = false;
502
+ this.bondAbortController = new AbortController();
430
503
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
431
504
  this.runPromise = null;
432
505
  this.runPromiseDeviceId = null;
@@ -472,6 +545,7 @@ class ReactNativeBleTransport {
472
545
  this.androidPriorityResetTimers = new Map();
473
546
  this.nextMonitorToken = 1;
474
547
  this.lifecycleOperations = new Map();
548
+ this.scanCleanups = new Set();
475
549
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
476
550
  }
477
551
  init(logger, emitter) {
@@ -500,10 +574,38 @@ class ReactNativeBleTransport {
500
574
  listen() {
501
575
  }
502
576
  getPlxManager() {
503
- if (this.blePlxManager)
504
- return Promise.resolve(this.blePlxManager);
505
- this.blePlxManager = new reactNativeBlePlx.BleManager();
506
- return Promise.resolve(this.blePlxManager);
577
+ return __awaiter(this, void 0, void 0, function* () {
578
+ while (bleManagerResetPromise) {
579
+ yield this.waitForManagerReset();
580
+ }
581
+ if (this.stopped)
582
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
583
+ if (!this.blePlxManager)
584
+ this.blePlxManager = new reactNativeBlePlx.BleManager();
585
+ return this.blePlxManager;
586
+ });
587
+ }
588
+ waitForManagerReset() {
589
+ return __awaiter(this, void 0, void 0, function* () {
590
+ if (!bleManagerResetPromise)
591
+ return;
592
+ let timeout;
593
+ try {
594
+ yield Promise.race([
595
+ bleManagerResetPromise,
596
+ new Promise((_, reject) => {
597
+ timeout = setTimeout(() => reject(this.createWedgedBleSetupError()), BLE_CONNECT_TIMEOUT_MS);
598
+ }),
599
+ ]);
600
+ }
601
+ catch (_a) {
602
+ throw this.createWedgedBleSetupError();
603
+ }
604
+ finally {
605
+ if (timeout)
606
+ clearTimeout(timeout);
607
+ }
608
+ });
507
609
  }
508
610
  resolveCharacteristics(device) {
509
611
  return __awaiter(this, void 0, void 0, function* () {
@@ -668,30 +770,55 @@ class ReactNativeBleTransport {
668
770
  }
669
771
  enumerate() {
670
772
  return __awaiter(this, void 0, void 0, function* () {
671
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
672
- const deviceList = [];
673
- const blePlxManager = yield this.getPlxManager();
674
- try {
675
- yield subscribeBleOn(blePlxManager);
676
- }
677
- catch (error) {
678
- Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
679
- reject(error);
680
- return;
681
- }
682
- if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
683
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
684
- const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
685
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
686
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
687
- ]);
688
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
689
- if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
690
- resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
691
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
692
- return;
693
- }
773
+ const scanStartedAt = Date.now();
774
+ let firstDeviceMs;
775
+ const blePlxManager = yield this.getPlxManager();
776
+ yield subscribeBleOn(blePlxManager);
777
+ if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
778
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
779
+ const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
780
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
781
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
782
+ ]);
783
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
784
+ if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
785
+ resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
786
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
694
787
  }
788
+ }
789
+ if (this.stopped)
790
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
791
+ return new Promise((resolve, reject) => {
792
+ const deviceList = [];
793
+ let finished = false;
794
+ let scanCleanup;
795
+ const finishScan = (error) => {
796
+ if (scanCleanup)
797
+ return scanCleanup;
798
+ finished = true;
799
+ clearScanTimer();
800
+ scanCleanup = this.runNativeTeardown('scan', blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
801
+ yield blePlxManager.stopDeviceScan();
802
+ })).then(() => {
803
+ this.scanCleanups.delete(cancelScan);
804
+ if (error)
805
+ reject(error);
806
+ else
807
+ resolve(deviceList);
808
+ });
809
+ return scanCleanup;
810
+ };
811
+ const cancelScan = () => finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
812
+ this.scanCleanups.add(cancelScan);
813
+ const clearScanTimer = timer.timeout(() => {
814
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] scan completed', {
815
+ elapsedMs: Date.now() - scanStartedAt,
816
+ firstDeviceMs,
817
+ deviceCount: deviceList.length,
818
+ scanWindowMs: this.scanTimeout,
819
+ });
820
+ finishScan();
821
+ }, this.scanTimeout);
695
822
  blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
696
823
  allowDuplicates: true,
697
824
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
@@ -700,19 +827,17 @@ class ReactNativeBleTransport {
700
827
  if (error) {
701
828
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
702
829
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
703
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
830
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
704
831
  }
705
832
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothUnauthorized) {
706
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
833
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
707
834
  }
708
835
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.LocationServicesDisabled) {
709
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
710
- }
711
- else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) {
712
- timer.timeout(() => { }, this.scanTimeout);
836
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
713
837
  }
838
+ else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) ;
714
839
  else {
715
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
840
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
716
841
  }
717
842
  return;
718
843
  }
@@ -737,6 +862,8 @@ class ReactNativeBleTransport {
737
862
  });
738
863
  }
739
864
  });
865
+ if (finished)
866
+ return;
740
867
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
741
868
  for (const device of devices) {
742
869
  const localName = 'localName' in device && typeof device.localName === 'string'
@@ -752,10 +879,11 @@ class ReactNativeBleTransport {
752
879
  addDevice(device);
753
880
  }
754
881
  }
755
- });
882
+ }, error => Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral failed:', error));
756
883
  const addDevice = (device) => {
757
884
  var _a;
758
- if (deviceList.every(d => d.id !== device.id)) {
885
+ if (!finished && deviceList.every(d => d.id !== device.id)) {
886
+ firstDeviceMs !== null && firstDeviceMs !== void 0 ? firstDeviceMs : (firstDeviceMs = Date.now() - scanStartedAt);
759
887
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
760
888
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
761
889
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
@@ -765,16 +893,14 @@ class ReactNativeBleTransport {
765
893
  });
766
894
  }
767
895
  };
768
- timer.timeout(() => {
769
- blePlxManager.stopDeviceScan();
770
- resolve(deviceList);
771
- }, this.scanTimeout);
772
- }));
896
+ });
773
897
  });
774
898
  }
775
899
  installTransportForAcquire(uuid, device, characteristics) {
776
900
  return __awaiter(this, void 0, void 0, function* () {
777
901
  const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
902
+ if (this.stopped)
903
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
778
904
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
779
905
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
780
906
  const monitorToken = this.nextMonitorToken;
@@ -795,17 +921,23 @@ class ReactNativeBleTransport {
795
921
  else if (reactNative.Platform.OS === 'android') {
796
922
  yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
797
923
  }
924
+ if (this.stopped)
925
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
798
926
  const initialMtu = transport$1.mtuSize;
799
927
  let refreshAttempts = 0;
800
928
  if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
801
929
  shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
802
930
  refreshAttempts += 1;
803
931
  let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
932
+ if (this.stopped)
933
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
804
934
  transport$1.device = refreshedDevice;
805
935
  transport$1.mtuSize =
806
936
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
807
937
  if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
808
938
  yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
939
+ if (this.stopped)
940
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
809
941
  refreshAttempts += 1;
810
942
  refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
811
943
  transport$1.device = refreshedDevice;
@@ -813,6 +945,8 @@ class ReactNativeBleTransport {
813
945
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
814
946
  }
815
947
  }
948
+ if (this.stopped)
949
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
816
950
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
817
951
  platform: reactNative.Platform.OS,
818
952
  requested: getRequestedBleMtu(),
@@ -835,6 +969,8 @@ class ReactNativeBleTransport {
835
969
  acquireUnlocked(input) {
836
970
  var _a;
837
971
  return __awaiter(this, void 0, void 0, function* () {
972
+ if (this.stopped)
973
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
838
974
  const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
839
975
  const shouldMapProtocolV2StaleBond = expectedProtocol
840
976
  ? expectedProtocol === 'V2'
@@ -869,6 +1005,8 @@ class ReactNativeBleTransport {
869
1005
  if (isCachedDeviceConnected &&
870
1006
  cachedProtocol &&
871
1007
  (!expectedProtocol || cachedProtocol === expectedProtocol)) {
1008
+ if (this.stopped)
1009
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
872
1010
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
873
1011
  return { uuid, protocolType: cachedProtocol };
874
1012
  }
@@ -892,15 +1030,27 @@ class ReactNativeBleTransport {
892
1030
  Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
893
1031
  throw error;
894
1032
  }
1033
+ if (this.stopped)
1034
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
895
1035
  if (reactNative.Platform.OS === 'android') {
896
- const bondState = yield pairDevice(uuid);
897
- if (bondState.bonding) {
898
- yield onDeviceBondState(uuid);
1036
+ try {
1037
+ const bondState = yield pairDevice(uuid);
1038
+ if (bondState.bonding) {
1039
+ yield onDeviceBondState(uuid, this.bondAbortController.signal);
1040
+ }
1041
+ else if (!bondState.bonded) {
1042
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1043
+ }
899
1044
  }
900
- else if (!bondState.bonded) {
901
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1045
+ catch (error) {
1046
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1047
+ yield this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
1048
+ }));
1049
+ throw error;
902
1050
  }
903
1051
  }
1052
+ if (this.stopped)
1053
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
904
1054
  if (!device) {
905
1055
  const devices = yield blePlxManager.devices([uuid]);
906
1056
  [device] = devices;
@@ -931,7 +1081,7 @@ class ReactNativeBleTransport {
931
1081
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleAlreadyConnected);
932
1082
  }
933
1083
  else {
934
- remapError(e, shouldMapProtocolV2StaleBond);
1084
+ remapError(e);
935
1085
  }
936
1086
  }
937
1087
  }
@@ -955,23 +1105,44 @@ class ReactNativeBleTransport {
955
1105
  try {
956
1106
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
957
1107
  }
958
- catch (e) {
959
- Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
960
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1108
+ catch (fallbackError) {
1109
+ Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', fallbackError);
1110
+ if (fallbackError.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
961
1111
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
962
1112
  yield disconnectedDevice.cancelConnection();
963
1113
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
964
1114
  }
1115
+ else {
1116
+ remapError(fallbackError);
1117
+ }
965
1118
  }
966
1119
  }
967
1120
  else {
968
- remapError(e, shouldMapProtocolV2StaleBond);
1121
+ remapError(e);
969
1122
  }
970
1123
  }
971
1124
  }
1125
+ if (this.stopped)
1126
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1127
+ if (reactNative.Platform.OS === 'android' && !(yield device.isConnected().catch(() => false))) {
1128
+ const disconnectedDevice = device;
1129
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1130
+ yield Promise.all([
1131
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1132
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () => disconnectedDevice.cancelConnection()),
1133
+ ]);
1134
+ }));
1135
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'device is not connected');
1136
+ }
1137
+ if (this.stopped)
1138
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
972
1139
  device = yield resolveNegotiatedMtu(device);
1140
+ if (this.stopped)
1141
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
973
1142
  const acquiredDevice = device;
974
1143
  const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1144
+ if (this.stopped)
1145
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
975
1146
  const protocolHint = expectedProtocol
976
1147
  ? undefined
977
1148
  : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
@@ -1014,6 +1185,8 @@ class ReactNativeBleTransport {
1014
1185
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
1015
1186
  yield this.installTransportForAcquire(uuid, acquiredDevice);
1016
1187
  }));
1188
+ if (this.stopped)
1189
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1017
1190
  const currentTransport = transportCache[uuid];
1018
1191
  if (!currentTransport) {
1019
1192
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
@@ -1022,7 +1195,7 @@ class ReactNativeBleTransport {
1022
1195
  return { uuid, protocolType };
1023
1196
  }
1024
1197
  catch (error) {
1025
- if (hdShared.isBleStaleBondHardwareError(error)) {
1198
+ if (hdShared.isBleStaleBondHardwareError(error) || shouldRethrowProtocolProbeError(error)) {
1026
1199
  yield this.disconnectUnlocked(uuid);
1027
1200
  }
1028
1201
  else {
@@ -1435,7 +1608,43 @@ class ReactNativeBleTransport {
1435
1608
  });
1436
1609
  }
1437
1610
  stop() {
1611
+ var _a;
1612
+ if (this.stopPromise)
1613
+ return this.stopPromise;
1438
1614
  this.stopped = true;
1615
+ this.bondAbortController.abort();
1616
+ const deviceIds = new Set([
1617
+ ...this.monitorTokens.keys(),
1618
+ ...this.sessionProtocols.keys(),
1619
+ ...this.lifecycleOperations.keys(),
1620
+ ...(this.runPromiseDeviceId ? [this.runPromiseDeviceId] : []),
1621
+ ]);
1622
+ const scans = Array.from(this.scanCleanups, cleanup => cleanup());
1623
+ this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
1624
+ this.androidPriorityResetTimers.clear();
1625
+ this.androidHighPriorityDevices.clear();
1626
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1627
+ (_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.reject(error);
1628
+ this.runPromise = null;
1629
+ this.runPromiseDeviceId = null;
1630
+ deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1631
+ const manager = this.blePlxManager;
1632
+ const pendingConnections = manager
1633
+ ? Array.from(this.lifecycleOperations.keys(), uuid => this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1634
+ yield this.runBestEffortNativeOperation('stop: cancel pending device connection', () => manager.cancelDeviceConnection(uuid));
1635
+ })))
1636
+ : [];
1637
+ this.stopPromise = Promise.all([
1638
+ ...scans,
1639
+ ...pendingConnections,
1640
+ ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1641
+ ]).then(() => __awaiter(this, void 0, void 0, function* () {
1642
+ yield this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
1643
+ yield this.waitForManagerReset();
1644
+ this.blePlxManager = undefined;
1645
+ this.emitter = undefined;
1646
+ }));
1647
+ return this.stopPromise;
1439
1648
  }
1440
1649
  disconnect(session) {
1441
1650
  return __awaiter(this, void 0, void 0, function* () {
@@ -1565,13 +1774,27 @@ class ReactNativeBleTransport {
1565
1774
  });
1566
1775
  }
1567
1776
  cancel() {
1568
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1569
- if (this.runPromise) ;
1570
- this.runPromise = null;
1571
- this.runPromiseDeviceId = null;
1777
+ return __awaiter(this, void 0, void 0, function* () {
1778
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1779
+ const pending = this.runPromise;
1780
+ const deviceId = this.runPromiseDeviceId;
1781
+ if (pending) {
1782
+ pending.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled));
1783
+ if (this.runPromise === pending) {
1784
+ this.runPromise = null;
1785
+ this.runPromiseDeviceId = null;
1786
+ }
1787
+ if (deviceId)
1788
+ yield this.disconnect(deviceId);
1789
+ }
1790
+ });
1572
1791
  }
1573
1792
  connectWithTimeout(uuid, connect) {
1574
1793
  return __awaiter(this, void 0, void 0, function* () {
1794
+ if (this.stopped)
1795
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1796
+ const startedAt = Date.now();
1797
+ let succeeded = false;
1575
1798
  let timer;
1576
1799
  let timedOut = false;
1577
1800
  const pending = connect();
@@ -1586,6 +1809,7 @@ class ReactNativeBleTransport {
1586
1809
  }, BLE_CONNECT_TIMEOUT_MS);
1587
1810
  }),
1588
1811
  ]);
1812
+ succeeded = true;
1589
1813
  return result;
1590
1814
  }
1591
1815
  catch (error) {
@@ -1600,11 +1824,19 @@ class ReactNativeBleTransport {
1600
1824
  finally {
1601
1825
  if (timer)
1602
1826
  clearTimeout(timer);
1827
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] connect completed', {
1828
+ connectIdSuffix: uuid.slice(-8),
1829
+ elapsedMs: Date.now() - startedAt,
1830
+ succeeded,
1831
+ backstopExpired: timedOut,
1832
+ });
1603
1833
  }
1604
1834
  });
1605
1835
  }
1606
1836
  resolveCharacteristicsWithTimeout(uuid, device) {
1607
1837
  return __awaiter(this, void 0, void 0, function* () {
1838
+ const startedAt = Date.now();
1839
+ let succeeded = false;
1608
1840
  let timer;
1609
1841
  let timedOut = false;
1610
1842
  const pending = this.resolveCharacteristics(device);
@@ -1620,6 +1852,7 @@ class ReactNativeBleTransport {
1620
1852
  }),
1621
1853
  ]);
1622
1854
  this.connectionSetupTimeoutCounts.delete(uuid);
1855
+ succeeded = true;
1623
1856
  return result;
1624
1857
  }
1625
1858
  catch (error) {
@@ -1634,6 +1867,12 @@ class ReactNativeBleTransport {
1634
1867
  finally {
1635
1868
  if (timer)
1636
1869
  clearTimeout(timer);
1870
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] GATT setup completed', {
1871
+ connectIdSuffix: uuid.slice(-8),
1872
+ elapsedMs: Date.now() - startedAt,
1873
+ succeeded,
1874
+ backstopExpired: timedOut,
1875
+ });
1637
1876
  }
1638
1877
  });
1639
1878
  }
@@ -1735,6 +1974,8 @@ class ReactNativeBleTransport {
1735
1974
  }
1736
1975
  }
1737
1976
  resetPlxManager() {
1977
+ if (bleManagerResetPromise)
1978
+ return;
1738
1979
  const manager = this.blePlxManager;
1739
1980
  this.blePlxManager = undefined;
1740
1981
  const reason = 'React Native BLE manager reset';
@@ -1776,12 +2017,20 @@ class ReactNativeBleTransport {
1776
2017
  this.connectionSetupTimeoutCounts.clear();
1777
2018
  this.monitorTokens.clear();
1778
2019
  this.protocolV2Assemblers.clear();
2020
+ let reset;
1779
2021
  try {
1780
- manager === null || manager === void 0 ? void 0 : manager.destroy();
2022
+ reset = Promise.resolve(manager === null || manager === void 0 ? void 0 : manager.destroy());
1781
2023
  }
1782
2024
  catch (error) {
1783
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
2025
+ reset = Promise.reject(error);
1784
2026
  }
2027
+ bleManagerResetPromise = reset;
2028
+ reset.then(() => {
2029
+ if (bleManagerResetPromise === reset)
2030
+ bleManagerResetPromise = undefined;
2031
+ }, error => {
2032
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE manager destroy failed:', error);
2033
+ });
1785
2034
  }
1786
2035
  createProtocolMismatchError(expected) {
1787
2036
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
@@ -1931,7 +2180,7 @@ class ReactNativeBleTransport {
1931
2180
  catch (error) {
1932
2181
  this.clearProbeProtocol(uuid, 'V1');
1933
2182
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1934
- if (isWedgedWriteError(error)) {
2183
+ if (shouldRethrowProtocolProbeError(error)) {
1935
2184
  throw error;
1936
2185
  }
1937
2186
  return false;
@@ -1957,7 +2206,7 @@ class ReactNativeBleTransport {
1957
2206
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1958
2207
  this.resetProtocolV2Frames(uuid);
1959
2208
  },
1960
- shouldRethrow: hdShared.isBleStaleBondHardwareError,
2209
+ shouldRethrow: shouldRethrowProtocolProbeError,
1961
2210
  });
1962
2211
  if (!detected) {
1963
2212
  this.clearProbeProtocol(uuid, 'V2');
@@ -2084,6 +2333,9 @@ class ReactNativeBleTransport {
2084
2333
  this.rememberStaleBondError(uuid, bondError);
2085
2334
  throw bondError;
2086
2335
  }
2336
+ if (isNativeBleDisconnectError(error)) {
2337
+ throw toBleDisconnectHardwareError(error);
2338
+ }
2087
2339
  if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2088
2340
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2089
2341
  throw error;