@onekeyfe/hd-transport-react-native 1.2.2-alpha.100 → 1.2.2-alpha.3

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,UAkBzD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -38,6 +38,14 @@ type BleAcquireInput = {
38
38
  protocolHint?: ProtocolType;
39
39
  };
40
40
 
41
+ type FirmwareInstallBleAcquireInput = BleAcquireInput & {
42
+ /**
43
+ * Reuse the already-verified protocol after an expected firmware-install
44
+ * disconnect. The install loader accepts status requests but may not answer
45
+ * the generic protocol probe used by a normal acquire.
46
+ */
47
+ skipProtocolProbe?: boolean;
48
+ };
41
49
  type FirmwareUploadWriteRetryType = 'congested';
42
50
  type ResolvedBleCharacteristics = {
43
51
  writeCharacteristic: Characteristic;
@@ -126,6 +134,14 @@ declare class ReactNativeBleTransport {
126
134
  private confirmedProtocolV2;
127
135
  /** Consecutive detections that failed while trusting sessionProtocols. */
128
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;
129
145
  private protocolV2Assemblers;
130
146
  private protocolV2FrameQueues;
131
147
  private protocolV2FramePromises;
@@ -155,7 +171,7 @@ declare class ReactNativeBleTransport {
155
171
  */
156
172
  enumerate(): Promise<IOneKeyDevice[]>;
157
173
  private installTransportForAcquire;
158
- acquire(input: BleAcquireInput): Promise<{
174
+ acquire(input: FirmwareInstallBleAcquireInput): Promise<{
159
175
  uuid: string;
160
176
  protocolType: ProtocolType;
161
177
  }>;
@@ -216,6 +232,8 @@ declare class ReactNativeBleTransport {
216
232
  private resolveProtocolV2Frame;
217
233
  private resetProtocolV2Frames;
218
234
  private rejectProtocolV2Frames;
235
+ private rememberStaleBondError;
236
+ private throwIfStaleBondError;
219
237
  private readProtocolV2Frame;
220
238
  private writeProtocolV2Packet;
221
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,38 @@ 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 normalizedText = text.toLowerCase();
222
+ const peerRemoved = nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
223
+ normalizedText.includes('peer removed pairing information');
224
+ return hdShared.ERRORS.TypedError(peerRemoved
225
+ ? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
226
+ : hdShared.HardwareErrorCode.BleDeviceBondError, text || undefined);
227
+ };
228
+
197
229
  const isHeaderChunk = (chunk) => {
198
230
  if (chunk.length < 9)
199
231
  return false;
@@ -372,12 +404,11 @@ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void
372
404
  }
373
405
  });
374
406
  const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
375
- function remapError(error) {
407
+ function remapError(error, mapProtocolV2StaleBond) {
376
408
  var _a;
377
409
  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);
410
+ if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
411
+ throw toBleStaleBondHardwareError(error);
381
412
  }
382
413
  if ((error === null || error === void 0 ? void 0 : error.attErrorCode) === 22) {
383
414
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondError);
@@ -408,6 +439,8 @@ class ReactNativeBleTransport {
408
439
  this.sessionProtocols = new Map();
409
440
  this.confirmedProtocolV2 = new Set();
410
441
  this.protocolReprobeFailures = new Map();
442
+ this.staleBondErrors = new Map();
443
+ this.acquiringProtocolV2 = new Set();
411
444
  this.protocolV2Assemblers = new Map();
412
445
  this.protocolV2FrameQueues = new Map();
413
446
  this.protocolV2FramePromises = new Map();
@@ -802,19 +835,46 @@ class ReactNativeBleTransport {
802
835
  acquireUnlocked(input) {
803
836
  var _a;
804
837
  return __awaiter(this, void 0, void 0, function* () {
805
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
838
+ const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
839
+ const shouldMapProtocolV2StaleBond = expectedProtocol
840
+ ? expectedProtocol === 'V2'
841
+ : this.confirmedProtocolV2.has(uuid);
806
842
  const cachedTransport = transportCache[uuid];
843
+ if (skipProtocolProbe && !cachedTransport && this.blePlxManager) {
844
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] refresh uncached BLE connection for firmware install:', uuid);
845
+ const manager = this.blePlxManager;
846
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
847
+ yield this.runBestEffortNativeOperation('firmware install reconnect: cancel uncached device connection', () => manager.cancelDeviceConnection(uuid));
848
+ }));
849
+ }
807
850
  if (cachedTransport) {
808
- const cachedProtocol = this.deviceProtocol.get(uuid);
809
- const isCachedDeviceConnected = yield cachedTransport.device.isConnected().catch(() => false);
810
- if (isCachedDeviceConnected &&
811
- cachedProtocol &&
812
- (!expectedProtocol || cachedProtocol === expectedProtocol)) {
813
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
814
- return { uuid, protocolType: cachedProtocol };
851
+ if (skipProtocolProbe) {
852
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] refresh cached BLE connection for firmware install:', uuid);
853
+ const manager = this.blePlxManager;
854
+ yield this.releaseUnlocked(uuid, true);
855
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
856
+ const operations = [];
857
+ if (manager) {
858
+ operations.push(this.runBestEffortNativeOperation('firmware install reconnect: cancel device connection', () => manager.cancelDeviceConnection(uuid)));
859
+ }
860
+ operations.push(this.runBestEffortNativeOperation('firmware install reconnect: device cancel connection', () => cachedTransport.device.cancelConnection()));
861
+ yield Promise.all(operations);
862
+ }));
863
+ }
864
+ else {
865
+ const cachedProtocol = this.deviceProtocol.get(uuid);
866
+ const isCachedDeviceConnected = yield cachedTransport.device
867
+ .isConnected()
868
+ .catch(() => false);
869
+ if (isCachedDeviceConnected &&
870
+ cachedProtocol &&
871
+ (!expectedProtocol || cachedProtocol === expectedProtocol)) {
872
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
873
+ return { uuid, protocolType: cachedProtocol };
874
+ }
875
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
876
+ yield this.releaseUnlocked(uuid, true);
815
877
  }
816
- Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
817
- yield this.releaseUnlocked(uuid, true);
818
878
  }
819
879
  let device = null;
820
880
  if (forceCleanRunPromise && this.runPromise) {
@@ -871,7 +931,7 @@ class ReactNativeBleTransport {
871
931
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleAlreadyConnected);
872
932
  }
873
933
  else {
874
- remapError(e);
934
+ remapError(e, shouldMapProtocolV2StaleBond);
875
935
  }
876
936
  }
877
937
  }
@@ -905,7 +965,7 @@ class ReactNativeBleTransport {
905
965
  }
906
966
  }
907
967
  else {
908
- remapError(e);
968
+ remapError(e, shouldMapProtocolV2StaleBond);
909
969
  }
910
970
  }
911
971
  }
@@ -919,11 +979,38 @@ class ReactNativeBleTransport {
919
979
  if (protocolHint) {
920
980
  this.deviceProtocolHints.set(uuid, protocolHint);
921
981
  }
922
- yield this.installTransportForAcquire(uuid, acquiredDevice, {
923
- writeCharacteristic,
924
- notifyCharacteristic,
925
- });
982
+ if (shouldMapProtocolV2StaleBond) {
983
+ this.acquiringProtocolV2.add(uuid);
984
+ }
926
985
  try {
986
+ yield this.installTransportForAcquire(uuid, acquiredDevice, {
987
+ writeCharacteristic,
988
+ notifyCharacteristic,
989
+ });
990
+ if (skipProtocolProbe) {
991
+ if (!expectedProtocol) {
992
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected BLE protocol');
993
+ }
994
+ const hasConfirmedProtocol = this.sessionProtocols.get(uuid) === expectedProtocol ||
995
+ (expectedProtocol === 'V2' && this.confirmedProtocolV2.has(uuid));
996
+ if (!hasConfirmedProtocol) {
997
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint');
998
+ }
999
+ this.deviceProtocol.set(uuid, expectedProtocol);
1000
+ this.sessionProtocols.set(uuid, expectedProtocol);
1001
+ this.protocolReprobeFailures.delete(uuid);
1002
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected without probe', {
1003
+ deviceId: uuid,
1004
+ protocol: expectedProtocol,
1005
+ source: 'firmware-install-reconnect',
1006
+ });
1007
+ const currentTransport = transportCache[uuid];
1008
+ if (!currentTransport) {
1009
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1010
+ }
1011
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1012
+ return { uuid, protocolType: expectedProtocol };
1013
+ }
927
1014
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
928
1015
  yield this.installTransportForAcquire(uuid, acquiredDevice);
929
1016
  }));
@@ -935,7 +1022,7 @@ class ReactNativeBleTransport {
935
1022
  return { uuid, protocolType };
936
1023
  }
937
1024
  catch (error) {
938
- if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceBondError) {
1025
+ if (hdShared.isBleStaleBondHardwareError(error)) {
939
1026
  yield this.disconnectUnlocked(uuid);
940
1027
  }
941
1028
  else {
@@ -943,13 +1030,16 @@ class ReactNativeBleTransport {
943
1030
  }
944
1031
  throw error;
945
1032
  }
1033
+ finally {
1034
+ this.acquiringProtocolV2.delete(uuid);
1035
+ }
946
1036
  });
947
1037
  }
948
1038
  _monitorCharacteristic(characteristic, uuid, monitorToken, notifyTransactionId) {
949
1039
  let bufferLength = 0;
950
1040
  let buffer$1 = [];
951
1041
  const subscription = characteristic.monitor((error, c) => {
952
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
1042
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
953
1043
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
954
1044
  if (error) {
955
1045
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -961,19 +1051,21 @@ class ReactNativeBleTransport {
961
1051
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
962
1052
  return;
963
1053
  }
1054
+ if ((this.getActiveProtocol(uuid) === 'V2' || this.acquiringProtocolV2.has(uuid)) &&
1055
+ isNativeBleStaleBondError(error)) {
1056
+ this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
1057
+ return;
1058
+ }
964
1059
  if (this.getActiveProtocol(uuid) === 'V2') {
965
1060
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
966
1061
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
967
1062
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
968
1063
  }
969
- else if ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Encryption is insufficient')) {
970
- errorCode = hdShared.HardwareErrorCode.BleDeviceBondError;
971
- }
972
- else if (((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot write client characteristic config descriptor')) ||
973
- ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('Cannot find client characteristic config descriptor')) ||
974
- ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('The handle is invalid')) ||
975
- ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('Writing is not permitted')) ||
976
- ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('notify change failed for device'))) {
1064
+ else if (((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Cannot write client characteristic config descriptor')) ||
1065
+ ((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot find client characteristic config descriptor')) ||
1066
+ ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('The handle is invalid')) ||
1067
+ ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('Writing is not permitted')) ||
1068
+ ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('notify change failed for device'))) {
977
1069
  errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
978
1070
  }
979
1071
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
@@ -981,17 +1073,14 @@ class ReactNativeBleTransport {
981
1073
  }
982
1074
  if (this.runPromise && this.runPromiseDeviceId === uuid) {
983
1075
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
984
- if ((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('The connection has timed out unexpectedly')) {
1076
+ if ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('The connection has timed out unexpectedly')) {
985
1077
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
986
1078
  }
987
- if ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Encryption is insufficient')) {
988
- ERROR = hdShared.HardwareErrorCode.BleDeviceBondError;
989
- }
990
- if (((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('Cannot write client characteristic config descriptor')) ||
991
- ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Cannot find client characteristic config descriptor')) ||
992
- ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('The handle is invalid')) ||
993
- ((_o = error.reason) === null || _o === void 0 ? void 0 : _o.includes('Writing is not permitted')) ||
994
- ((_p = error.reason) === null || _p === void 0 ? void 0 : _p.includes('notify change failed for device'))) {
1079
+ if (((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('Cannot write client characteristic config descriptor')) ||
1080
+ ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Cannot find client characteristic config descriptor')) ||
1081
+ ((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('The handle is invalid')) ||
1082
+ ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Writing is not permitted')) ||
1083
+ ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('notify change failed for device'))) {
995
1084
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
996
1085
  this.runPromise.reject(notifyError);
997
1086
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
@@ -1033,7 +1122,7 @@ class ReactNativeBleTransport {
1033
1122
  bufferLength = 0;
1034
1123
  buffer$1 = [];
1035
1124
  if (this.runPromiseDeviceId === uuid) {
1036
- (_q = this.runPromise) === null || _q === void 0 ? void 0 : _q.resolve(value.toString('hex'));
1125
+ (_o = this.runPromise) === null || _o === void 0 ? void 0 : _o.resolve(value.toString('hex'));
1037
1126
  }
1038
1127
  }
1039
1128
  }
@@ -1044,7 +1133,7 @@ class ReactNativeBleTransport {
1044
1133
  this.rejectProtocolV2Frames(uuid, notifyError);
1045
1134
  }
1046
1135
  else if (this.runPromiseDeviceId === uuid) {
1047
- (_r = this.runPromise) === null || _r === void 0 ? void 0 : _r.reject(notifyError);
1136
+ (_p = this.runPromise) === null || _p === void 0 ? void 0 : _p.reject(notifyError);
1048
1137
  }
1049
1138
  }
1050
1139
  }, notifyTransactionId);
@@ -1098,6 +1187,8 @@ class ReactNativeBleTransport {
1098
1187
  this.protocolV2HighVolumeLogSignatures.delete(uuid);
1099
1188
  this.deviceProtocol.delete(uuid);
1100
1189
  this.probingProtocols.delete(uuid);
1190
+ this.staleBondErrors.delete(uuid);
1191
+ this.acquiringProtocolV2.delete(uuid);
1101
1192
  (_e = this.protocolV2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1102
1193
  this.protocolV2Assemblers.delete(uuid);
1103
1194
  this.resetProtocolV2Frames(uuid);
@@ -1383,6 +1474,7 @@ class ReactNativeBleTransport {
1383
1474
  }
1384
1475
  this.deviceProtocol.delete(session);
1385
1476
  this.probingProtocols.delete(session);
1477
+ this.staleBondErrors.delete(session);
1386
1478
  this.deviceProtocolHints.delete(session);
1387
1479
  this.sessionProtocols.delete(session);
1388
1480
  this.protocolReprobeFailures.delete(session);
@@ -1561,6 +1653,8 @@ class ReactNativeBleTransport {
1561
1653
  }
1562
1654
  this.deviceProtocol.delete(uuid);
1563
1655
  this.probingProtocols.delete(uuid);
1656
+ this.staleBondErrors.delete(uuid);
1657
+ this.acquiringProtocolV2.delete(uuid);
1564
1658
  this.protocolV2Assemblers.delete(uuid);
1565
1659
  this.resetProtocolV2Frames(uuid);
1566
1660
  if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
@@ -1630,6 +1724,8 @@ class ReactNativeBleTransport {
1630
1724
  }
1631
1725
  this.deviceProtocol.delete(uuid);
1632
1726
  this.probingProtocols.delete(uuid);
1727
+ this.staleBondErrors.delete(uuid);
1728
+ this.acquiringProtocolV2.delete(uuid);
1633
1729
  this.protocolV2Assemblers.delete(uuid);
1634
1730
  this.resetProtocolV2Frames(uuid);
1635
1731
  if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
@@ -1672,8 +1768,9 @@ class ReactNativeBleTransport {
1672
1768
  });
1673
1769
  this.deviceProtocol.clear();
1674
1770
  this.probingProtocols.clear();
1771
+ this.staleBondErrors.clear();
1772
+ this.acquiringProtocolV2.clear();
1675
1773
  this.sessionProtocols.clear();
1676
- this.confirmedProtocolV2.clear();
1677
1774
  this.protocolReprobeFailures.clear();
1678
1775
  this.writeTimeoutCounts.clear();
1679
1776
  this.connectionSetupTimeoutCounts.clear();
@@ -1686,9 +1783,8 @@ class ReactNativeBleTransport {
1686
1783
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1687
1784
  }
1688
1785
  }
1689
- createProtocolMismatchError(expected, uuid) {
1690
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1691
- return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1786
+ createProtocolMismatchError(expected) {
1787
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1692
1788
  }
1693
1789
  createProtocolDetectionError() {
1694
1790
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
@@ -1708,27 +1804,17 @@ class ReactNativeBleTransport {
1708
1804
  detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1709
1805
  var _a;
1710
1806
  return __awaiter(this, void 0, void 0, function* () {
1711
- if (reactNative.Platform.OS === 'ios' && expectedProtocol === 'V1') {
1712
- this.deviceProtocol.set(uuid, expectedProtocol);
1807
+ if (expectedProtocol === 'V1') {
1808
+ this.deviceProtocol.set(uuid, 'V1');
1713
1809
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1714
1810
  deviceId: uuid,
1715
- protocol: expectedProtocol,
1811
+ protocol: 'V1',
1716
1812
  source: 'expected',
1717
1813
  });
1718
- return expectedProtocol;
1814
+ return 'V1';
1719
1815
  }
1720
- if (expectedProtocol === 'V1') {
1721
- if (yield this.probeProtocolV1(uuid)) {
1722
- this.deviceProtocol.set(uuid, 'V1');
1723
- this.sessionProtocols.set(uuid, 'V1');
1724
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1725
- deviceId: uuid,
1726
- protocol: 'V1',
1727
- source: 'expected',
1728
- });
1729
- return 'V1';
1730
- }
1731
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
1816
+ if (expectedProtocol === 'V2' || this.acquiringProtocolV2.has(uuid)) {
1817
+ this.throwIfStaleBondError(uuid);
1732
1818
  }
1733
1819
  if (expectedProtocol === 'V2') {
1734
1820
  if (yield this.probeProtocolV2(uuid)) {
@@ -1742,7 +1828,7 @@ class ReactNativeBleTransport {
1742
1828
  });
1743
1829
  return 'V2';
1744
1830
  }
1745
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
1831
+ throw this.createProtocolMismatchError(expectedProtocol);
1746
1832
  }
1747
1833
  const sessionProtocol = this.sessionProtocols.get(uuid);
1748
1834
  const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
@@ -1860,6 +1946,7 @@ class ReactNativeBleTransport {
1860
1946
  }
1861
1947
  this.probingProtocols.set(uuid, 'V2');
1862
1948
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1949
+ this.throwIfStaleBondError(uuid);
1863
1950
  const detected = yield transport.probeProtocolV2({
1864
1951
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
1865
1952
  timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
@@ -1870,6 +1957,7 @@ class ReactNativeBleTransport {
1870
1957
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1871
1958
  this.resetProtocolV2Frames(uuid);
1872
1959
  },
1960
+ shouldRethrow: hdShared.isBleStaleBondHardwareError,
1873
1961
  });
1874
1962
  if (!detected) {
1875
1963
  this.clearProbeProtocol(uuid, 'V2');
@@ -1930,6 +2018,19 @@ class ReactNativeBleTransport {
1930
2018
  framePromise.reject(error);
1931
2019
  }
1932
2020
  }
2021
+ rememberStaleBondError(uuid, error) {
2022
+ this.staleBondErrors.set(uuid, error);
2023
+ this.rejectProtocolV2Frames(uuid, error);
2024
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
2025
+ this.runPromise.reject(error);
2026
+ }
2027
+ }
2028
+ throwIfStaleBondError(uuid) {
2029
+ const error = this.staleBondErrors.get(uuid);
2030
+ if (error) {
2031
+ throw error;
2032
+ }
2033
+ }
1933
2034
  readProtocolV2Frame(uuid) {
1934
2035
  return __awaiter(this, void 0, void 0, function* () {
1935
2036
  const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
@@ -1978,6 +2079,11 @@ class ReactNativeBleTransport {
1978
2079
  return;
1979
2080
  }
1980
2081
  catch (error) {
2082
+ if (isNativeBleStaleBondError(error) || hdShared.isBleStaleBondHardwareError(error)) {
2083
+ const bondError = toBleStaleBondHardwareError(error);
2084
+ this.rememberStaleBondError(uuid, bondError);
2085
+ throw bondError;
2086
+ }
1981
2087
  if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
1982
2088
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
1983
2089
  throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.2-alpha.100",
3
+ "version": "1.2.2-alpha.3",
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.100",
24
- "@onekeyfe/hd-shared": "1.2.2-alpha.100",
25
- "@onekeyfe/hd-transport": "1.2.2-alpha.100",
23
+ "@onekeyfe/hd-core": "1.2.2-alpha.3",
24
+ "@onekeyfe/hd-shared": "1.2.2-alpha.3",
25
+ "@onekeyfe/hd-transport": "1.2.2-alpha.3",
26
26
  "@onekeyfe/react-native-ble-utils": "^0.1.6",
27
27
  "react-native-ble-plx": "3.5.1"
28
28
  },
29
- "gitHead": "c40dad085297b0e3cc2020bd218c146dff6ed3e1"
29
+ "gitHead": "4efa66df8851607586d0ae6a431a63404beff51b"
30
30
  }
@@ -0,0 +1,39 @@
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
+ [
20
+ { reason: 'peer removed pairing information' },
21
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
22
+ ],
23
+ [
24
+ { message: 'PEER REMOVED PAIRING INFORMATION' },
25
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
26
+ ],
27
+ ])('maps %j to %s', (nativeError, errorCode) => {
28
+ expect(isNativeBleStaleBondError(nativeError)).toBe(true);
29
+ expect(toBleStaleBondHardwareError(nativeError)).toMatchObject({ errorCode });
30
+ });
31
+
32
+ test('does not treat the generic ATT unlikely error as a stale bond', () => {
33
+ expect(isNativeBleStaleBondError({ attErrorCode: 14, reason: 'Unlikely error' })).toBe(false);
34
+ });
35
+
36
+ test('does not treat a generic disconnect as a stale bond', () => {
37
+ expect(isNativeBleStaleBondError({ reason: 'Device disconnected' })).toBe(false);
38
+ });
39
+ });
@@ -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',
@@ -424,6 +445,93 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
424
445
  await transport.release(uuid, true);
425
446
  });
426
447
 
448
+ test('physically refreshes an uncached Protocol V2 firmware install link without Ping', async () => {
449
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
450
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
451
+ (transport as any).sessionProtocols.set(uuid, 'V2');
452
+ device.connect = jest.fn().mockResolvedValue(device);
453
+ device.isConnected.mockResolvedValueOnce(false);
454
+
455
+ await expect(
456
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
457
+ ).resolves.toEqual({
458
+ uuid,
459
+ protocolType: 'V2',
460
+ });
461
+
462
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
463
+ expect(device.cancelConnection).not.toHaveBeenCalled();
464
+ expect(device.connect).toHaveBeenCalled();
465
+ expect(probeProtocolV2).not.toHaveBeenCalled();
466
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
467
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
468
+ expect(transport.getProtocolType(uuid)).toBe('V2');
469
+ await transport.release(uuid, true);
470
+ });
471
+
472
+ test('refreshes a cached firmware install connection instead of trusting GATT state', async () => {
473
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
474
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
475
+
476
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
477
+ device.connect = jest.fn().mockResolvedValue(device);
478
+ device.isConnected.mockResolvedValueOnce(false);
479
+ writeCharacteristic.writeWithResponse.mockClear();
480
+ writeCharacteristic.writeWithoutResponse.mockClear();
481
+
482
+ await expect(
483
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
484
+ ).resolves.toEqual({
485
+ uuid,
486
+ protocolType: 'V2',
487
+ });
488
+
489
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
490
+ expect(device.cancelConnection).toHaveBeenCalled();
491
+ expect(device.connect).toHaveBeenCalled();
492
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
493
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
494
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
495
+ await transport.release(uuid, true);
496
+ });
497
+
498
+ test('rejects no-probe acquire before the BLE endpoint has confirmed a protocol', async () => {
499
+ const { transport, uuid } = createHarness();
500
+
501
+ await expect(
502
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
503
+ ).rejects.toThrow('previously confirmed protocol');
504
+
505
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
506
+ });
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
+
427
535
  test.each(['ios', 'android'] as const)(
428
536
  'keeps a first expected Protocol V2 probe miss retryable on %s',
429
537
  async platform => {
@@ -459,8 +567,37 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
459
567
  }
460
568
  );
461
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
+
462
599
  test.each(['ios', 'android'] as const)(
463
- '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',
464
601
  async platform => {
465
602
  setPlatformOS(platform);
466
603
  const { transport, uuid, device } = createHarness();
@@ -469,10 +606,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
469
606
  jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
470
607
 
471
608
  await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
472
- errorCode: HardwareErrorCode.BleDeviceBondError,
609
+ errorCode: HardwareErrorCode.RuntimeError,
473
610
  });
474
611
 
475
- expect(device.cancelConnection).toHaveBeenCalled();
612
+ expect(device.cancelConnection).not.toHaveBeenCalled();
476
613
  expect(transport.getProtocolType(uuid)).toBeUndefined();
477
614
  }
478
615
  );
@@ -0,0 +1,61 @@
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 normalizedText = text.toLowerCase();
51
+ const peerRemoved =
52
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
53
+ normalizedText.includes('peer removed pairing information');
54
+
55
+ return ERRORS.TypedError(
56
+ peerRemoved
57
+ ? HardwareErrorCode.BlePeerRemovedPairingInformation
58
+ : HardwareErrorCode.BleDeviceBondError,
59
+ text || undefined
60
+ );
61
+ };
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';
@@ -58,6 +62,15 @@ import type { Characteristic, Device, Subscription } from 'react-native-ble-plx'
58
62
  import type EventEmitter from 'events';
59
63
  import type { BleAcquireInput, TransportOptions } from './types';
60
64
 
65
+ type FirmwareInstallBleAcquireInput = BleAcquireInput & {
66
+ /**
67
+ * Reuse the already-verified protocol after an expected firmware-install
68
+ * disconnect. The install loader accepts status requests but may not answer
69
+ * the generic protocol probe used by a normal acquire.
70
+ */
71
+ skipProtocolProbe?: boolean;
72
+ };
73
+
61
74
  const { check, ProtocolV1, parseConfigure } = transport;
62
75
 
63
76
  const Log = bleLogger;
@@ -307,15 +320,10 @@ const resolveNegotiatedMtu = (device: Device) => requestNegotiatedMtu(device, 'c
307
320
 
308
321
  type IOBleErrorRemap = Error | BleError | null | undefined;
309
322
 
310
- function remapError(error: IOBleErrorRemap) {
323
+ function remapError(error: IOBleErrorRemap, mapProtocolV2StaleBond: boolean) {
311
324
  if (error instanceof BleError) {
312
- if (
313
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
314
- // @ts-expect-error
315
- error.iosErrorCode === BleATTErrorCode.UnlikelyError ||
316
- error.reason === 'Peer removed pairing information'
317
- ) {
318
- throw ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
325
+ if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
326
+ throw toBleStaleBondHardwareError(error);
319
327
  }
320
328
 
321
329
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -390,6 +398,16 @@ export default class ReactNativeBleTransport {
390
398
  /** Consecutive detections that failed while trusting sessionProtocols. */
391
399
  private protocolReprobeFailures: Map<string, number> = new Map();
392
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
+
393
411
  private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
394
412
 
395
413
  private protocolV2FrameQueues: Map<string, Uint8Array[]> = new Map();
@@ -865,7 +883,7 @@ export default class ReactNativeBleTransport {
865
883
  return transport;
866
884
  }
867
885
 
868
- async acquire(input: BleAcquireInput) {
886
+ async acquire(input: FirmwareInstallBleAcquireInput) {
869
887
  const { uuid } = input;
870
888
 
871
889
  if (!uuid) {
@@ -875,28 +893,73 @@ export default class ReactNativeBleTransport {
875
893
  return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
876
894
  }
877
895
 
878
- private async acquireUnlocked(input: BleAcquireInput) {
879
- const { uuid, forceCleanRunPromise, expectedProtocol } = input;
896
+ private async acquireUnlocked(input: FirmwareInstallBleAcquireInput) {
897
+ const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
898
+ const shouldMapProtocolV2StaleBond = expectedProtocol
899
+ ? expectedProtocol === 'V2'
900
+ : this.confirmedProtocolV2.has(uuid);
880
901
 
881
902
  const cachedTransport = transportCache[uuid];
903
+ if (skipProtocolProbe && !cachedTransport && this.blePlxManager) {
904
+ Log?.debug(
905
+ '[ReactNativeBleTransport] refresh uncached BLE connection for firmware install:',
906
+ uuid
907
+ );
908
+ const manager = this.blePlxManager;
909
+ await this.runNativeTeardown(uuid, manager, async () => {
910
+ await this.runBestEffortNativeOperation(
911
+ 'firmware install reconnect: cancel uncached device connection',
912
+ () => manager.cancelDeviceConnection(uuid)
913
+ );
914
+ });
915
+ }
882
916
  if (cachedTransport) {
883
- const cachedProtocol = this.deviceProtocol.get(uuid);
884
- const isCachedDeviceConnected = await cachedTransport.device.isConnected().catch(() => false);
885
- if (
886
- isCachedDeviceConnected &&
887
- cachedProtocol &&
888
- (!expectedProtocol || cachedProtocol === expectedProtocol)
889
- ) {
890
- Log?.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
891
- return { uuid, protocolType: cachedProtocol };
892
- }
917
+ if (skipProtocolProbe) {
918
+ Log?.debug(
919
+ '[ReactNativeBleTransport] refresh cached BLE connection for firmware install:',
920
+ uuid
921
+ );
922
+ const manager = this.blePlxManager;
923
+ await this.releaseUnlocked(uuid, true);
924
+ await this.runNativeTeardown(uuid, manager, async () => {
925
+ const operations: Promise<unknown>[] = [];
926
+ if (manager) {
927
+ operations.push(
928
+ this.runBestEffortNativeOperation(
929
+ 'firmware install reconnect: cancel device connection',
930
+ () => manager.cancelDeviceConnection(uuid)
931
+ )
932
+ );
933
+ }
934
+ operations.push(
935
+ this.runBestEffortNativeOperation(
936
+ 'firmware install reconnect: device cancel connection',
937
+ () => cachedTransport.device.cancelConnection()
938
+ )
939
+ );
940
+ await Promise.all(operations);
941
+ });
942
+ } else {
943
+ const cachedProtocol = this.deviceProtocol.get(uuid);
944
+ const isCachedDeviceConnected = await cachedTransport.device
945
+ .isConnected()
946
+ .catch(() => false);
947
+ if (
948
+ isCachedDeviceConnected &&
949
+ cachedProtocol &&
950
+ (!expectedProtocol || cachedProtocol === expectedProtocol)
951
+ ) {
952
+ Log?.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
953
+ return { uuid, protocolType: cachedProtocol };
954
+ }
893
955
 
894
- /**
895
- * If the transport is not reusable due to a protocol mismatch or stale
896
- * connection, clean it up before creating a new transport instance.
897
- */
898
- Log?.debug('transport not reusable, will release: ', uuid);
899
- await this.releaseUnlocked(uuid, true);
956
+ /**
957
+ * If the transport is not reusable due to a protocol mismatch or stale
958
+ * connection, clean it up before creating a new transport instance.
959
+ */
960
+ Log?.debug('transport not reusable, will release: ', uuid);
961
+ await this.releaseUnlocked(uuid, true);
962
+ }
900
963
  }
901
964
 
902
965
  let device: Device | null = null;
@@ -961,7 +1024,7 @@ export default class ReactNativeBleTransport {
961
1024
  Log?.debug('device already connected');
962
1025
  throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
963
1026
  } else {
964
- remapError(e);
1027
+ remapError(e, shouldMapProtocolV2StaleBond);
965
1028
  }
966
1029
  }
967
1030
  }
@@ -1005,7 +1068,7 @@ export default class ReactNativeBleTransport {
1005
1068
  }
1006
1069
  }
1007
1070
  } else {
1008
- remapError(e);
1071
+ remapError(e, shouldMapProtocolV2StaleBond);
1009
1072
  }
1010
1073
  }
1011
1074
  }
@@ -1025,12 +1088,48 @@ export default class ReactNativeBleTransport {
1025
1088
  this.deviceProtocolHints.set(uuid, protocolHint);
1026
1089
  }
1027
1090
 
1028
- await this.installTransportForAcquire(uuid, acquiredDevice, {
1029
- writeCharacteristic,
1030
- notifyCharacteristic,
1031
- });
1091
+ if (shouldMapProtocolV2StaleBond) {
1092
+ this.acquiringProtocolV2.add(uuid);
1093
+ }
1032
1094
 
1033
1095
  try {
1096
+ await this.installTransportForAcquire(uuid, acquiredDevice, {
1097
+ writeCharacteristic,
1098
+ notifyCharacteristic,
1099
+ });
1100
+
1101
+ if (skipProtocolProbe) {
1102
+ if (!expectedProtocol) {
1103
+ throw ERRORS.TypedError(
1104
+ HardwareErrorCode.RuntimeError,
1105
+ 'skipProtocolProbe requires an expected BLE protocol'
1106
+ );
1107
+ }
1108
+ const hasConfirmedProtocol =
1109
+ this.sessionProtocols.get(uuid) === expectedProtocol ||
1110
+ (expectedProtocol === 'V2' && this.confirmedProtocolV2.has(uuid));
1111
+ if (!hasConfirmedProtocol) {
1112
+ throw ERRORS.TypedError(
1113
+ HardwareErrorCode.RuntimeError,
1114
+ 'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint'
1115
+ );
1116
+ }
1117
+ this.deviceProtocol.set(uuid, expectedProtocol);
1118
+ this.sessionProtocols.set(uuid, expectedProtocol);
1119
+ this.protocolReprobeFailures.delete(uuid);
1120
+ Log?.debug('[ReactNativeBleTransport] protocol selected without probe', {
1121
+ deviceId: uuid,
1122
+ protocol: expectedProtocol,
1123
+ source: 'firmware-install-reconnect',
1124
+ });
1125
+ const currentTransport = transportCache[uuid];
1126
+ if (!currentTransport) {
1127
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
1128
+ }
1129
+ this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1130
+ return { uuid, protocolType: expectedProtocol };
1131
+ }
1132
+
1034
1133
  const protocolType = await this.detectProtocol(
1035
1134
  uuid,
1036
1135
  expectedProtocol,
@@ -1046,12 +1145,14 @@ export default class ReactNativeBleTransport {
1046
1145
  this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1047
1146
  return { uuid, protocolType };
1048
1147
  } catch (error) {
1049
- if ((error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleDeviceBondError) {
1148
+ if (isBleStaleBondHardwareError(error)) {
1050
1149
  await this.disconnectUnlocked(uuid);
1051
1150
  } else {
1052
1151
  await this.releaseUnlocked(uuid, true);
1053
1152
  }
1054
1153
  throw error;
1154
+ } finally {
1155
+ this.acquiringProtocolV2.delete(uuid);
1055
1156
  }
1056
1157
  }
1057
1158
 
@@ -1079,17 +1180,21 @@ export default class ReactNativeBleTransport {
1079
1180
  Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
1080
1181
  return;
1081
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
+ }
1082
1190
  if (this.getActiveProtocol(uuid) === 'V2') {
1083
1191
  let errorCode:
1084
- | typeof HardwareErrorCode.BleDeviceBondError
1085
1192
  | typeof HardwareErrorCode.BleCharacteristicNotifyError
1086
1193
  | typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
1087
1194
  | typeof HardwareErrorCode.BleTimeoutError =
1088
1195
  HardwareErrorCode.BleCharacteristicNotifyError;
1089
1196
  if (error.reason?.includes('The connection has timed out unexpectedly')) {
1090
1197
  errorCode = HardwareErrorCode.BleTimeoutError;
1091
- } else if (error.reason?.includes('Encryption is insufficient')) {
1092
- errorCode = HardwareErrorCode.BleDeviceBondError;
1093
1198
  } else if (
1094
1199
  error.reason?.includes('Cannot write client characteristic config descriptor') ||
1095
1200
  error.reason?.includes('Cannot find client characteristic config descriptor') ||
@@ -1104,16 +1209,12 @@ export default class ReactNativeBleTransport {
1104
1209
  }
1105
1210
  if (this.runPromise && this.runPromiseDeviceId === uuid) {
1106
1211
  let ERROR:
1107
- | typeof HardwareErrorCode.BleDeviceBondError
1108
1212
  | typeof HardwareErrorCode.BleCharacteristicNotifyError
1109
1213
  | typeof HardwareErrorCode.BleTimeoutError =
1110
1214
  HardwareErrorCode.BleCharacteristicNotifyError;
1111
1215
  if (error.reason?.includes('The connection has timed out unexpectedly')) {
1112
1216
  ERROR = HardwareErrorCode.BleTimeoutError;
1113
1217
  }
1114
- if (error.reason?.includes('Encryption is insufficient')) {
1115
- ERROR = HardwareErrorCode.BleDeviceBondError;
1116
- }
1117
1218
  if (
1118
1219
  error.reason?.includes('Cannot write client characteristic config descriptor') ||
1119
1220
  error.reason?.includes('Cannot find client characteristic config descriptor') || // pro firmware 2.3.0 upgrade
@@ -1248,6 +1349,8 @@ export default class ReactNativeBleTransport {
1248
1349
 
1249
1350
  this.deviceProtocol.delete(uuid);
1250
1351
  this.probingProtocols.delete(uuid);
1352
+ this.staleBondErrors.delete(uuid);
1353
+ this.acquiringProtocolV2.delete(uuid);
1251
1354
  // Confirmed protocol and caller hints stay in deviceProtocol / protocolHint.
1252
1355
  this.protocolV2Assemblers.get(uuid)?.reset();
1253
1356
  this.protocolV2Assemblers.delete(uuid);
@@ -1620,6 +1723,7 @@ export default class ReactNativeBleTransport {
1620
1723
  }
1621
1724
  this.deviceProtocol.delete(session);
1622
1725
  this.probingProtocols.delete(session);
1726
+ this.staleBondErrors.delete(session);
1623
1727
  this.deviceProtocolHints.delete(session);
1624
1728
  this.sessionProtocols.delete(session);
1625
1729
  this.protocolReprobeFailures.delete(session);
@@ -1843,6 +1947,8 @@ export default class ReactNativeBleTransport {
1843
1947
  }
1844
1948
  this.deviceProtocol.delete(uuid);
1845
1949
  this.probingProtocols.delete(uuid);
1950
+ this.staleBondErrors.delete(uuid);
1951
+ this.acquiringProtocolV2.delete(uuid);
1846
1952
  this.protocolV2Assemblers.delete(uuid);
1847
1953
  this.resetProtocolV2Frames(uuid);
1848
1954
 
@@ -1939,6 +2045,8 @@ export default class ReactNativeBleTransport {
1939
2045
  }
1940
2046
  this.deviceProtocol.delete(uuid);
1941
2047
  this.probingProtocols.delete(uuid);
2048
+ this.staleBondErrors.delete(uuid);
2049
+ this.acquiringProtocolV2.delete(uuid);
1942
2050
  this.protocolV2Assemblers.delete(uuid);
1943
2051
  this.resetProtocolV2Frames(uuid);
1944
2052
 
@@ -1989,8 +2097,11 @@ export default class ReactNativeBleTransport {
1989
2097
  });
1990
2098
  this.deviceProtocol.clear();
1991
2099
  this.probingProtocols.clear();
2100
+ this.staleBondErrors.clear();
2101
+ this.acquiringProtocolV2.clear();
1992
2102
  this.sessionProtocols.clear();
1993
- 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.
1994
2105
  this.protocolReprobeFailures.clear();
1995
2106
  this.writeTimeoutCounts.clear();
1996
2107
  this.connectionSetupTimeoutCounts.clear();
@@ -2003,12 +2114,11 @@ export default class ReactNativeBleTransport {
2003
2114
  }
2004
2115
  }
2005
2116
 
2006
- private createProtocolMismatchError(expected: ProtocolType, uuid: string) {
2007
- // A generic Ping miss is not a bond failure. Only a later miss after this
2008
- // endpoint already answered V2, or a native encryption/pairing error, is.
2009
- 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.
2010
2120
  return ERRORS.TypedError(
2011
- isStaleV2Bond ? HardwareErrorCode.BleDeviceBondError : HardwareErrorCode.RuntimeError,
2121
+ HardwareErrorCode.RuntimeError,
2012
2122
  `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
2013
2123
  );
2014
2124
  }
@@ -2040,31 +2150,28 @@ export default class ReactNativeBleTransport {
2040
2150
  protocolHint?: ProtocolType,
2041
2151
  rebuildTransport?: () => Promise<void>
2042
2152
  ): Promise<ProtocolType> {
2043
- // iOS still skips an extra V1 Initialize during acquire. Expected V2 must
2044
- // Ping so USB-priority `link disabled` can surface instead of a later
2045
- // unmapped RuntimeError.
2046
- if (Platform.OS === 'ios' && expectedProtocol === 'V1') {
2047
- this.deviceProtocol.set(uuid, expectedProtocol);
2153
+ // A declared V1 is taken at face value on every platform, as iOS has done
2154
+ // since protocol probing arrived: the caller reads the protocol off its own
2155
+ // device record, so the probe re-asks a question that is already answered
2156
+ // and costs a round trip on every acquire. Expected V2 must still Ping so
2157
+ // USB-priority `link disabled` surfaces here instead of as a later unmapped
2158
+ // RuntimeError. sessionProtocols is deliberately NOT stamped here: that map
2159
+ // records protocols the device actually answered on (it gates the
2160
+ // trustSessionProtocol narrowing in forced detection), and this branch has
2161
+ // received no response. skipProtocolProbe does not need it either — its only
2162
+ // caller is the V2 firmware-install reconnect.
2163
+ if (expectedProtocol === 'V1') {
2164
+ this.deviceProtocol.set(uuid, 'V1');
2048
2165
  Log?.debug('[ReactNativeBleTransport] protocol selected', {
2049
2166
  deviceId: uuid,
2050
- protocol: expectedProtocol,
2167
+ protocol: 'V1',
2051
2168
  source: 'expected',
2052
2169
  });
2053
- return expectedProtocol;
2170
+ return 'V1';
2054
2171
  }
2055
2172
 
2056
- if (expectedProtocol === 'V1') {
2057
- if (await this.probeProtocolV1(uuid)) {
2058
- this.deviceProtocol.set(uuid, 'V1');
2059
- this.sessionProtocols.set(uuid, 'V1');
2060
- Log?.debug('[ReactNativeBleTransport] protocol detected', {
2061
- deviceId: uuid,
2062
- protocol: 'V1',
2063
- source: 'expected',
2064
- });
2065
- return 'V1';
2066
- }
2067
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
2173
+ if (expectedProtocol === 'V2' || this.acquiringProtocolV2.has(uuid)) {
2174
+ this.throwIfStaleBondError(uuid);
2068
2175
  }
2069
2176
 
2070
2177
  if (expectedProtocol === 'V2') {
@@ -2079,7 +2186,7 @@ export default class ReactNativeBleTransport {
2079
2186
  });
2080
2187
  return 'V2';
2081
2188
  }
2082
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
2189
+ throw this.createProtocolMismatchError(expectedProtocol);
2083
2190
  }
2084
2191
 
2085
2192
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -2223,6 +2330,7 @@ export default class ReactNativeBleTransport {
2223
2330
 
2224
2331
  this.probingProtocols.set(uuid, 'V2');
2225
2332
  this.protocolV2Assemblers.get(uuid)?.reset();
2333
+ this.throwIfStaleBondError(uuid);
2226
2334
  const detected = await probeProtocolV2Helper({
2227
2335
  call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
2228
2336
  this.callProtocolV2(uuid, name, data, options),
@@ -2233,6 +2341,7 @@ export default class ReactNativeBleTransport {
2233
2341
  this.protocolV2Assemblers.get(uuid)?.reset();
2234
2342
  this.resetProtocolV2Frames(uuid);
2235
2343
  },
2344
+ shouldRethrow: isBleStaleBondHardwareError,
2236
2345
  });
2237
2346
  if (!detected) {
2238
2347
  this.clearProbeProtocol(uuid, 'V2');
@@ -2301,6 +2410,21 @@ export default class ReactNativeBleTransport {
2301
2410
  }
2302
2411
  }
2303
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
+
2304
2428
  private async readProtocolV2Frame(uuid: string) {
2305
2429
  const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
2306
2430
  if (queuedFrame) {
@@ -2359,6 +2483,11 @@ export default class ReactNativeBleTransport {
2359
2483
  assertCurrentGeneration();
2360
2484
  return;
2361
2485
  } catch (error) {
2486
+ if (isNativeBleStaleBondError(error) || isBleStaleBondHardwareError(error)) {
2487
+ const bondError = toBleStaleBondHardwareError(error);
2488
+ this.rememberStaleBondError(uuid, bondError);
2489
+ throw bondError;
2490
+ }
2362
2491
  if (
2363
2492
  getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2364
2493
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES