@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.
@@ -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;
@@ -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
@@ -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 (error.iosErrorCode === reactNativeBlePlx.BleATTErrorCode.UnlikelyError ||
379
- error.reason === 'Peer removed pairing information') {
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
- yield this.installTransportForAcquire(uuid, acquiredDevice, {
947
- writeCharacteristic,
948
- notifyCharacteristic,
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
- if (this.sessionProtocols.get(uuid) !== expectedProtocol) {
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 === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceBondError) {
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, _q, _r;
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('Encryption is insufficient')) {
1016
- errorCode = hdShared.HardwareErrorCode.BleDeviceBondError;
1017
- }
1018
- else if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
1019
- ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('Cannot find client characteristic config descriptor')) ||
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 ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
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 ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Encryption is insufficient')) {
1034
- ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
1035
- }
1036
- if (((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('Cannot write client characteristic config descriptor')) ||
1037
- ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Cannot find client characteristic config descriptor')) ||
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
- (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
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
- (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
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, uuid) {
1736
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
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, uuid);
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.183",
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.183",
24
- "@onekeyfe/hd-shared": "1.2.0-alpha.183",
25
- "@onekeyfe/hd-transport": "1.2.0-alpha.183",
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": "fd9b6e749495b0144e734dbe32c57cf483547349"
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
- 'reports a stale bond on %s when a previously confirmed Protocol V2 device stops responding',
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.BleDeviceBondError,
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
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
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
- await this.installTransportForAcquire(uuid, acquiredDevice, {
1080
- writeCharacteristic,
1081
- notifyCharacteristic,
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
- if (this.sessionProtocols.get(uuid) !== expectedProtocol) {
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 as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleDeviceBondError) {
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
- this.confirmedProtocolV2.clear();
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, uuid: string) {
2087
- // A generic Ping miss is not a bond failure. Only a later miss after this
2088
- // endpoint already answered V2, or a native encryption/pairing error, is.
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
- isStaleV2Bond ? HardwareErrorCode.BleDeviceBondError : HardwareErrorCode.RuntimeError,
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, uuid);
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