@onekeyfe/hd-transport-react-native 1.2.0-alpha.183 → 1.2.0-alpha.185
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/bleStaleBond.d.ts +5 -0
- package/dist/bleStaleBond.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -37
- package/package.json +5 -5
- package/src/__tests__/bleStaleBond.test.ts +31 -0
- package/src/__tests__/protocolV2Link.test.ts +80 -3
- package/src/bleStaleBond.ts +60 -0
- package/src/index.ts +87 -31
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { isBleStaleBondHardwareError } from '@onekeyfe/hd-shared';
|
|
2
|
+
export { isBleStaleBondHardwareError };
|
|
3
|
+
export declare const isNativeBleStaleBondError: (error: unknown) => boolean;
|
|
4
|
+
export declare const toBleStaleBondHardwareError: (error: unknown) => Error;
|
|
5
|
+
//# sourceMappingURL=bleStaleBond.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bleStaleBond.d.ts","sourceRoot":"","sources":["../src/bleStaleBond.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,2BAA2B,EAAE,CAAC;AAkBvC,eAAO,MAAM,yBAAyB,UAAW,OAAO,KAAG,OAe1D,CAAC;AAEF,eAAO,MAAM,2BAA2B,UAAW,OAAO,UAiBzD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,14 @@ declare class ReactNativeBleTransport {
|
|
|
134
134
|
private confirmedProtocolV2;
|
|
135
135
|
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
136
136
|
private protocolReprobeFailures;
|
|
137
|
+
/**
|
|
138
|
+
* Native encryption/pairing failures seen before Protocol V2 probe starts.
|
|
139
|
+
* Pro2/Neo GATT connect can succeed on a stale iOS bond; the CCCD write then
|
|
140
|
+
* fails with ATT 5/15. Remember it so detectProtocol fails immediately.
|
|
141
|
+
*/
|
|
142
|
+
private staleBondErrors;
|
|
143
|
+
/** Strict or previously confirmed V2 target while acquire installs notifications. */
|
|
144
|
+
private acquiringProtocolV2;
|
|
137
145
|
private protocolV2Assemblers;
|
|
138
146
|
private protocolV2FrameQueues;
|
|
139
147
|
private protocolV2FramePromises;
|
|
@@ -224,6 +232,8 @@ declare class ReactNativeBleTransport {
|
|
|
224
232
|
private resolveProtocolV2Frame;
|
|
225
233
|
private resetProtocolV2Frames;
|
|
226
234
|
private rejectProtocolV2Frames;
|
|
235
|
+
private rememberStaleBondError;
|
|
236
|
+
private throwIfStaleBondError;
|
|
227
237
|
private readProtocolV2Frame;
|
|
228
238
|
private writeProtocolV2Packet;
|
|
229
239
|
private writeProtocolV2Frame;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,37 @@ const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject)
|
|
|
194
194
|
}, ms);
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
const ATT_INSUFFICIENT_AUTHENTICATION = 5;
|
|
198
|
+
const ATT_INSUFFICIENT_ENCRYPTION = 15;
|
|
199
|
+
const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
|
|
200
|
+
const nativeErrorText = (error) => [error.reason, error.message]
|
|
201
|
+
.filter((value) => typeof value === 'string')
|
|
202
|
+
.join(' ');
|
|
203
|
+
const isNativeBleStaleBondError = (error) => {
|
|
204
|
+
if (!error || typeof error !== 'object') {
|
|
205
|
+
return typeof error === 'string' ? hdShared.isBleStaleBondErrorText(error) : false;
|
|
206
|
+
}
|
|
207
|
+
const nativeError = error;
|
|
208
|
+
if (nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
|
|
209
|
+
nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
|
|
210
|
+
nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return hdShared.isBleStaleBondErrorText(nativeErrorText(nativeError));
|
|
214
|
+
};
|
|
215
|
+
const toBleStaleBondHardwareError = (error) => {
|
|
216
|
+
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
217
|
+
return error;
|
|
218
|
+
}
|
|
219
|
+
const nativeError = (error !== null && error !== void 0 ? error : {});
|
|
220
|
+
const text = nativeErrorText(nativeError);
|
|
221
|
+
const peerRemoved = nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
|
|
222
|
+
text.includes('Peer removed pairing information');
|
|
223
|
+
return hdShared.ERRORS.TypedError(peerRemoved
|
|
224
|
+
? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
225
|
+
: hdShared.HardwareErrorCode.BleDeviceBondError, text || undefined);
|
|
226
|
+
};
|
|
227
|
+
|
|
197
228
|
const isHeaderChunk = (chunk) => {
|
|
198
229
|
if (chunk.length < 9)
|
|
199
230
|
return false;
|
|
@@ -372,12 +403,11 @@ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void
|
|
|
372
403
|
}
|
|
373
404
|
});
|
|
374
405
|
const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
|
|
375
|
-
function remapError(error) {
|
|
406
|
+
function remapError(error, mapProtocolV2StaleBond) {
|
|
376
407
|
var _a;
|
|
377
408
|
if (error instanceof reactNativeBlePlx.BleError) {
|
|
378
|
-
if (
|
|
379
|
-
error
|
|
380
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
409
|
+
if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
|
|
410
|
+
throw toBleStaleBondHardwareError(error);
|
|
381
411
|
}
|
|
382
412
|
if ((error === null || error === void 0 ? void 0 : error.attErrorCode) === 22) {
|
|
383
413
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondError);
|
|
@@ -408,6 +438,8 @@ class ReactNativeBleTransport {
|
|
|
408
438
|
this.sessionProtocols = new Map();
|
|
409
439
|
this.confirmedProtocolV2 = new Set();
|
|
410
440
|
this.protocolReprobeFailures = new Map();
|
|
441
|
+
this.staleBondErrors = new Map();
|
|
442
|
+
this.acquiringProtocolV2 = new Set();
|
|
411
443
|
this.protocolV2Assemblers = new Map();
|
|
412
444
|
this.protocolV2FrameQueues = new Map();
|
|
413
445
|
this.protocolV2FramePromises = new Map();
|
|
@@ -803,6 +835,9 @@ class ReactNativeBleTransport {
|
|
|
803
835
|
var _a;
|
|
804
836
|
return __awaiter(this, void 0, void 0, function* () {
|
|
805
837
|
const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
|
|
838
|
+
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
839
|
+
? expectedProtocol === 'V2'
|
|
840
|
+
: this.confirmedProtocolV2.has(uuid);
|
|
806
841
|
const cachedTransport = transportCache[uuid];
|
|
807
842
|
if (skipProtocolProbe && !cachedTransport && this.blePlxManager) {
|
|
808
843
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] refresh uncached BLE connection for firmware install:', uuid);
|
|
@@ -895,7 +930,7 @@ class ReactNativeBleTransport {
|
|
|
895
930
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleAlreadyConnected);
|
|
896
931
|
}
|
|
897
932
|
else {
|
|
898
|
-
remapError(e);
|
|
933
|
+
remapError(e, shouldMapProtocolV2StaleBond);
|
|
899
934
|
}
|
|
900
935
|
}
|
|
901
936
|
}
|
|
@@ -929,7 +964,7 @@ class ReactNativeBleTransport {
|
|
|
929
964
|
}
|
|
930
965
|
}
|
|
931
966
|
else {
|
|
932
|
-
remapError(e);
|
|
967
|
+
remapError(e, shouldMapProtocolV2StaleBond);
|
|
933
968
|
}
|
|
934
969
|
}
|
|
935
970
|
}
|
|
@@ -943,16 +978,21 @@ class ReactNativeBleTransport {
|
|
|
943
978
|
if (protocolHint) {
|
|
944
979
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
945
980
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
});
|
|
981
|
+
if (shouldMapProtocolV2StaleBond) {
|
|
982
|
+
this.acquiringProtocolV2.add(uuid);
|
|
983
|
+
}
|
|
950
984
|
try {
|
|
985
|
+
yield this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
986
|
+
writeCharacteristic,
|
|
987
|
+
notifyCharacteristic,
|
|
988
|
+
});
|
|
951
989
|
if (skipProtocolProbe) {
|
|
952
990
|
if (!expectedProtocol) {
|
|
953
991
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected BLE protocol');
|
|
954
992
|
}
|
|
955
|
-
|
|
993
|
+
const hasConfirmedProtocol = this.sessionProtocols.get(uuid) === expectedProtocol ||
|
|
994
|
+
(expectedProtocol === 'V2' && this.confirmedProtocolV2.has(uuid));
|
|
995
|
+
if (!hasConfirmedProtocol) {
|
|
956
996
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint');
|
|
957
997
|
}
|
|
958
998
|
this.deviceProtocol.set(uuid, expectedProtocol);
|
|
@@ -981,7 +1021,7 @@ class ReactNativeBleTransport {
|
|
|
981
1021
|
return { uuid, protocolType };
|
|
982
1022
|
}
|
|
983
1023
|
catch (error) {
|
|
984
|
-
if ((error
|
|
1024
|
+
if (hdShared.isBleStaleBondHardwareError(error)) {
|
|
985
1025
|
yield this.disconnectUnlocked(uuid);
|
|
986
1026
|
}
|
|
987
1027
|
else {
|
|
@@ -989,13 +1029,16 @@ class ReactNativeBleTransport {
|
|
|
989
1029
|
}
|
|
990
1030
|
throw error;
|
|
991
1031
|
}
|
|
1032
|
+
finally {
|
|
1033
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1034
|
+
}
|
|
992
1035
|
});
|
|
993
1036
|
}
|
|
994
1037
|
_monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
|
|
995
1038
|
let bufferLength = 0;
|
|
996
1039
|
let buffer$1 = [];
|
|
997
1040
|
const subscription = characteristic.monitor((error, c) => {
|
|
998
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p
|
|
1041
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
999
1042
|
const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
|
|
1000
1043
|
if (error) {
|
|
1001
1044
|
Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
|
|
@@ -1007,19 +1050,21 @@ class ReactNativeBleTransport {
|
|
|
1007
1050
|
Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
1008
1051
|
return;
|
|
1009
1052
|
}
|
|
1053
|
+
if ((this.getActiveProtocol(uuid) === 'V2' || this.acquiringProtocolV2.has(uuid)) &&
|
|
1054
|
+
isNativeBleStaleBondError(error)) {
|
|
1055
|
+
this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1010
1058
|
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
1011
1059
|
let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1012
1060
|
if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
|
|
1013
1061
|
errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
|
|
1014
1062
|
}
|
|
1015
|
-
else if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
((
|
|
1020
|
-
((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('The handle is invalid')) ||
|
|
1021
|
-
((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('Writing is not permitted')) ||
|
|
1022
|
-
((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('notify change failed for device'))) {
|
|
1063
|
+
else if (((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Cannot write client characteristic config descriptor')) ||
|
|
1064
|
+
((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot find client characteristic config descriptor')) ||
|
|
1065
|
+
((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('The handle is invalid')) ||
|
|
1066
|
+
((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('Writing is not permitted')) ||
|
|
1067
|
+
((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('notify change failed for device'))) {
|
|
1023
1068
|
errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
|
|
1024
1069
|
}
|
|
1025
1070
|
this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
|
|
@@ -1027,17 +1072,14 @@ class ReactNativeBleTransport {
|
|
|
1027
1072
|
}
|
|
1028
1073
|
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1029
1074
|
let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1030
|
-
if ((
|
|
1075
|
+
if ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('The connection has timed out unexpectedly')) {
|
|
1031
1076
|
ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
|
|
1032
1077
|
}
|
|
1033
|
-
if ((
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
((
|
|
1038
|
-
((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('The handle is invalid')) ||
|
|
1039
|
-
((_o = error.reason) === null || _o === void 0 ? void 0 : _o.includes('Writing is not permitted')) ||
|
|
1040
|
-
((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
|
|
1078
|
+
if (((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('Cannot write client characteristic config descriptor')) ||
|
|
1079
|
+
((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Cannot find client characteristic config descriptor')) ||
|
|
1080
|
+
((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('The handle is invalid')) ||
|
|
1081
|
+
((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Writing is not permitted')) ||
|
|
1082
|
+
((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('notify change failed for device'))) {
|
|
1041
1083
|
const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
|
|
1042
1084
|
this.runPromise.reject(notifyError);
|
|
1043
1085
|
Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
|
|
@@ -1079,7 +1121,7 @@ class ReactNativeBleTransport {
|
|
|
1079
1121
|
bufferLength = 0;
|
|
1080
1122
|
buffer$1 = [];
|
|
1081
1123
|
if (this.runPromiseDeviceId === uuid) {
|
|
1082
|
-
(
|
|
1124
|
+
(_o = this.runPromise) === null || _o === void 0 ? void 0 : _o.resolve(value.toString('hex'));
|
|
1083
1125
|
}
|
|
1084
1126
|
}
|
|
1085
1127
|
}
|
|
@@ -1090,7 +1132,7 @@ class ReactNativeBleTransport {
|
|
|
1090
1132
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
1091
1133
|
}
|
|
1092
1134
|
else if (this.runPromiseDeviceId === uuid) {
|
|
1093
|
-
(
|
|
1135
|
+
(_p = this.runPromise) === null || _p === void 0 ? void 0 : _p.reject(notifyError);
|
|
1094
1136
|
}
|
|
1095
1137
|
}
|
|
1096
1138
|
}, notifyTransactionId);
|
|
@@ -1144,6 +1186,8 @@ class ReactNativeBleTransport {
|
|
|
1144
1186
|
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1145
1187
|
this.deviceProtocol.delete(uuid);
|
|
1146
1188
|
this.probingProtocols.delete(uuid);
|
|
1189
|
+
this.staleBondErrors.delete(uuid);
|
|
1190
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1147
1191
|
(_e = this.protocolV2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
|
|
1148
1192
|
this.protocolV2Assemblers.delete(uuid);
|
|
1149
1193
|
this.resetProtocolV2Frames(uuid);
|
|
@@ -1429,6 +1473,7 @@ class ReactNativeBleTransport {
|
|
|
1429
1473
|
}
|
|
1430
1474
|
this.deviceProtocol.delete(session);
|
|
1431
1475
|
this.probingProtocols.delete(session);
|
|
1476
|
+
this.staleBondErrors.delete(session);
|
|
1432
1477
|
this.deviceProtocolHints.delete(session);
|
|
1433
1478
|
this.sessionProtocols.delete(session);
|
|
1434
1479
|
this.protocolReprobeFailures.delete(session);
|
|
@@ -1607,6 +1652,8 @@ class ReactNativeBleTransport {
|
|
|
1607
1652
|
}
|
|
1608
1653
|
this.deviceProtocol.delete(uuid);
|
|
1609
1654
|
this.probingProtocols.delete(uuid);
|
|
1655
|
+
this.staleBondErrors.delete(uuid);
|
|
1656
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1610
1657
|
this.protocolV2Assemblers.delete(uuid);
|
|
1611
1658
|
this.resetProtocolV2Frames(uuid);
|
|
1612
1659
|
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
@@ -1676,6 +1723,8 @@ class ReactNativeBleTransport {
|
|
|
1676
1723
|
}
|
|
1677
1724
|
this.deviceProtocol.delete(uuid);
|
|
1678
1725
|
this.probingProtocols.delete(uuid);
|
|
1726
|
+
this.staleBondErrors.delete(uuid);
|
|
1727
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1679
1728
|
this.protocolV2Assemblers.delete(uuid);
|
|
1680
1729
|
this.resetProtocolV2Frames(uuid);
|
|
1681
1730
|
if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
@@ -1718,8 +1767,9 @@ class ReactNativeBleTransport {
|
|
|
1718
1767
|
});
|
|
1719
1768
|
this.deviceProtocol.clear();
|
|
1720
1769
|
this.probingProtocols.clear();
|
|
1770
|
+
this.staleBondErrors.clear();
|
|
1771
|
+
this.acquiringProtocolV2.clear();
|
|
1721
1772
|
this.sessionProtocols.clear();
|
|
1722
|
-
this.confirmedProtocolV2.clear();
|
|
1723
1773
|
this.protocolReprobeFailures.clear();
|
|
1724
1774
|
this.writeTimeoutCounts.clear();
|
|
1725
1775
|
this.connectionSetupTimeoutCounts.clear();
|
|
@@ -1732,9 +1782,8 @@ class ReactNativeBleTransport {
|
|
|
1732
1782
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
|
|
1733
1783
|
}
|
|
1734
1784
|
}
|
|
1735
|
-
createProtocolMismatchError(expected
|
|
1736
|
-
|
|
1737
|
-
return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1785
|
+
createProtocolMismatchError(expected) {
|
|
1786
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
|
|
1738
1787
|
}
|
|
1739
1788
|
createProtocolDetectionError() {
|
|
1740
1789
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
|
|
@@ -1763,6 +1812,9 @@ class ReactNativeBleTransport {
|
|
|
1763
1812
|
});
|
|
1764
1813
|
return 'V1';
|
|
1765
1814
|
}
|
|
1815
|
+
if (expectedProtocol === 'V2' || this.acquiringProtocolV2.has(uuid)) {
|
|
1816
|
+
this.throwIfStaleBondError(uuid);
|
|
1817
|
+
}
|
|
1766
1818
|
if (expectedProtocol === 'V2') {
|
|
1767
1819
|
if (yield this.probeProtocolV2(uuid)) {
|
|
1768
1820
|
this.deviceProtocol.set(uuid, 'V2');
|
|
@@ -1775,7 +1827,7 @@ class ReactNativeBleTransport {
|
|
|
1775
1827
|
});
|
|
1776
1828
|
return 'V2';
|
|
1777
1829
|
}
|
|
1778
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
1830
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
1779
1831
|
}
|
|
1780
1832
|
const sessionProtocol = this.sessionProtocols.get(uuid);
|
|
1781
1833
|
const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
|
|
@@ -1893,6 +1945,7 @@ class ReactNativeBleTransport {
|
|
|
1893
1945
|
}
|
|
1894
1946
|
this.probingProtocols.set(uuid, 'V2');
|
|
1895
1947
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1948
|
+
this.throwIfStaleBondError(uuid);
|
|
1896
1949
|
const detected = yield transport.probeProtocolV2({
|
|
1897
1950
|
call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
|
|
1898
1951
|
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
|
|
@@ -1903,6 +1956,7 @@ class ReactNativeBleTransport {
|
|
|
1903
1956
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1904
1957
|
this.resetProtocolV2Frames(uuid);
|
|
1905
1958
|
},
|
|
1959
|
+
shouldRethrow: hdShared.isBleStaleBondHardwareError,
|
|
1906
1960
|
});
|
|
1907
1961
|
if (!detected) {
|
|
1908
1962
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -1963,6 +2017,19 @@ class ReactNativeBleTransport {
|
|
|
1963
2017
|
framePromise.reject(error);
|
|
1964
2018
|
}
|
|
1965
2019
|
}
|
|
2020
|
+
rememberStaleBondError(uuid, error) {
|
|
2021
|
+
this.staleBondErrors.set(uuid, error);
|
|
2022
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
2023
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
2024
|
+
this.runPromise.reject(error);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
throwIfStaleBondError(uuid) {
|
|
2028
|
+
const error = this.staleBondErrors.get(uuid);
|
|
2029
|
+
if (error) {
|
|
2030
|
+
throw error;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
1966
2033
|
readProtocolV2Frame(uuid) {
|
|
1967
2034
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1968
2035
|
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
@@ -2011,6 +2078,11 @@ class ReactNativeBleTransport {
|
|
|
2011
2078
|
return;
|
|
2012
2079
|
}
|
|
2013
2080
|
catch (error) {
|
|
2081
|
+
if (isNativeBleStaleBondError(error) || hdShared.isBleStaleBondHardwareError(error)) {
|
|
2082
|
+
const bondError = toBleStaleBondHardwareError(error);
|
|
2083
|
+
this.rememberStaleBondError(uuid, bondError);
|
|
2084
|
+
throw bondError;
|
|
2085
|
+
}
|
|
2014
2086
|
if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
2015
2087
|
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
2016
2088
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.185",
|
|
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.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.185",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.185",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.185",
|
|
26
26
|
"@onekeyfe/react-native-ble-utils": "^0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "dba45132513abeef33dc448824b4eaace74900ee"
|
|
30
30
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { isNativeBleStaleBondError, toBleStaleBondHardwareError } from '../bleStaleBond';
|
|
4
|
+
|
|
5
|
+
describe('native BLE stale bond mapping', () => {
|
|
6
|
+
test.each([
|
|
7
|
+
[
|
|
8
|
+
{ attErrorCode: 15, reason: 'Encryption is insufficient' },
|
|
9
|
+
HardwareErrorCode.BleDeviceBondError,
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
{ attErrorCode: 5, reason: 'GATT_INSUF_AUTHENTICATION' },
|
|
13
|
+
HardwareErrorCode.BleDeviceBondError,
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
{ iosErrorCode: 14, reason: 'Peer removed pairing information' },
|
|
17
|
+
HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
18
|
+
],
|
|
19
|
+
])('maps %j to %s', (nativeError, errorCode) => {
|
|
20
|
+
expect(isNativeBleStaleBondError(nativeError)).toBe(true);
|
|
21
|
+
expect(toBleStaleBondHardwareError(nativeError)).toMatchObject({ errorCode });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('does not treat the generic ATT unlikely error as a stale bond', () => {
|
|
25
|
+
expect(isNativeBleStaleBondError({ attErrorCode: 14, reason: 'Unlikely error' })).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('does not treat a generic disconnect as a stale bond', () => {
|
|
29
|
+
expect(isNativeBleStaleBondError({ reason: 'Device disconnected' })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -109,9 +109,11 @@ const schemas = {
|
|
|
109
109
|
const createHarness = ({
|
|
110
110
|
deviceName = 'OneKey Pro 2',
|
|
111
111
|
isWritableWithResponse = true,
|
|
112
|
+
monitorError,
|
|
112
113
|
}: {
|
|
113
114
|
deviceName?: string;
|
|
114
115
|
isWritableWithResponse?: boolean;
|
|
116
|
+
monitorError?: (Error & { reason?: string; attErrorCode?: number; iosErrorCode?: number }) | null;
|
|
115
117
|
} = {}) => {
|
|
116
118
|
const uuid = 'rn-pro2-id';
|
|
117
119
|
const sentSeqs: number[] = [];
|
|
@@ -130,6 +132,9 @@ const createHarness = ({
|
|
|
130
132
|
isNotifiable: true,
|
|
131
133
|
monitor: jest.fn(callback => {
|
|
132
134
|
notifyCallback = callback;
|
|
135
|
+
if (monitorError) {
|
|
136
|
+
queueMicrotask(() => callback(monitorError, null));
|
|
137
|
+
}
|
|
133
138
|
return { remove: jest.fn() };
|
|
134
139
|
}),
|
|
135
140
|
};
|
|
@@ -405,6 +410,22 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
405
410
|
await transport.release(uuid, true);
|
|
406
411
|
});
|
|
407
412
|
|
|
413
|
+
test('keeps native stale-bond write mapping out of Protocol V1 calls', async () => {
|
|
414
|
+
const { transport, uuid, writeCharacteristic } = createV1Harness();
|
|
415
|
+
const nativeError = Object.assign(new Error('Encryption is insufficient'), {
|
|
416
|
+
attErrorCode: 15,
|
|
417
|
+
reason: 'Encryption is insufficient',
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
421
|
+
writeCharacteristic.writeWithResponse.mockRejectedValueOnce(nativeError);
|
|
422
|
+
|
|
423
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
|
|
424
|
+
errorCode: HardwareErrorCode.BleWriteCharacteristicError,
|
|
425
|
+
});
|
|
426
|
+
await transport.release(uuid, true);
|
|
427
|
+
});
|
|
428
|
+
|
|
408
429
|
test('detects Protocol V2 on iOS without using the BLE name as a protocol hint', async () => {
|
|
409
430
|
const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
|
|
410
431
|
deviceName: 'Pro2 6E9E',
|
|
@@ -484,6 +505,33 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
484
505
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
485
506
|
});
|
|
486
507
|
|
|
508
|
+
test('keeps confirmed V2 authorization across a BLE manager reset', async () => {
|
|
509
|
+
const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
|
|
510
|
+
const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
511
|
+
|
|
512
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
513
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
514
|
+
|
|
515
|
+
(transport as any).resetPlxManager();
|
|
516
|
+
transport.blePlxManager = bleManager as any;
|
|
517
|
+
device.connect = jest.fn().mockResolvedValue(device);
|
|
518
|
+
device.isConnected.mockResolvedValueOnce(false);
|
|
519
|
+
writeCharacteristic.writeWithResponse.mockClear();
|
|
520
|
+
writeCharacteristic.writeWithoutResponse.mockClear();
|
|
521
|
+
|
|
522
|
+
await expect(
|
|
523
|
+
transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
|
|
524
|
+
).resolves.toEqual({
|
|
525
|
+
uuid,
|
|
526
|
+
protocolType: 'V2',
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
530
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
531
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
532
|
+
await transport.release(uuid, true);
|
|
533
|
+
});
|
|
534
|
+
|
|
487
535
|
test.each(['ios', 'android'] as const)(
|
|
488
536
|
'keeps a first expected Protocol V2 probe miss retryable on %s',
|
|
489
537
|
async platform => {
|
|
@@ -519,8 +567,37 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
519
567
|
}
|
|
520
568
|
);
|
|
521
569
|
|
|
570
|
+
test.each([
|
|
571
|
+
[
|
|
572
|
+
'Encryption is insufficient',
|
|
573
|
+
{ reason: 'Encryption is insufficient', attErrorCode: 15 },
|
|
574
|
+
HardwareErrorCode.BleDeviceBondError,
|
|
575
|
+
],
|
|
576
|
+
[
|
|
577
|
+
'Peer removed pairing information',
|
|
578
|
+
{ reason: 'Peer removed pairing information', iosErrorCode: 14 },
|
|
579
|
+
HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
580
|
+
],
|
|
581
|
+
] as const)(
|
|
582
|
+
'fails Protocol V2 acquire immediately on %s instead of waiting for Ping',
|
|
583
|
+
async (_label, nativeError, errorCode) => {
|
|
584
|
+
const { transport, uuid, device } = createHarness({
|
|
585
|
+
monitorError: Object.assign(new Error(nativeError.reason), nativeError),
|
|
586
|
+
});
|
|
587
|
+
const probe = jest.spyOn(transport as any, 'probeProtocolV2');
|
|
588
|
+
|
|
589
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
|
|
590
|
+
errorCode,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
expect(probe).not.toHaveBeenCalled();
|
|
594
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
595
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
596
|
+
}
|
|
597
|
+
);
|
|
598
|
+
|
|
522
599
|
test.each(['ios', 'android'] as const)(
|
|
523
|
-
'
|
|
600
|
+
'keeps a confirmed Protocol V2 probe miss retryable on %s without native bond evidence',
|
|
524
601
|
async platform => {
|
|
525
602
|
setPlatformOS(platform);
|
|
526
603
|
const { transport, uuid, device } = createHarness();
|
|
@@ -529,10 +606,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
529
606
|
jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
|
|
530
607
|
|
|
531
608
|
await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
|
|
532
|
-
errorCode: HardwareErrorCode.
|
|
609
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
533
610
|
});
|
|
534
611
|
|
|
535
|
-
expect(device.cancelConnection).toHaveBeenCalled();
|
|
612
|
+
expect(device.cancelConnection).not.toHaveBeenCalled();
|
|
536
613
|
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
537
614
|
}
|
|
538
615
|
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ERRORS,
|
|
3
|
+
HardwareErrorCode,
|
|
4
|
+
isBleStaleBondErrorText,
|
|
5
|
+
isBleStaleBondHardwareError,
|
|
6
|
+
} from '@onekeyfe/hd-shared';
|
|
7
|
+
|
|
8
|
+
export { isBleStaleBondHardwareError };
|
|
9
|
+
|
|
10
|
+
const ATT_INSUFFICIENT_AUTHENTICATION = 5;
|
|
11
|
+
const ATT_INSUFFICIENT_ENCRYPTION = 15;
|
|
12
|
+
const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
|
|
13
|
+
|
|
14
|
+
type NativeBleErrorFields = {
|
|
15
|
+
attErrorCode?: unknown;
|
|
16
|
+
iosErrorCode?: unknown;
|
|
17
|
+
reason?: unknown;
|
|
18
|
+
message?: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const nativeErrorText = (error: NativeBleErrorFields) =>
|
|
22
|
+
[error.reason, error.message]
|
|
23
|
+
.filter((value): value is string => typeof value === 'string')
|
|
24
|
+
.join(' ');
|
|
25
|
+
|
|
26
|
+
export const isNativeBleStaleBondError = (error: unknown): boolean => {
|
|
27
|
+
if (!error || typeof error !== 'object') {
|
|
28
|
+
return typeof error === 'string' ? isBleStaleBondErrorText(error) : false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nativeError = error as NativeBleErrorFields;
|
|
32
|
+
if (
|
|
33
|
+
nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
|
|
34
|
+
nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
|
|
35
|
+
nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION
|
|
36
|
+
) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return isBleStaleBondErrorText(nativeErrorText(nativeError));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const toBleStaleBondHardwareError = (error: unknown) => {
|
|
44
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
45
|
+
return error as Error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const nativeError = (error ?? {}) as NativeBleErrorFields;
|
|
49
|
+
const text = nativeErrorText(nativeError);
|
|
50
|
+
const peerRemoved =
|
|
51
|
+
nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
|
|
52
|
+
text.includes('Peer removed pairing information');
|
|
53
|
+
|
|
54
|
+
return ERRORS.TypedError(
|
|
55
|
+
peerRemoved
|
|
56
|
+
? HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
57
|
+
: HardwareErrorCode.BleDeviceBondError,
|
|
58
|
+
text || undefined
|
|
59
|
+
);
|
|
60
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PermissionsAndroid, Platform } from 'react-native';
|
|
2
2
|
import { Buffer } from 'buffer';
|
|
3
3
|
import {
|
|
4
|
-
BleATTErrorCode,
|
|
5
4
|
BleError,
|
|
6
5
|
BleErrorCode,
|
|
7
6
|
BleManager as BlePlxManager,
|
|
@@ -48,6 +47,11 @@ import {
|
|
|
48
47
|
getInfosForServiceUuid,
|
|
49
48
|
isSameBleUuid,
|
|
50
49
|
} from './constants';
|
|
50
|
+
import {
|
|
51
|
+
isBleStaleBondHardwareError,
|
|
52
|
+
isNativeBleStaleBondError,
|
|
53
|
+
toBleStaleBondHardwareError,
|
|
54
|
+
} from './bleStaleBond';
|
|
51
55
|
import { isHeaderChunk } from './utils/validateNotify';
|
|
52
56
|
import BleTransport from './BleTransport';
|
|
53
57
|
import timer from './utils/timer';
|
|
@@ -316,15 +320,10 @@ const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'c
|
|
|
316
320
|
|
|
317
321
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
318
322
|
|
|
319
|
-
function remapError(error: IOBleErrorRemap) {
|
|
323
|
+
function remapError(error: IOBleErrorRemap, mapProtocolV2StaleBond: boolean) {
|
|
320
324
|
if (error instanceof BleError) {
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
// @ts-expect-error
|
|
324
|
-
error.iosErrorCode === BleATTErrorCode.UnlikelyError ||
|
|
325
|
-
error.reason === 'Peer removed pairing information'
|
|
326
|
-
) {
|
|
327
|
-
throw ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
325
|
+
if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
|
|
326
|
+
throw toBleStaleBondHardwareError(error);
|
|
328
327
|
}
|
|
329
328
|
|
|
330
329
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -399,6 +398,16 @@ export default class ReactNativeBleTransport {
|
|
|
399
398
|
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
400
399
|
private protocolReprobeFailures: Map<string, number> = new Map();
|
|
401
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Native encryption/pairing failures seen before Protocol V2 probe starts.
|
|
403
|
+
* Pro2/Neo GATT connect can succeed on a stale iOS bond; the CCCD write then
|
|
404
|
+
* fails with ATT 5/15. Remember it so detectProtocol fails immediately.
|
|
405
|
+
*/
|
|
406
|
+
private staleBondErrors: Map<string, Error> = new Map();
|
|
407
|
+
|
|
408
|
+
/** Strict or previously confirmed V2 target while acquire installs notifications. */
|
|
409
|
+
private acquiringProtocolV2 = new Set<string>();
|
|
410
|
+
|
|
402
411
|
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
403
412
|
|
|
404
413
|
private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
|
|
@@ -886,6 +895,9 @@ export default class ReactNativeBleTransport {
|
|
|
886
895
|
|
|
887
896
|
private async acquireUnlocked(input: FirmwareInstallBleAcquireInput) {
|
|
888
897
|
const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
|
|
898
|
+
const shouldMapProtocolV2StaleBond = expectedProtocol
|
|
899
|
+
? expectedProtocol === 'V2'
|
|
900
|
+
: this.confirmedProtocolV2.has(uuid);
|
|
889
901
|
|
|
890
902
|
const cachedTransport = transportCache[uuid];
|
|
891
903
|
if (skipProtocolProbe && !cachedTransport && this.blePlxManager) {
|
|
@@ -1012,7 +1024,7 @@ export default class ReactNativeBleTransport {
|
|
|
1012
1024
|
Log?.debug('device already connected');
|
|
1013
1025
|
throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
|
|
1014
1026
|
} else {
|
|
1015
|
-
remapError(e);
|
|
1027
|
+
remapError(e, shouldMapProtocolV2StaleBond);
|
|
1016
1028
|
}
|
|
1017
1029
|
}
|
|
1018
1030
|
}
|
|
@@ -1056,7 +1068,7 @@ export default class ReactNativeBleTransport {
|
|
|
1056
1068
|
}
|
|
1057
1069
|
}
|
|
1058
1070
|
} else {
|
|
1059
|
-
remapError(e);
|
|
1071
|
+
remapError(e, shouldMapProtocolV2StaleBond);
|
|
1060
1072
|
}
|
|
1061
1073
|
}
|
|
1062
1074
|
}
|
|
@@ -1076,12 +1088,16 @@ export default class ReactNativeBleTransport {
|
|
|
1076
1088
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
1077
1089
|
}
|
|
1078
1090
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
});
|
|
1091
|
+
if (shouldMapProtocolV2StaleBond) {
|
|
1092
|
+
this.acquiringProtocolV2.add(uuid);
|
|
1093
|
+
}
|
|
1083
1094
|
|
|
1084
1095
|
try {
|
|
1096
|
+
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
1097
|
+
writeCharacteristic,
|
|
1098
|
+
notifyCharacteristic,
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1085
1101
|
if (skipProtocolProbe) {
|
|
1086
1102
|
if (!expectedProtocol) {
|
|
1087
1103
|
throw ERRORS.TypedError(
|
|
@@ -1089,7 +1105,10 @@ export default class ReactNativeBleTransport {
|
|
|
1089
1105
|
'skipProtocolProbe requires an expected BLE protocol'
|
|
1090
1106
|
);
|
|
1091
1107
|
}
|
|
1092
|
-
|
|
1108
|
+
const hasConfirmedProtocol =
|
|
1109
|
+
this.sessionProtocols.get(uuid) === expectedProtocol ||
|
|
1110
|
+
(expectedProtocol === 'V2' && this.confirmedProtocolV2.has(uuid));
|
|
1111
|
+
if (!hasConfirmedProtocol) {
|
|
1093
1112
|
throw ERRORS.TypedError(
|
|
1094
1113
|
HardwareErrorCode.RuntimeError,
|
|
1095
1114
|
'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint'
|
|
@@ -1126,12 +1145,14 @@ export default class ReactNativeBleTransport {
|
|
|
1126
1145
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1127
1146
|
return { uuid, protocolType };
|
|
1128
1147
|
} catch (error) {
|
|
1129
|
-
if ((error
|
|
1148
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1130
1149
|
await this.disconnectUnlocked(uuid);
|
|
1131
1150
|
} else {
|
|
1132
1151
|
await this.releaseUnlocked(uuid, true);
|
|
1133
1152
|
}
|
|
1134
1153
|
throw error;
|
|
1154
|
+
} finally {
|
|
1155
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1135
1156
|
}
|
|
1136
1157
|
}
|
|
1137
1158
|
|
|
@@ -1159,17 +1180,21 @@ export default class ReactNativeBleTransport {
|
|
|
1159
1180
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
1160
1181
|
return;
|
|
1161
1182
|
}
|
|
1183
|
+
if (
|
|
1184
|
+
(this.getActiveProtocol(uuid) === 'V2' || this.acquiringProtocolV2.has(uuid)) &&
|
|
1185
|
+
isNativeBleStaleBondError(error)
|
|
1186
|
+
) {
|
|
1187
|
+
this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1162
1190
|
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
1163
1191
|
let errorCode:
|
|
1164
|
-
| typeof HardwareErrorCode.BleDeviceBondError
|
|
1165
1192
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
1166
1193
|
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
1167
1194
|
| typeof HardwareErrorCode.BleTimeoutError =
|
|
1168
1195
|
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1169
1196
|
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
1170
1197
|
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
1171
|
-
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
1172
|
-
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
1173
1198
|
} else if (
|
|
1174
1199
|
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
1175
1200
|
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
@@ -1184,16 +1209,12 @@ export default class ReactNativeBleTransport {
|
|
|
1184
1209
|
}
|
|
1185
1210
|
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1186
1211
|
let ERROR:
|
|
1187
|
-
| typeof HardwareErrorCode.BleDeviceBondError
|
|
1188
1212
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
1189
1213
|
| typeof HardwareErrorCode.BleTimeoutError =
|
|
1190
1214
|
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1191
1215
|
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
1192
1216
|
ERROR = HardwareErrorCode.BleTimeoutError;
|
|
1193
1217
|
}
|
|
1194
|
-
if (error.reason?.includes('Encryption is insufficient')) {
|
|
1195
|
-
ERROR = HardwareErrorCode.BleDeviceBondError;
|
|
1196
|
-
}
|
|
1197
1218
|
if (
|
|
1198
1219
|
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
1199
1220
|
error.reason?.includes('Cannot find client characteristic config descriptor') || // pro firmware 2.3.0 upgrade
|
|
@@ -1328,6 +1349,8 @@ export default class ReactNativeBleTransport {
|
|
|
1328
1349
|
|
|
1329
1350
|
this.deviceProtocol.delete(uuid);
|
|
1330
1351
|
this.probingProtocols.delete(uuid);
|
|
1352
|
+
this.staleBondErrors.delete(uuid);
|
|
1353
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1331
1354
|
// Confirmed protocol and caller hints stay in deviceProtocol / protocolHint.
|
|
1332
1355
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1333
1356
|
this.protocolV2Assemblers.delete(uuid);
|
|
@@ -1700,6 +1723,7 @@ export default class ReactNativeBleTransport {
|
|
|
1700
1723
|
}
|
|
1701
1724
|
this.deviceProtocol.delete(session);
|
|
1702
1725
|
this.probingProtocols.delete(session);
|
|
1726
|
+
this.staleBondErrors.delete(session);
|
|
1703
1727
|
this.deviceProtocolHints.delete(session);
|
|
1704
1728
|
this.sessionProtocols.delete(session);
|
|
1705
1729
|
this.protocolReprobeFailures.delete(session);
|
|
@@ -1923,6 +1947,8 @@ export default class ReactNativeBleTransport {
|
|
|
1923
1947
|
}
|
|
1924
1948
|
this.deviceProtocol.delete(uuid);
|
|
1925
1949
|
this.probingProtocols.delete(uuid);
|
|
1950
|
+
this.staleBondErrors.delete(uuid);
|
|
1951
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1926
1952
|
this.protocolV2Assemblers.delete(uuid);
|
|
1927
1953
|
this.resetProtocolV2Frames(uuid);
|
|
1928
1954
|
|
|
@@ -2019,6 +2045,8 @@ export default class ReactNativeBleTransport {
|
|
|
2019
2045
|
}
|
|
2020
2046
|
this.deviceProtocol.delete(uuid);
|
|
2021
2047
|
this.probingProtocols.delete(uuid);
|
|
2048
|
+
this.staleBondErrors.delete(uuid);
|
|
2049
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
2022
2050
|
this.protocolV2Assemblers.delete(uuid);
|
|
2023
2051
|
this.resetProtocolV2Frames(uuid);
|
|
2024
2052
|
|
|
@@ -2069,8 +2097,11 @@ export default class ReactNativeBleTransport {
|
|
|
2069
2097
|
});
|
|
2070
2098
|
this.deviceProtocol.clear();
|
|
2071
2099
|
this.probingProtocols.clear();
|
|
2100
|
+
this.staleBondErrors.clear();
|
|
2101
|
+
this.acquiringProtocolV2.clear();
|
|
2072
2102
|
this.sessionProtocols.clear();
|
|
2073
|
-
|
|
2103
|
+
// Keep transport-lifetime V2 proof so the same endpoint can finish a no-probe
|
|
2104
|
+
// firmware reconnect after the native BLE manager is recreated.
|
|
2074
2105
|
this.protocolReprobeFailures.clear();
|
|
2075
2106
|
this.writeTimeoutCounts.clear();
|
|
2076
2107
|
this.connectionSetupTimeoutCounts.clear();
|
|
@@ -2083,12 +2114,11 @@ export default class ReactNativeBleTransport {
|
|
|
2083
2114
|
}
|
|
2084
2115
|
}
|
|
2085
2116
|
|
|
2086
|
-
private createProtocolMismatchError(expected: ProtocolType
|
|
2087
|
-
// A
|
|
2088
|
-
//
|
|
2089
|
-
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
2117
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
2118
|
+
// A protocol probe miss alone does not prove that the OS bond is stale.
|
|
2119
|
+
// Native authentication/encryption failures are mapped separately.
|
|
2090
2120
|
return ERRORS.TypedError(
|
|
2091
|
-
|
|
2121
|
+
HardwareErrorCode.RuntimeError,
|
|
2092
2122
|
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
2093
2123
|
);
|
|
2094
2124
|
}
|
|
@@ -2140,6 +2170,10 @@ export default class ReactNativeBleTransport {
|
|
|
2140
2170
|
return 'V1';
|
|
2141
2171
|
}
|
|
2142
2172
|
|
|
2173
|
+
if (expectedProtocol === 'V2' || this.acquiringProtocolV2.has(uuid)) {
|
|
2174
|
+
this.throwIfStaleBondError(uuid);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2143
2177
|
if (expectedProtocol === 'V2') {
|
|
2144
2178
|
if (await this.probeProtocolV2(uuid)) {
|
|
2145
2179
|
this.deviceProtocol.set(uuid, 'V2');
|
|
@@ -2152,7 +2186,7 @@ export default class ReactNativeBleTransport {
|
|
|
2152
2186
|
});
|
|
2153
2187
|
return 'V2';
|
|
2154
2188
|
}
|
|
2155
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
2189
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
2156
2190
|
}
|
|
2157
2191
|
|
|
2158
2192
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
@@ -2296,6 +2330,7 @@ export default class ReactNativeBleTransport {
|
|
|
2296
2330
|
|
|
2297
2331
|
this.probingProtocols.set(uuid, 'V2');
|
|
2298
2332
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2333
|
+
this.throwIfStaleBondError(uuid);
|
|
2299
2334
|
const detected = await probeProtocolV2Helper({
|
|
2300
2335
|
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
2301
2336
|
this.callProtocolV2(uuid, name, data, options),
|
|
@@ -2306,6 +2341,7 @@ export default class ReactNativeBleTransport {
|
|
|
2306
2341
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2307
2342
|
this.resetProtocolV2Frames(uuid);
|
|
2308
2343
|
},
|
|
2344
|
+
shouldRethrow: isBleStaleBondHardwareError,
|
|
2309
2345
|
});
|
|
2310
2346
|
if (!detected) {
|
|
2311
2347
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -2374,6 +2410,21 @@ export default class ReactNativeBleTransport {
|
|
|
2374
2410
|
}
|
|
2375
2411
|
}
|
|
2376
2412
|
|
|
2413
|
+
private rememberStaleBondError(uuid: string, error: Error) {
|
|
2414
|
+
this.staleBondErrors.set(uuid, error);
|
|
2415
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
2416
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
2417
|
+
this.runPromise.reject(error);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
private throwIfStaleBondError(uuid: string) {
|
|
2422
|
+
const error = this.staleBondErrors.get(uuid);
|
|
2423
|
+
if (error) {
|
|
2424
|
+
throw error;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2377
2428
|
private async readProtocolV2Frame(uuid: string) {
|
|
2378
2429
|
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
2379
2430
|
if (queuedFrame) {
|
|
@@ -2432,6 +2483,11 @@ export default class ReactNativeBleTransport {
|
|
|
2432
2483
|
assertCurrentGeneration();
|
|
2433
2484
|
return;
|
|
2434
2485
|
} catch (error) {
|
|
2486
|
+
if (isNativeBleStaleBondError(error) || isBleStaleBondHardwareError(error)) {
|
|
2487
|
+
const bondError = toBleStaleBondHardwareError(error);
|
|
2488
|
+
this.rememberStaleBondError(uuid, bondError);
|
|
2489
|
+
throw bondError;
|
|
2490
|
+
}
|
|
2435
2491
|
if (
|
|
2436
2492
|
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
2437
2493
|
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|