@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.
- package/dist/BleManager.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -28
- package/package.json +6 -6
- package/src/BleManager.ts +58 -4
- package/src/__tests__/connectTimeout.test.ts +173 -2
- package/src/__tests__/protocolV2Link.test.ts +81 -29
- package/src/index.ts +59 -31
package/dist/BleManager.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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, '
|
|
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
|
|
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 (
|
|
101
|
+
else if (hasFailed) {
|
|
88
102
|
cleanup();
|
|
89
|
-
|
|
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 (
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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.
|
|
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.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
26
|
-
"@onekeyfe/react-native-ble-utils": "
|
|
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": "
|
|
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(
|
|
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
|
|
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 (
|
|
66
|
+
} else if (hasFailed) {
|
|
49
67
|
cleanup();
|
|
50
|
-
|
|
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
|
|
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('
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
bonding.resolve();
|
|
392
|
+
await expect(acquiring).resolves.toEqual({
|
|
393
|
+
uuid,
|
|
394
|
+
protocolType: protocol,
|
|
395
|
+
});
|
|
387
396
|
|
|
388
|
-
|
|
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).
|
|
429
|
+
expect(pairDeviceMock).toHaveBeenCalledTimes(1);
|
|
416
430
|
});
|
|
417
431
|
|
|
418
|
-
test('
|
|
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).
|
|
498
|
+
expect(pairDeviceMock).toHaveBeenCalledTimes(platform === 'android' ? 1 : 0);
|
|
472
499
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
473
500
|
}
|
|
474
501
|
);
|
|
475
502
|
|
|
476
|
-
test('
|
|
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(
|
|
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 (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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;
|