@onekeyfe/hd-transport-react-native 1.2.2-alpha.106 → 1.2.2-alpha.108

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.
@@ -1 +1 @@
1
- {"version":3,"file":"BleManager.d.ts","sourceRoot":"","sources":["../src/BleManager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AASnE,eAAO,MAAM,qBAAqB,iBAAkB,MAAM,EAAE,0BACZ,CAAC;AAEjD,eAAO,MAAM,gBAAgB,6BAAwC,CAAC;AAEtE,eAAO,MAAM,UAAU,eAAgB,MAAM;;;EAAoC,CAAC;AAElF,eAAO,MAAM,iBAAiB,kBAAmB,MAAM,KAAG,QAAQ,UAAU,GAAG,SAAS,CA+BpF,CAAC"}
1
+ {"version":3,"file":"BleManager.d.ts","sourceRoot":"","sources":["../src/BleManager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAsBnE,eAAO,MAAM,qBAAqB,iBAAkB,MAAM,EAAE,0BACZ,CAAC;AAEjD,eAAO,MAAM,gBAAgB,6BAAwC,CAAC;AAEtE,eAAO,MAAM,UAAU,eAAgB,MAAM;;;EAAoC,CAAC;AAElF,eAAO,MAAM,iBAAiB,kBAAmB,MAAM,KAAG,QAAQ,UAAU,GAAG,SAAS,CAwEpF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -57,6 +57,17 @@ 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
73
  const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
@@ -69,24 +80,40 @@ const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
69
80
  };
70
81
  const timeout = setTimeout(() => {
71
82
  cleanup();
72
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
83
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', {
84
+ phase: 'bond',
85
+ reason: 'timeout',
86
+ }));
73
87
  }, 60 * 1000);
74
88
  const cleanupListener = BleUtils__default["default"].onDeviceBondState(peripheral => {
75
- var _a;
89
+ var _a, _b;
76
90
  if (((_a = peripheral.id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== bleMacAddress.toLowerCase()) {
77
91
  return;
78
92
  }
79
93
  const { bondState } = peripheral;
80
94
  const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
81
- const hasCanceled = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
95
+ const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
82
96
  Logger.debug('onDeviceBondState bondState:', bondState);
83
97
  if (hasBonded) {
84
98
  cleanup();
85
99
  resolve(peripheral);
86
100
  }
87
- else if (hasCanceled) {
101
+ else if (hasFailed) {
88
102
  cleanup();
89
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'bonding canceled'));
103
+ const nativeReason = 'reason' in bondState && typeof bondState.reason === 'number'
104
+ ? bondState.reason
105
+ : undefined;
106
+ const reason = nativeReason === undefined ? 'unknown' : (_b = bondFailureReasons[nativeReason]) !== null && _b !== void 0 ? _b : 'unknown';
107
+ const params = Object.assign({ phase: 'bond', reason }, (nativeReason === undefined ? {} : { nativeReason }));
108
+ if (reason === 'timeout') {
109
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', params));
110
+ }
111
+ else if (reason === 'canceled') {
112
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'Bluetooth pairing canceled', params));
113
+ }
114
+ else {
115
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed', params));
116
+ }
90
117
  }
91
118
  });
92
119
  });
@@ -861,6 +888,8 @@ class ReactNativeBleTransport {
861
888
  installTransportForAcquire(uuid, device, characteristics) {
862
889
  return __awaiter(this, void 0, void 0, function* () {
863
890
  const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
891
+ if (this.stopped)
892
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
864
893
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
865
894
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
866
895
  const monitorToken = this.nextMonitorToken;
@@ -881,17 +910,23 @@ class ReactNativeBleTransport {
881
910
  else if (reactNative.Platform.OS === 'android') {
882
911
  yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
883
912
  }
913
+ if (this.stopped)
914
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
884
915
  const initialMtu = transport$1.mtuSize;
885
916
  let refreshAttempts = 0;
886
917
  if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
887
918
  shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
888
919
  refreshAttempts += 1;
889
920
  let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
921
+ if (this.stopped)
922
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
890
923
  transport$1.device = refreshedDevice;
891
924
  transport$1.mtuSize =
892
925
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
893
926
  if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
894
927
  yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
928
+ if (this.stopped)
929
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
895
930
  refreshAttempts += 1;
896
931
  refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
897
932
  transport$1.device = refreshedDevice;
@@ -899,6 +934,8 @@ class ReactNativeBleTransport {
899
934
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
900
935
  }
901
936
  }
937
+ if (this.stopped)
938
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
902
939
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
903
940
  platform: reactNative.Platform.OS,
904
941
  requested: getRequestedBleMtu(),
@@ -957,6 +994,8 @@ class ReactNativeBleTransport {
957
994
  if (isCachedDeviceConnected &&
958
995
  cachedProtocol &&
959
996
  (!expectedProtocol || cachedProtocol === expectedProtocol)) {
997
+ if (this.stopped)
998
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
960
999
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
961
1000
  return { uuid, protocolType: cachedProtocol };
962
1001
  }
@@ -980,6 +1019,27 @@ class ReactNativeBleTransport {
980
1019
  Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
981
1020
  throw error;
982
1021
  }
1022
+ if (this.stopped)
1023
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1024
+ if (reactNative.Platform.OS === 'android') {
1025
+ try {
1026
+ const bondState = yield pairDevice(uuid);
1027
+ if (bondState.bonding) {
1028
+ yield onDeviceBondState(uuid);
1029
+ }
1030
+ else if (!bondState.bonded) {
1031
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1032
+ }
1033
+ }
1034
+ catch (error) {
1035
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1036
+ yield this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
1037
+ }));
1038
+ throw error;
1039
+ }
1040
+ }
1041
+ if (this.stopped)
1042
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
983
1043
  if (!device) {
984
1044
  const devices = yield blePlxManager.devices([uuid]);
985
1045
  [device] = devices;
@@ -1051,33 +1111,27 @@ class ReactNativeBleTransport {
1051
1111
  }
1052
1112
  }
1053
1113
  }
1054
- if (reactNative.Platform.OS === 'android') {
1055
- const connectedDevice = device;
1056
- try {
1057
- if (!(yield connectedDevice.isConnected().catch(() => false))) {
1058
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Device ${uuid} is not connected before bonding`);
1059
- }
1060
- const bondState = yield pairDevice(uuid);
1061
- if (bondState.bonding) {
1062
- yield onDeviceBondState(uuid);
1063
- }
1064
- else if (!bondState.bonded) {
1065
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1066
- }
1067
- }
1068
- catch (error) {
1069
- yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1070
- yield Promise.all([
1071
- this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1072
- this.runBestEffortNativeOperation('bond failure: cancel device connection', () => connectedDevice.cancelConnection()),
1073
- ]);
1074
- }));
1075
- throw error;
1076
- }
1114
+ if (this.stopped)
1115
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1116
+ if (reactNative.Platform.OS === 'android' && !(yield device.isConnected().catch(() => false))) {
1117
+ const disconnectedDevice = device;
1118
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1119
+ yield Promise.all([
1120
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1121
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () => disconnectedDevice.cancelConnection()),
1122
+ ]);
1123
+ }));
1124
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'device is not connected');
1077
1125
  }
1126
+ if (this.stopped)
1127
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1078
1128
  device = yield resolveNegotiatedMtu(device);
1129
+ if (this.stopped)
1130
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1079
1131
  const acquiredDevice = device;
1080
1132
  const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1133
+ if (this.stopped)
1134
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1081
1135
  const protocolHint = expectedProtocol
1082
1136
  ? undefined
1083
1137
  : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
@@ -1120,6 +1174,8 @@ class ReactNativeBleTransport {
1120
1174
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
1121
1175
  yield this.installTransportForAcquire(uuid, acquiredDevice);
1122
1176
  }));
1177
+ if (this.stopped)
1178
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1123
1179
  const currentTransport = transportCache[uuid];
1124
1180
  if (!currentTransport) {
1125
1181
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
@@ -1560,8 +1616,15 @@ class ReactNativeBleTransport {
1560
1616
  this.runPromise = null;
1561
1617
  this.runPromiseDeviceId = null;
1562
1618
  deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1619
+ const manager = this.blePlxManager;
1620
+ const pendingConnections = manager
1621
+ ? Array.from(this.lifecycleOperations.keys(), uuid => this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1622
+ yield this.runBestEffortNativeOperation('stop: cancel pending device connection', () => manager.cancelDeviceConnection(uuid));
1623
+ })))
1624
+ : [];
1563
1625
  this.stopPromise = Promise.all([
1564
1626
  ...scans,
1627
+ ...pendingConnections,
1565
1628
  ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1566
1629
  ]).then(() => __awaiter(this, void 0, void 0, function* () {
1567
1630
  yield this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
@@ -1716,6 +1779,8 @@ class ReactNativeBleTransport {
1716
1779
  }
1717
1780
  connectWithTimeout(uuid, connect) {
1718
1781
  return __awaiter(this, void 0, void 0, function* () {
1782
+ if (this.stopped)
1783
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1719
1784
  const startedAt = Date.now();
1720
1785
  let succeeded = false;
1721
1786
  let timer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.2-alpha.106",
3
+ "version": "1.2.2-alpha.108",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,11 +20,11 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-core": "1.2.2-alpha.106",
24
- "@onekeyfe/hd-shared": "1.2.2-alpha.106",
25
- "@onekeyfe/hd-transport": "1.2.2-alpha.106",
26
- "@onekeyfe/react-native-ble-utils": "^0.1.6",
23
+ "@onekeyfe/hd-core": "1.2.2-alpha.108",
24
+ "@onekeyfe/hd-shared": "1.2.2-alpha.108",
25
+ "@onekeyfe/hd-transport": "1.2.2-alpha.108",
26
+ "@onekeyfe/react-native-ble-utils": "0.1.6",
27
27
  "react-native-ble-plx": "3.5.1"
28
28
  },
29
- "gitHead": "e52ad091098aaab6b2845154457ace5e2f8c9098"
29
+ "gitHead": "962db9ecea348976a93de3c3039ac171ca0086d8"
30
30
  }
package/src/BleManager.ts CHANGED
@@ -7,6 +7,19 @@ import type { Peripheral } from '@onekeyfe/react-native-ble-utils';
7
7
 
8
8
  const Logger = bleLogger;
9
9
 
10
+ // Android BluetoothDevice.EXTRA_UNBOND_REASON values.
11
+ const bondFailureReasons: Record<number, string> = {
12
+ 1: 'authentication_failed',
13
+ 2: 'rejected',
14
+ 3: 'canceled',
15
+ 4: 'device_unreachable',
16
+ 5: 'discovery_in_progress',
17
+ 6: 'timeout',
18
+ 7: 'repeated_attempts',
19
+ 8: 'remote_canceled',
20
+ 9: 'removed',
21
+ };
22
+
10
23
  /**
11
24
  * get the device basic info of connected devices
12
25
  * @param serviceUuids
@@ -30,7 +43,12 @@ export const onDeviceBondState = (bleMacAddress: string): Promise<Peripheral | u
30
43
 
31
44
  const timeout = setTimeout(() => {
32
45
  cleanup();
33
- reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
46
+ reject(
47
+ ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', {
48
+ phase: 'bond',
49
+ reason: 'timeout',
50
+ })
51
+ );
34
52
  }, 60 * 1000);
35
53
 
36
54
  const cleanupListener = BleUtils.onDeviceBondState(peripheral => {
@@ -40,14 +58,50 @@ export const onDeviceBondState = (bleMacAddress: string): Promise<Peripheral | u
40
58
  const { bondState } = peripheral;
41
59
 
42
60
  const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
43
- const hasCanceled = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
61
+ const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
44
62
  Logger.debug('onDeviceBondState bondState:', bondState);
45
63
  if (hasBonded) {
46
64
  cleanup();
47
65
  resolve(peripheral);
48
- } else if (hasCanceled) {
66
+ } else if (hasFailed) {
49
67
  cleanup();
50
- reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled, 'bonding canceled'));
68
+ const nativeReason =
69
+ 'reason' in bondState && typeof bondState.reason === 'number'
70
+ ? bondState.reason
71
+ : undefined;
72
+ const reason =
73
+ nativeReason === undefined ? 'unknown' : bondFailureReasons[nativeReason] ?? 'unknown';
74
+ const params = {
75
+ phase: 'bond',
76
+ reason,
77
+ ...(nativeReason === undefined ? {} : { nativeReason }),
78
+ };
79
+ if (reason === 'timeout') {
80
+ // Connection timeouts are retried by Core; pairing requires a new user attempt.
81
+ reject(
82
+ ERRORS.TypedError(
83
+ HardwareErrorCode.BleDeviceNotBonded,
84
+ 'Bluetooth pairing timed out',
85
+ params
86
+ )
87
+ );
88
+ } else if (reason === 'canceled') {
89
+ reject(
90
+ ERRORS.TypedError(
91
+ HardwareErrorCode.BleDeviceBondedCanceled,
92
+ 'Bluetooth pairing canceled',
93
+ params
94
+ )
95
+ );
96
+ } else {
97
+ reject(
98
+ ERRORS.TypedError(
99
+ HardwareErrorCode.BleDeviceNotBonded,
100
+ 'Bluetooth pairing failed',
101
+ params
102
+ )
103
+ );
104
+ }
51
105
  }
52
106
  });
53
107
  });
@@ -1,7 +1,10 @@
1
1
  import { EventEmitter } from 'events';
2
+ import { Platform } from 'react-native';
2
3
  import { BleErrorCode, BleManager } from 'react-native-ble-plx';
3
- import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
4
+ import BleUtils from '@onekeyfe/react-native-ble-utils';
5
+ import { ERRORS, HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
4
6
 
7
+ import { onDeviceBondState } from '../BleManager';
5
8
  import ReactNativeBleTransport, {
6
9
  BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD,
7
10
  BLE_CONNECT_TIMEOUT_MS,
@@ -10,6 +13,8 @@ import ReactNativeBleTransport, {
10
13
  } from '../index';
11
14
  import protocolV1Schema from './protocolV1SchemaFixture';
12
15
 
16
+ import type { Peripheral } from '@onekeyfe/react-native-ble-utils';
17
+
13
18
  jest.mock(
14
19
  'react-native',
15
20
  () => ({
@@ -44,12 +49,105 @@ jest.mock('@onekeyfe/react-native-ble-utils', () => ({
44
49
  default: {
45
50
  getConnectedPeripherals: jest.fn(() => Promise.resolve([])),
46
51
  getBondedPeripherals: jest.fn(() => Promise.resolve([])),
47
- pairDevice: jest.fn(() => Promise.resolve()),
52
+ pairDevice: jest.fn(() => Promise.resolve({ bonded: true, bonding: false })),
53
+ onDeviceBondState: jest.fn(),
48
54
  },
49
55
  }));
50
56
 
51
57
  const UUID = 'stalled-connect-device';
52
58
 
59
+ describe('Android bond failure reasons', () => {
60
+ let emitBondState: (peripheral: Peripheral) => void;
61
+ const cleanup = jest.fn();
62
+
63
+ beforeEach(() => {
64
+ jest.useFakeTimers({ doNotFake: ['performance'] });
65
+ cleanup.mockClear();
66
+ jest.spyOn(BleUtils, 'onDeviceBondState').mockImplementation(listener => {
67
+ emitBondState = listener;
68
+ return cleanup;
69
+ });
70
+ });
71
+
72
+ afterEach(() => {
73
+ jest.restoreAllMocks();
74
+ jest.useRealTimers();
75
+ });
76
+
77
+ test.each([
78
+ [6, 'timeout', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out'],
79
+ [3, 'canceled', HardwareErrorCode.BleDeviceBondedCanceled, 'Bluetooth pairing canceled'],
80
+ [1, 'authentication_failed', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
81
+ [2, 'rejected', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
82
+ [4, 'device_unreachable', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
83
+ [5, 'discovery_in_progress', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
84
+ [7, 'repeated_attempts', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
85
+ [8, 'remote_canceled', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
86
+ [9, 'removed', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
87
+ [undefined, 'unknown', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
88
+ [999, 'unknown', HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed'],
89
+ ] as const)(
90
+ 'preserves Android reason %s as %s across serialization',
91
+ async (nativeReason, reason, code, message) => {
92
+ const result = onDeviceBondState(UUID).catch(error =>
93
+ JSON.parse(JSON.stringify(ERRORS.serializeError({ error })))
94
+ );
95
+ emitBondState({
96
+ id: UUID,
97
+ advertising: {},
98
+ bondState: {
99
+ preState: 'BOND_BONDING',
100
+ state: 'BOND_NONE',
101
+ ...(nativeReason === undefined ? {} : { reason: nativeReason }),
102
+ },
103
+ });
104
+
105
+ await expect(result).resolves.toEqual({
106
+ code,
107
+ error: message,
108
+ params: {
109
+ phase: 'bond',
110
+ reason,
111
+ ...(nativeReason === undefined ? {} : { nativeReason }),
112
+ },
113
+ });
114
+ expect(cleanup).toHaveBeenCalledTimes(1);
115
+ expect(jest.getTimerCount()).toBe(0);
116
+ }
117
+ );
118
+
119
+ test('reports a timeout if no bond event arrives within the SDK deadline', async () => {
120
+ const result = onDeviceBondState(UUID);
121
+ const rejection = expect(result).rejects.toMatchObject({
122
+ errorCode: HardwareErrorCode.BleDeviceNotBonded,
123
+ params: { phase: 'bond', reason: 'timeout' },
124
+ });
125
+ jest.advanceTimersByTime(60_000);
126
+ await rejection;
127
+ expect(cleanup).toHaveBeenCalledTimes(1);
128
+ });
129
+
130
+ test('ignores other devices and clears the deadline after bonding succeeds', async () => {
131
+ const result = onDeviceBondState(UUID);
132
+ const otherPeripheral = {
133
+ id: 'another-device',
134
+ advertising: {},
135
+ bondState: { preState: 'BOND_BONDING', state: 'BOND_NONE', reason: 6 },
136
+ };
137
+ emitBondState(otherPeripheral);
138
+ expect(cleanup).not.toHaveBeenCalled();
139
+ const peripheral: Peripheral = {
140
+ id: UUID.toUpperCase(),
141
+ advertising: {},
142
+ bondState: { preState: 'BOND_BONDING', state: 'BOND_BONDED' },
143
+ };
144
+ emitBondState(peripheral);
145
+ await expect(result).resolves.toBe(peripheral);
146
+ expect(cleanup).toHaveBeenCalledTimes(1);
147
+ expect(jest.getTimerCount()).toBe(0);
148
+ });
149
+ });
150
+
53
151
  const flush = () =>
54
152
  new Promise(resolve => {
55
153
  setImmediate(resolve);
@@ -129,6 +227,7 @@ describe('BLE connect timeout', () => {
129
227
  afterEach(() => {
130
228
  jest.clearAllTimers();
131
229
  jest.restoreAllMocks();
230
+ Object.assign(Platform, { OS: 'ios' });
132
231
  });
133
232
 
134
233
  test('waits for singleton destruction before creating the next manager', async () => {
@@ -270,6 +369,78 @@ describe('BLE connect timeout', () => {
270
369
  expect(BLE_CONNECT_TIMEOUT_MS).toBeLessThanOrEqual(12000);
271
370
  });
272
371
 
372
+ test.each(
373
+ (['ios', 'android'] as const).flatMap(platform =>
374
+ (['connect', 'mtu', 'gatt'] as const).flatMap(stage =>
375
+ (['reject', 'resolve'] as const).map(completion => ({ platform, stage, completion }))
376
+ )
377
+ )
378
+ )(
379
+ 'stop cancels $platform $stage before draining a late $completion',
380
+ async ({ platform, stage, completion }) => {
381
+ Object.assign(Platform, { OS: platform });
382
+ const { transport, device, bleManager, connect } = createHarness(
383
+ () => nativeOperation.promise
384
+ );
385
+ const nativeOperation = createDeferred<typeof device>();
386
+ const nativeDisconnect = createDeferred<void>();
387
+ const requestMtu = jest.fn(() => Promise.resolve(device));
388
+ Object.assign(device, { mtu: 247, requestMTU: requestMtu });
389
+ device.isConnected.mockResolvedValue(true);
390
+ if (stage === 'connect') device.isConnected.mockResolvedValueOnce(false);
391
+ if (stage === 'mtu') requestMtu.mockImplementationOnce(() => nativeOperation.promise);
392
+ if (stage === 'gatt') {
393
+ device.discoverAllServicesAndCharacteristics.mockImplementationOnce(() =>
394
+ nativeOperation.promise.then(() => undefined)
395
+ );
396
+ }
397
+ const destroy = jest.fn();
398
+ Object.assign(bleManager, { destroy });
399
+ const monitor = jest.spyOn(transport, '_monitorCharacteristic');
400
+ const acquire = transport.acquire({ uuid: UUID }).catch(error => error);
401
+ await flush();
402
+ await flush();
403
+ const pending = {
404
+ connect,
405
+ mtu: requestMtu,
406
+ gatt: device.discoverAllServicesAndCharacteristics,
407
+ }[stage];
408
+ expect(pending).toHaveBeenCalledTimes(1);
409
+
410
+ bleManager.cancelDeviceConnection.mockImplementation(() => nativeDisconnect.promise);
411
+ let stopped = false;
412
+ const stopping = transport.stop().then(() => {
413
+ stopped = true;
414
+ });
415
+ try {
416
+ await flush();
417
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(UUID);
418
+ expect(stopped).toBe(false);
419
+ } finally {
420
+ // Native completion can race cancellation; neither outcome may restart setup.
421
+ if (completion === 'resolve') nativeOperation.resolve(device);
422
+ else
423
+ nativeOperation.reject(
424
+ Object.assign(new Error('Operation was cancelled'), {
425
+ errorCode: BleErrorCode.OperationCancelled,
426
+ })
427
+ );
428
+ nativeDisconnect.resolve();
429
+ await advanceUntil(() => stopped, BLE_CONNECT_TIMEOUT_MS + BLE_GATT_SETUP_TIMEOUT_MS);
430
+ await stopping;
431
+ }
432
+ await expect(acquire).resolves.toBeInstanceOf(Error);
433
+ expect(connect).toHaveBeenCalledTimes(stage === 'connect' ? 1 : 0);
434
+ expect(monitor).not.toHaveBeenCalled();
435
+ if (stage === 'mtu')
436
+ expect(device.discoverAllServicesAndCharacteristics).not.toHaveBeenCalled();
437
+ expect(bleManager.cancelDeviceConnection.mock.calls).toEqual(
438
+ Array.from({ length: bleManager.cancelDeviceConnection.mock.calls.length }, () => [UUID])
439
+ );
440
+ expect(destroy).not.toHaveBeenCalled();
441
+ }
442
+ );
443
+
273
444
  test('a stalled connect is abandoned natively so the next attempt is not cancelled by it', async () => {
274
445
  const { transport, bleManager } = createHarness(
275
446
  () =>
@@ -360,32 +360,46 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
360
360
  expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
361
361
  });
362
362
 
363
- test('connects the Android GATT link before starting system bonding', async () => {
364
- setPlatformOS('android');
365
- const { transport, uuid, device } = createHarness();
366
- const operationOrder: string[] = [];
367
- const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
363
+ test.each(['V1', 'V2'] as const)(
364
+ 'waits for Android bonding before connecting the %s GATT link',
365
+ async protocol => {
366
+ setPlatformOS('android');
367
+ const { transport, uuid, device } = protocol === 'V1' ? createV1Harness() : createHarness();
368
+ const operationOrder: string[] = [];
369
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
370
+ const bondStateMock = jest.requireMock('../BleManager').onDeviceBondState as jest.Mock;
371
+ const bonding = createDeferred<void>();
368
372
 
369
- device.isConnected.mockResolvedValueOnce(false);
370
- device.connect = jest.fn(() => {
371
- operationOrder.push('connect');
372
- return Promise.resolve(device);
373
- });
374
- pairDeviceMock.mockImplementationOnce(() => {
375
- operationOrder.push('bond');
376
- return Promise.resolve({ bonded: true, bonding: false });
377
- });
373
+ device.isConnected.mockResolvedValueOnce(false);
374
+ device.connect = jest.fn(() => {
375
+ operationOrder.push('connect');
376
+ return Promise.resolve(device);
377
+ });
378
+ pairDeviceMock.mockImplementationOnce(() => {
379
+ operationOrder.push('bond');
380
+ return Promise.resolve({ bonded: false, bonding: true });
381
+ });
382
+ bondStateMock.mockImplementationOnce(() => bonding.promise);
378
383
 
379
- await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
380
- uuid,
381
- protocolType: 'V2',
382
- });
384
+ const acquiring = transport.acquire({ uuid, expectedProtocol: protocol });
385
+ await new Promise(resolve => {
386
+ setImmediate(resolve);
387
+ });
388
+ expect(operationOrder).toEqual(['bond']);
389
+ expect(device.connect).not.toHaveBeenCalled();
383
390
 
384
- expect(operationOrder).toEqual(['connect', 'bond']);
385
- await transport.release(uuid, true);
386
- });
391
+ bonding.resolve();
392
+ await expect(acquiring).resolves.toEqual({
393
+ uuid,
394
+ protocolType: protocol,
395
+ });
387
396
 
388
- test('does not start Android bonding when both reconnect attempts fail', async () => {
397
+ expect(operationOrder).toEqual(['bond', 'connect']);
398
+ await transport.release(uuid, true);
399
+ }
400
+ );
401
+
402
+ test('preserves Android connect errors after bonding when both reconnect attempts fail', async () => {
389
403
  setPlatformOS('android');
390
404
  const { transport, uuid, device } = createHarness();
391
405
  const BleErrorMock = jest.requireMock('react-native-ble-plx').BleError as new (
@@ -412,22 +426,35 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
412
426
  errorCode: HardwareErrorCode.BleConnectedError,
413
427
  });
414
428
  expect(device.connect).toHaveBeenCalledTimes(2);
415
- expect(pairDeviceMock).not.toHaveBeenCalled();
429
+ expect(pairDeviceMock).toHaveBeenCalledTimes(1);
416
430
  });
417
431
 
418
- test('checks that the Android GATT link is connected before bonding', async () => {
432
+ test('does not connect Android GATT when the system cannot start bonding', async () => {
419
433
  setPlatformOS('android');
420
434
  const { transport, uuid, device, bleManager } = createHarness();
421
435
  const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
422
436
 
423
- device.isConnected.mockResolvedValueOnce(false).mockResolvedValueOnce(false);
424
437
  device.connect = jest.fn().mockResolvedValue(device);
425
438
  pairDeviceMock.mockClear();
439
+ pairDeviceMock.mockResolvedValueOnce({ bonded: false, bonding: false });
440
+
441
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
442
+ errorCode: HardwareErrorCode.BleDeviceNotBonded,
443
+ });
444
+ expect(device.connect).not.toHaveBeenCalled();
445
+ expect(bleManager.devices).not.toHaveBeenCalled();
446
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
447
+ });
448
+
449
+ test('checks that Android GATT is connected after bonding and reconnecting', async () => {
450
+ setPlatformOS('android');
451
+ const { transport, uuid, device, bleManager } = createHarness();
452
+ device.isConnected.mockResolvedValueOnce(false).mockResolvedValueOnce(false);
453
+ device.connect = jest.fn().mockResolvedValue(device);
426
454
 
427
455
  await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
428
456
  errorCode: HardwareErrorCode.BleConnectedError,
429
457
  });
430
- expect(pairDeviceMock).not.toHaveBeenCalled();
431
458
  expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
432
459
  expect(device.cancelConnection).toHaveBeenCalledTimes(1);
433
460
  });
@@ -468,12 +495,12 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
468
495
  .mockRejectedValue(Object.assign(new BleErrorMock(nativeError.reason), nativeError));
469
496
 
470
497
  await expect(transport.acquire({ uuid })).rejects.toMatchObject({ errorCode });
471
- expect(pairDeviceMock).not.toHaveBeenCalled();
498
+ expect(pairDeviceMock).toHaveBeenCalledTimes(platform === 'android' ? 1 : 0);
472
499
  expect(transport.getProtocolType(uuid)).toBeUndefined();
473
500
  }
474
501
  );
475
502
 
476
- test('closes the Android GATT link when system bonding fails', async () => {
503
+ test('cleans up Android bonding failures without starting GATT', async () => {
477
504
  setPlatformOS('android');
478
505
  const { transport, uuid, device, bleManager } = createHarness();
479
506
  const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
@@ -485,7 +512,32 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
485
512
  );
486
513
 
487
514
  expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
488
- expect(device.cancelConnection).toHaveBeenCalledTimes(1);
515
+ expect(bleManager.devices).not.toHaveBeenCalled();
516
+ expect(device.isConnected).not.toHaveBeenCalled();
517
+ });
518
+
519
+ test('does not connect after stop while Android bonding is pending', async () => {
520
+ setPlatformOS('android');
521
+ const { transport, uuid, bleManager } = createHarness();
522
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
523
+ const bondStateMock = jest.requireMock('../BleManager').onDeviceBondState as jest.Mock;
524
+ const bonding = createDeferred<void>();
525
+ pairDeviceMock.mockResolvedValueOnce({ bonded: false, bonding: true });
526
+ bondStateMock.mockImplementationOnce(() => bonding.promise);
527
+
528
+ const acquiring = transport.acquire({ uuid, expectedProtocol: 'V2' });
529
+ const rejection = expect(acquiring).rejects.toMatchObject({
530
+ errorCode: HardwareErrorCode.BleDeviceDisconnected,
531
+ });
532
+ await new Promise(resolve => {
533
+ setImmediate(resolve);
534
+ });
535
+ const stopping = transport.stop();
536
+ bonding.resolve();
537
+
538
+ await rejection;
539
+ await stopping;
540
+ expect(bleManager.devices).not.toHaveBeenCalled();
489
541
  });
490
542
 
491
543
  test('uses withResponse for consecutive iOS Protocol V1 control commands without releasing', async () => {
package/src/index.ts CHANGED
@@ -885,6 +885,7 @@ export default class ReactNativeBleTransport {
885
885
  ) {
886
886
  const { writeCharacteristic, notifyCharacteristic } =
887
887
  characteristics ?? (await this.resolveCharacteristicsWithTimeout(uuid, device));
888
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
888
889
  const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
889
890
  transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
890
891
  const monitorToken = this.nextMonitorToken;
@@ -913,6 +914,7 @@ export default class ReactNativeBleTransport {
913
914
  } else if (Platform.OS === 'android') {
914
915
  await delay(ANDROID_NOTIFY_READY_DELAY_MS);
915
916
  }
917
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
916
918
 
917
919
  const initialMtu = transport.mtuSize;
918
920
  let refreshAttempts = 0;
@@ -926,12 +928,14 @@ export default class ReactNativeBleTransport {
926
928
  'servicesAndNotifyReady',
927
929
  1
928
930
  );
931
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
929
932
  transport.device = refreshedDevice;
930
933
  transport.mtuSize =
931
934
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
932
935
 
933
936
  if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
934
937
  await delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
938
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
935
939
  refreshAttempts += 1;
936
940
  refreshedDevice = await requestNegotiatedMtu(transport.device, 'servicesAndNotifyReady', 2);
937
941
  transport.device = refreshedDevice;
@@ -940,6 +944,7 @@ export default class ReactNativeBleTransport {
940
944
  }
941
945
  }
942
946
 
947
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
943
948
  Log?.debug('[ReactNativeBleTransport] BLE MTU ready', {
944
949
  platform: Platform.OS,
945
950
  requested: getRequestedBleMtu(),
@@ -1018,6 +1023,7 @@ export default class ReactNativeBleTransport {
1018
1023
  cachedProtocol &&
1019
1024
  (!expectedProtocol || cachedProtocol === expectedProtocol)
1020
1025
  ) {
1026
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1021
1027
  Log?.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
1022
1028
  return { uuid, protocolType: cachedProtocol };
1023
1029
  }
@@ -1049,6 +1055,27 @@ export default class ReactNativeBleTransport {
1049
1055
  throw error;
1050
1056
  }
1051
1057
 
1058
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1059
+ if (Platform.OS === 'android') {
1060
+ // Initiate bonding locally before GATT can trigger peripheral-initiated pairing.
1061
+ try {
1062
+ const bondState = await pairDevice(uuid);
1063
+ if (bondState.bonding) {
1064
+ await onDeviceBondState(uuid);
1065
+ } else if (!bondState.bonded) {
1066
+ throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1067
+ }
1068
+ } catch (error) {
1069
+ await this.runNativeTeardown(uuid, blePlxManager, async () => {
1070
+ await this.runBestEffortNativeOperation('bond failure: cancel manager connection', () =>
1071
+ blePlxManager.cancelDeviceConnection(uuid)
1072
+ );
1073
+ });
1074
+ throw error;
1075
+ }
1076
+ }
1077
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1078
+
1052
1079
  if (!device) {
1053
1080
  const devices = await blePlxManager.devices([uuid]);
1054
1081
  [device] = devices;
@@ -1135,42 +1162,28 @@ export default class ReactNativeBleTransport {
1135
1162
  }
1136
1163
  }
1137
1164
 
1138
- if (Platform.OS === 'android') {
1139
- // Establish the LE link before createBond(). Without an existing LE ACL,
1140
- // Android TRANSPORT_AUTO can choose BR/EDR for a BLE-only device.
1141
- const connectedDevice = device;
1142
- try {
1143
- if (!(await connectedDevice.isConnected().catch(() => false))) {
1144
- throw ERRORS.TypedError(
1145
- HardwareErrorCode.BleConnectedError,
1146
- `Device ${uuid} is not connected before bonding`
1147
- );
1148
- }
1149
- const bondState = await pairDevice(uuid);
1150
- if (bondState.bonding) {
1151
- await onDeviceBondState(uuid);
1152
- } else if (!bondState.bonded) {
1153
- throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1154
- }
1155
- } catch (error) {
1156
- await this.runNativeTeardown(uuid, blePlxManager, async () => {
1157
- await Promise.all([
1158
- this.runBestEffortNativeOperation('bond failure: cancel manager connection', () =>
1159
- blePlxManager.cancelDeviceConnection(uuid)
1160
- ),
1161
- this.runBestEffortNativeOperation('bond failure: cancel device connection', () =>
1162
- connectedDevice.cancelConnection()
1163
- ),
1164
- ]);
1165
- });
1166
- throw error;
1167
- }
1165
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1166
+ if (Platform.OS === 'android' && !(await device.isConnected().catch(() => false))) {
1167
+ const disconnectedDevice = device;
1168
+ await this.runNativeTeardown(uuid, blePlxManager, async () => {
1169
+ await Promise.all([
1170
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () =>
1171
+ blePlxManager.cancelDeviceConnection(uuid)
1172
+ ),
1173
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () =>
1174
+ disconnectedDevice.cancelConnection()
1175
+ ),
1176
+ ]);
1177
+ });
1178
+ throw ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'device is not connected');
1168
1179
  }
1169
-
1180
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1170
1181
  device = await resolveNegotiatedMtu(device);
1182
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1171
1183
  const acquiredDevice = device;
1172
1184
  const { writeCharacteristic, notifyCharacteristic } =
1173
1185
  await this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1186
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1174
1187
 
1175
1188
  const protocolHint = expectedProtocol
1176
1189
  ? undefined
@@ -1232,6 +1245,7 @@ export default class ReactNativeBleTransport {
1232
1245
  await this.installTransportForAcquire(uuid, acquiredDevice);
1233
1246
  }
1234
1247
  );
1248
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1235
1249
  const currentTransport = transportCache[uuid];
1236
1250
  if (!currentTransport) {
1237
1251
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
@@ -1789,9 +1803,22 @@ export default class ReactNativeBleTransport {
1789
1803
  this.runPromise = null;
1790
1804
  this.runPromiseDeviceId = null;
1791
1805
  deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1806
+ const manager = this.blePlxManager;
1807
+ // Cancel native setup before waiting for its lifecycle lock. Otherwise stop
1808
+ // waits for the very connect/MTU/GATT operation it needs to interrupt.
1809
+ const pendingConnections = manager
1810
+ ? Array.from(this.lifecycleOperations.keys(), uuid =>
1811
+ this.runNativeTeardown(uuid, manager, async () => {
1812
+ await this.runBestEffortNativeOperation('stop: cancel pending device connection', () =>
1813
+ manager.cancelDeviceConnection(uuid)
1814
+ );
1815
+ })
1816
+ )
1817
+ : [];
1792
1818
  // Release only this transport's endpoints; other connectors may share ble-plx.
1793
1819
  this.stopPromise = Promise.all([
1794
1820
  ...scans,
1821
+ ...pendingConnections,
1795
1822
  ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1796
1823
  ]).then(async () => {
1797
1824
  await this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
@@ -1969,6 +1996,7 @@ export default class ReactNativeBleTransport {
1969
1996
 
1970
1997
  /** Run a native connect under the JS backstop budget. */
1971
1998
  private async connectWithTimeout<T>(uuid: string, connect: () => Promise<T>): Promise<T> {
1999
+ if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1972
2000
  const startedAt = Date.now();
1973
2001
  let succeeded = false;
1974
2002
  let timer: ReturnType<typeof setTimeout> | undefined;