@onekeyfe/hd-transport-react-native 1.2.2-alpha.100 → 1.2.2-alpha.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bleStaleBond.d.ts +5 -0
- package/dist/bleStaleBond.d.ts.map +1 -0
- package/dist/bleStrategy.d.ts +1 -1
- package/dist/bleStrategy.d.ts.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -73
- package/package.json +5 -5
- package/src/__tests__/bleStaleBond.test.ts +47 -0
- package/src/__tests__/bleStrategy.test.ts +2 -3
- package/src/__tests__/protocolV2Link.test.ts +286 -16
- package/src/bleStaleBond.ts +64 -0
- package/src/bleStrategy.ts +1 -3
- package/src/index.ts +231 -77
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;
|
|
@@ -309,13 +322,8 @@ type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
|
309
322
|
|
|
310
323
|
function remapError(error: IOBleErrorRemap) {
|
|
311
324
|
if (error instanceof BleError) {
|
|
312
|
-
if (
|
|
313
|
-
|
|
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 (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:
|
|
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:
|
|
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
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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;
|
|
@@ -917,15 +980,6 @@ export default class ReactNativeBleTransport {
|
|
|
917
980
|
throw error;
|
|
918
981
|
}
|
|
919
982
|
|
|
920
|
-
if (Platform.OS === 'android') {
|
|
921
|
-
const bondState = await pairDevice(uuid);
|
|
922
|
-
if (bondState.bonding) {
|
|
923
|
-
await onDeviceBondState(uuid);
|
|
924
|
-
} else if (!bondState.bonded) {
|
|
925
|
-
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
983
|
if (!device) {
|
|
930
984
|
const devices = await blePlxManager.devices([uuid]);
|
|
931
985
|
[device] = devices;
|
|
@@ -992,16 +1046,18 @@ export default class ReactNativeBleTransport {
|
|
|
992
1046
|
device = await this.connectWithTimeout(uuid, () =>
|
|
993
1047
|
disconnectedDevice.connect(fallbackConnectOptions)
|
|
994
1048
|
);
|
|
995
|
-
} catch (
|
|
996
|
-
Log?.debug('last try to reconnect error: ',
|
|
1049
|
+
} catch (fallbackError) {
|
|
1050
|
+
Log?.debug('last try to reconnect error: ', fallbackError);
|
|
997
1051
|
// last try to reconnect device if this issue exists
|
|
998
1052
|
// https://github.com/dotintent/react-native-ble-plx/issues/426
|
|
999
|
-
if (
|
|
1053
|
+
if (fallbackError.errorCode === BleErrorCode.OperationCancelled) {
|
|
1000
1054
|
Log?.debug('last try to reconnect');
|
|
1001
1055
|
await disconnectedDevice.cancelConnection();
|
|
1002
1056
|
device = await this.connectWithTimeout(uuid, () =>
|
|
1003
1057
|
disconnectedDevice.connect(fallbackConnectOptions)
|
|
1004
1058
|
);
|
|
1059
|
+
} else {
|
|
1060
|
+
remapError(fallbackError);
|
|
1005
1061
|
}
|
|
1006
1062
|
}
|
|
1007
1063
|
} else {
|
|
@@ -1010,6 +1066,38 @@ export default class ReactNativeBleTransport {
|
|
|
1010
1066
|
}
|
|
1011
1067
|
}
|
|
1012
1068
|
|
|
1069
|
+
if (Platform.OS === 'android') {
|
|
1070
|
+
// Establish the LE link before createBond(). Without an existing LE ACL,
|
|
1071
|
+
// Android TRANSPORT_AUTO can choose BR/EDR for a BLE-only device.
|
|
1072
|
+
const connectedDevice = device;
|
|
1073
|
+
try {
|
|
1074
|
+
if (!(await connectedDevice.isConnected().catch(() => false))) {
|
|
1075
|
+
throw ERRORS.TypedError(
|
|
1076
|
+
HardwareErrorCode.BleConnectedError,
|
|
1077
|
+
`Device ${uuid} is not connected before bonding`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
const bondState = await pairDevice(uuid);
|
|
1081
|
+
if (bondState.bonding) {
|
|
1082
|
+
await onDeviceBondState(uuid);
|
|
1083
|
+
} else if (!bondState.bonded) {
|
|
1084
|
+
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
|
|
1085
|
+
}
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
await this.runNativeTeardown(uuid, blePlxManager, async () => {
|
|
1088
|
+
await Promise.all([
|
|
1089
|
+
this.runBestEffortNativeOperation('bond failure: cancel manager connection', () =>
|
|
1090
|
+
blePlxManager.cancelDeviceConnection(uuid)
|
|
1091
|
+
),
|
|
1092
|
+
this.runBestEffortNativeOperation('bond failure: cancel device connection', () =>
|
|
1093
|
+
connectedDevice.cancelConnection()
|
|
1094
|
+
),
|
|
1095
|
+
]);
|
|
1096
|
+
});
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1013
1101
|
device = await resolveNegotiatedMtu(device);
|
|
1014
1102
|
const acquiredDevice = device;
|
|
1015
1103
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
@@ -1025,12 +1113,48 @@ export default class ReactNativeBleTransport {
|
|
|
1025
1113
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
1026
1114
|
}
|
|
1027
1115
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
});
|
|
1116
|
+
if (shouldMapProtocolV2StaleBond) {
|
|
1117
|
+
this.acquiringProtocolV2.add(uuid);
|
|
1118
|
+
}
|
|
1032
1119
|
|
|
1033
1120
|
try {
|
|
1121
|
+
await this.installTransportForAcquire(uuid, acquiredDevice, {
|
|
1122
|
+
writeCharacteristic,
|
|
1123
|
+
notifyCharacteristic,
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
if (skipProtocolProbe) {
|
|
1127
|
+
if (!expectedProtocol) {
|
|
1128
|
+
throw ERRORS.TypedError(
|
|
1129
|
+
HardwareErrorCode.RuntimeError,
|
|
1130
|
+
'skipProtocolProbe requires an expected BLE protocol'
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const hasConfirmedProtocol =
|
|
1134
|
+
this.sessionProtocols.get(uuid) === expectedProtocol ||
|
|
1135
|
+
(expectedProtocol === 'V2' && this.confirmedProtocolV2.has(uuid));
|
|
1136
|
+
if (!hasConfirmedProtocol) {
|
|
1137
|
+
throw ERRORS.TypedError(
|
|
1138
|
+
HardwareErrorCode.RuntimeError,
|
|
1139
|
+
'skipProtocolProbe requires a previously confirmed protocol for this BLE endpoint'
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
this.deviceProtocol.set(uuid, expectedProtocol);
|
|
1143
|
+
this.sessionProtocols.set(uuid, expectedProtocol);
|
|
1144
|
+
this.protocolReprobeFailures.delete(uuid);
|
|
1145
|
+
Log?.debug('[ReactNativeBleTransport] protocol selected without probe', {
|
|
1146
|
+
deviceId: uuid,
|
|
1147
|
+
protocol: expectedProtocol,
|
|
1148
|
+
source: 'firmware-install-reconnect',
|
|
1149
|
+
});
|
|
1150
|
+
const currentTransport = transportCache[uuid];
|
|
1151
|
+
if (!currentTransport) {
|
|
1152
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
|
|
1153
|
+
}
|
|
1154
|
+
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1155
|
+
return { uuid, protocolType: expectedProtocol };
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1034
1158
|
const protocolType = await this.detectProtocol(
|
|
1035
1159
|
uuid,
|
|
1036
1160
|
expectedProtocol,
|
|
@@ -1046,12 +1170,14 @@ export default class ReactNativeBleTransport {
|
|
|
1046
1170
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1047
1171
|
return { uuid, protocolType };
|
|
1048
1172
|
} catch (error) {
|
|
1049
|
-
if ((error
|
|
1173
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1050
1174
|
await this.disconnectUnlocked(uuid);
|
|
1051
1175
|
} else {
|
|
1052
1176
|
await this.releaseUnlocked(uuid, true);
|
|
1053
1177
|
}
|
|
1054
1178
|
throw error;
|
|
1179
|
+
} finally {
|
|
1180
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1055
1181
|
}
|
|
1056
1182
|
}
|
|
1057
1183
|
|
|
@@ -1079,17 +1205,21 @@ export default class ReactNativeBleTransport {
|
|
|
1079
1205
|
Log?.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
|
|
1080
1206
|
return;
|
|
1081
1207
|
}
|
|
1208
|
+
if (
|
|
1209
|
+
(this.getActiveProtocol(uuid) === 'V2' || this.acquiringProtocolV2.has(uuid)) &&
|
|
1210
|
+
isNativeBleStaleBondError(error)
|
|
1211
|
+
) {
|
|
1212
|
+
this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1082
1215
|
if (this.getActiveProtocol(uuid) === 'V2') {
|
|
1083
1216
|
let errorCode:
|
|
1084
|
-
| typeof HardwareErrorCode.BleDeviceBondError
|
|
1085
1217
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
1086
1218
|
| typeof HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
1087
1219
|
| typeof HardwareErrorCode.BleTimeoutError =
|
|
1088
1220
|
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1089
1221
|
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
1090
1222
|
errorCode = HardwareErrorCode.BleTimeoutError;
|
|
1091
|
-
} else if (error.reason?.includes('Encryption is insufficient')) {
|
|
1092
|
-
errorCode = HardwareErrorCode.BleDeviceBondError;
|
|
1093
1223
|
} else if (
|
|
1094
1224
|
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
1095
1225
|
error.reason?.includes('Cannot find client characteristic config descriptor') ||
|
|
@@ -1104,16 +1234,12 @@ export default class ReactNativeBleTransport {
|
|
|
1104
1234
|
}
|
|
1105
1235
|
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1106
1236
|
let ERROR:
|
|
1107
|
-
| typeof HardwareErrorCode.BleDeviceBondError
|
|
1108
1237
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
1109
1238
|
| typeof HardwareErrorCode.BleTimeoutError =
|
|
1110
1239
|
HardwareErrorCode.BleCharacteristicNotifyError;
|
|
1111
1240
|
if (error.reason?.includes('The connection has timed out unexpectedly')) {
|
|
1112
1241
|
ERROR = HardwareErrorCode.BleTimeoutError;
|
|
1113
1242
|
}
|
|
1114
|
-
if (error.reason?.includes('Encryption is insufficient')) {
|
|
1115
|
-
ERROR = HardwareErrorCode.BleDeviceBondError;
|
|
1116
|
-
}
|
|
1117
1243
|
if (
|
|
1118
1244
|
error.reason?.includes('Cannot write client characteristic config descriptor') ||
|
|
1119
1245
|
error.reason?.includes('Cannot find client characteristic config descriptor') || // pro firmware 2.3.0 upgrade
|
|
@@ -1248,6 +1374,8 @@ export default class ReactNativeBleTransport {
|
|
|
1248
1374
|
|
|
1249
1375
|
this.deviceProtocol.delete(uuid);
|
|
1250
1376
|
this.probingProtocols.delete(uuid);
|
|
1377
|
+
this.staleBondErrors.delete(uuid);
|
|
1378
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1251
1379
|
// Confirmed protocol and caller hints stay in deviceProtocol / protocolHint.
|
|
1252
1380
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
1253
1381
|
this.protocolV2Assemblers.delete(uuid);
|
|
@@ -1620,6 +1748,7 @@ export default class ReactNativeBleTransport {
|
|
|
1620
1748
|
}
|
|
1621
1749
|
this.deviceProtocol.delete(session);
|
|
1622
1750
|
this.probingProtocols.delete(session);
|
|
1751
|
+
this.staleBondErrors.delete(session);
|
|
1623
1752
|
this.deviceProtocolHints.delete(session);
|
|
1624
1753
|
this.sessionProtocols.delete(session);
|
|
1625
1754
|
this.protocolReprobeFailures.delete(session);
|
|
@@ -1843,6 +1972,8 @@ export default class ReactNativeBleTransport {
|
|
|
1843
1972
|
}
|
|
1844
1973
|
this.deviceProtocol.delete(uuid);
|
|
1845
1974
|
this.probingProtocols.delete(uuid);
|
|
1975
|
+
this.staleBondErrors.delete(uuid);
|
|
1976
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1846
1977
|
this.protocolV2Assemblers.delete(uuid);
|
|
1847
1978
|
this.resetProtocolV2Frames(uuid);
|
|
1848
1979
|
|
|
@@ -1939,6 +2070,8 @@ export default class ReactNativeBleTransport {
|
|
|
1939
2070
|
}
|
|
1940
2071
|
this.deviceProtocol.delete(uuid);
|
|
1941
2072
|
this.probingProtocols.delete(uuid);
|
|
2073
|
+
this.staleBondErrors.delete(uuid);
|
|
2074
|
+
this.acquiringProtocolV2.delete(uuid);
|
|
1942
2075
|
this.protocolV2Assemblers.delete(uuid);
|
|
1943
2076
|
this.resetProtocolV2Frames(uuid);
|
|
1944
2077
|
|
|
@@ -1989,8 +2122,11 @@ export default class ReactNativeBleTransport {
|
|
|
1989
2122
|
});
|
|
1990
2123
|
this.deviceProtocol.clear();
|
|
1991
2124
|
this.probingProtocols.clear();
|
|
2125
|
+
this.staleBondErrors.clear();
|
|
2126
|
+
this.acquiringProtocolV2.clear();
|
|
1992
2127
|
this.sessionProtocols.clear();
|
|
1993
|
-
|
|
2128
|
+
// Keep transport-lifetime V2 proof so the same endpoint can finish a no-probe
|
|
2129
|
+
// firmware reconnect after the native BLE manager is recreated.
|
|
1994
2130
|
this.protocolReprobeFailures.clear();
|
|
1995
2131
|
this.writeTimeoutCounts.clear();
|
|
1996
2132
|
this.connectionSetupTimeoutCounts.clear();
|
|
@@ -2003,12 +2139,11 @@ export default class ReactNativeBleTransport {
|
|
|
2003
2139
|
}
|
|
2004
2140
|
}
|
|
2005
2141
|
|
|
2006
|
-
private createProtocolMismatchError(expected: ProtocolType
|
|
2007
|
-
// A
|
|
2008
|
-
//
|
|
2009
|
-
const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
|
|
2142
|
+
private createProtocolMismatchError(expected: ProtocolType) {
|
|
2143
|
+
// A protocol probe miss alone does not prove that the OS bond is stale.
|
|
2144
|
+
// Native authentication/encryption failures are mapped separately.
|
|
2010
2145
|
return ERRORS.TypedError(
|
|
2011
|
-
|
|
2146
|
+
HardwareErrorCode.RuntimeError,
|
|
2012
2147
|
`Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
|
|
2013
2148
|
);
|
|
2014
2149
|
}
|
|
@@ -2040,31 +2175,28 @@ export default class ReactNativeBleTransport {
|
|
|
2040
2175
|
protocolHint?: ProtocolType,
|
|
2041
2176
|
rebuildTransport?: () => Promise<void>
|
|
2042
2177
|
): Promise<ProtocolType> {
|
|
2043
|
-
//
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
|
|
2047
|
-
|
|
2178
|
+
// A declared V1 is taken at face value on every platform, as iOS has done
|
|
2179
|
+
// since protocol probing arrived: the caller reads the protocol off its own
|
|
2180
|
+
// device record, so the probe re-asks a question that is already answered
|
|
2181
|
+
// and costs a round trip on every acquire. Expected V2 must still Ping so
|
|
2182
|
+
// USB-priority `link disabled` surfaces here instead of as a later unmapped
|
|
2183
|
+
// RuntimeError. sessionProtocols is deliberately NOT stamped here: that map
|
|
2184
|
+
// records protocols the device actually answered on (it gates the
|
|
2185
|
+
// trustSessionProtocol narrowing in forced detection), and this branch has
|
|
2186
|
+
// received no response. skipProtocolProbe does not need it either — its only
|
|
2187
|
+
// caller is the V2 firmware-install reconnect.
|
|
2188
|
+
if (expectedProtocol === 'V1') {
|
|
2189
|
+
this.deviceProtocol.set(uuid, 'V1');
|
|
2048
2190
|
Log?.debug('[ReactNativeBleTransport] protocol selected', {
|
|
2049
2191
|
deviceId: uuid,
|
|
2050
|
-
protocol:
|
|
2192
|
+
protocol: 'V1',
|
|
2051
2193
|
source: 'expected',
|
|
2052
2194
|
});
|
|
2053
|
-
return
|
|
2195
|
+
return 'V1';
|
|
2054
2196
|
}
|
|
2055
2197
|
|
|
2056
|
-
if (expectedProtocol === '
|
|
2057
|
-
|
|
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);
|
|
2198
|
+
if (expectedProtocol === 'V2' || this.acquiringProtocolV2.has(uuid)) {
|
|
2199
|
+
this.throwIfStaleBondError(uuid);
|
|
2068
2200
|
}
|
|
2069
2201
|
|
|
2070
2202
|
if (expectedProtocol === 'V2') {
|
|
@@ -2079,7 +2211,7 @@ export default class ReactNativeBleTransport {
|
|
|
2079
2211
|
});
|
|
2080
2212
|
return 'V2';
|
|
2081
2213
|
}
|
|
2082
|
-
throw this.createProtocolMismatchError(expectedProtocol
|
|
2214
|
+
throw this.createProtocolMismatchError(expectedProtocol);
|
|
2083
2215
|
}
|
|
2084
2216
|
|
|
2085
2217
|
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
@@ -2223,6 +2355,7 @@ export default class ReactNativeBleTransport {
|
|
|
2223
2355
|
|
|
2224
2356
|
this.probingProtocols.set(uuid, 'V2');
|
|
2225
2357
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2358
|
+
this.throwIfStaleBondError(uuid);
|
|
2226
2359
|
const detected = await probeProtocolV2Helper({
|
|
2227
2360
|
call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
|
|
2228
2361
|
this.callProtocolV2(uuid, name, data, options),
|
|
@@ -2233,6 +2366,7 @@ export default class ReactNativeBleTransport {
|
|
|
2233
2366
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2234
2367
|
this.resetProtocolV2Frames(uuid);
|
|
2235
2368
|
},
|
|
2369
|
+
shouldRethrow: isBleStaleBondHardwareError,
|
|
2236
2370
|
});
|
|
2237
2371
|
if (!detected) {
|
|
2238
2372
|
this.clearProbeProtocol(uuid, 'V2');
|
|
@@ -2301,6 +2435,21 @@ export default class ReactNativeBleTransport {
|
|
|
2301
2435
|
}
|
|
2302
2436
|
}
|
|
2303
2437
|
|
|
2438
|
+
private rememberStaleBondError(uuid: string, error: Error) {
|
|
2439
|
+
this.staleBondErrors.set(uuid, error);
|
|
2440
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
2441
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
2442
|
+
this.runPromise.reject(error);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
private throwIfStaleBondError(uuid: string) {
|
|
2447
|
+
const error = this.staleBondErrors.get(uuid);
|
|
2448
|
+
if (error) {
|
|
2449
|
+
throw error;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2304
2453
|
private async readProtocolV2Frame(uuid: string) {
|
|
2305
2454
|
const queuedFrame = this.getProtocolV2FrameQueue(uuid).shift();
|
|
2306
2455
|
if (queuedFrame) {
|
|
@@ -2359,6 +2508,11 @@ export default class ReactNativeBleTransport {
|
|
|
2359
2508
|
assertCurrentGeneration();
|
|
2360
2509
|
return;
|
|
2361
2510
|
} catch (error) {
|
|
2511
|
+
if (isNativeBleStaleBondError(error) || isBleStaleBondHardwareError(error)) {
|
|
2512
|
+
const bondError = toBleStaleBondHardwareError(error);
|
|
2513
|
+
this.rememberStaleBondError(uuid, bondError);
|
|
2514
|
+
throw bondError;
|
|
2515
|
+
}
|
|
2362
2516
|
if (
|
|
2363
2517
|
getFirmwareUploadWriteRetryType(error) !== 'congested' ||
|
|
2364
2518
|
attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES
|