@onekeyfe/hd-transport-react-native 1.2.2-alpha.113 → 1.2.2-alpha.115
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/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -8
- package/package.json +5 -5
- package/src/__tests__/protocolV2Link.test.ts +69 -5
- package/src/index.ts +149 -12
package/dist/index.d.ts
CHANGED
|
@@ -135,6 +135,15 @@ declare class ReactNativeBleTransport {
|
|
|
135
135
|
private confirmedProtocolV2;
|
|
136
136
|
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
137
137
|
private protocolReprobeFailures;
|
|
138
|
+
/** Endpoints whose last detection got no answer on any protocol. */
|
|
139
|
+
private silentDetections;
|
|
140
|
+
/**
|
|
141
|
+
* Endpoints already sent a Protocol V1 wake. The wake starts a fresh wallet session
|
|
142
|
+
* on the device, and a failed detection tears the link down and reconnects on every
|
|
143
|
+
* poll, so neither set is cleared on teardown: one wake is spent per endpoint until a
|
|
144
|
+
* detection succeeds, which re-arms it for the next time the device sleeps.
|
|
145
|
+
*/
|
|
146
|
+
private protocolWakeAttempts;
|
|
138
147
|
/**
|
|
139
148
|
* Native encryption/pairing failures seen before Protocol V2 probe starts.
|
|
140
149
|
* Pro2/Neo GATT connect can succeed on a stale iOS bond; the CCCD write then
|
|
@@ -228,6 +237,23 @@ declare class ReactNativeBleTransport {
|
|
|
228
237
|
/** Protocol to route a call with: confirmed if known, otherwise the one being probed. */
|
|
229
238
|
private getActiveProtocol;
|
|
230
239
|
private detectProtocol;
|
|
240
|
+
/**
|
|
241
|
+
* Sent once before probing when the previous detection on this endpoint got no answer
|
|
242
|
+
* at all. On Classic-family firmware that silence is also what a sleeping device looks
|
|
243
|
+
* like: its BLE co-processor keeps serving connect, GATT and MTU while the main MCU
|
|
244
|
+
* sits in the screensaver loop, whose host-message filter accepts only Initialize and
|
|
245
|
+
* the *Ack messages. GetFeatures and Ping are dropped there without a reply, so no
|
|
246
|
+
* amount of probing can bring the device back.
|
|
247
|
+
*
|
|
248
|
+
* Initialize is the one message that breaks that loop, but it also starts a fresh
|
|
249
|
+
* wallet session, which is why it is not the probe itself — a bare Initialize on every
|
|
250
|
+
* acquire would drop a hidden-wallet session before Core can restore it. Requiring a
|
|
251
|
+
* fully silent previous detection keeps it to devices with no session left to protect.
|
|
252
|
+
*
|
|
253
|
+
* The firmware consumes the wake to leave its loop and does not reliably answer it, so
|
|
254
|
+
* the result is ignored: the probes that follow are what decide the protocol.
|
|
255
|
+
*/
|
|
256
|
+
private wakeSilentProtocolV1Device;
|
|
231
257
|
private resetProbeStateAfterProtocolProbe;
|
|
232
258
|
private probeProtocolV1;
|
|
233
259
|
private probeProtocolV2;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAGL,UAAU,IAAI,aAAa,EAG5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAChB,KAAK,oBAAoB,EAIzB,KAAK,YAAY,EAKjB,KAAK,oBAAoB,EAI1B,MAAM,wBAAwB,CAAC;AAgChC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAI1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,KAAK,8BAA8B,GAAG,eAAe,GAAG;IAMtD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAkBF,KAAK,4BAA4B,GAAG,WAAW,CAAC;AAChD,KAAK,0BAA0B,GAAG;IAChC,mBAAmB,EAAE,cAAc,CAAC;IACpC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AAkCF,eAAO,MAAM,+BAA+B,UACnC,OAAO,KACb,4BAA4B,GAAG,IAsBjC,CAAC;AAcF,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,8BAA8B,OAAQ,CAAC;AAwBpD,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAI3D,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAiBF,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,mBAAwB,QAY5E;AAED,wBAAgB,wBAAwB,SAGvC;AAED,wBAAgB,sBAAsB;;;EAErC;AAkCD,eAAO,MAAM,sBAAsB,QAA2C,CAAC;AAO/E,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAQhD,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAEpD,eAAO,MAAM,2CAA2C,IAAI,CAAC;AAE7D,eAAO,MAAM,wBAAwB,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -446,6 +446,10 @@ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? v
|
|
|
446
446
|
error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
|
|
447
447
|
const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
|
|
448
448
|
const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
|
|
449
|
+
const isAbandonedNativeOperationError = (error) => {
|
|
450
|
+
const errorCode = error === null || error === void 0 ? void 0 : error.errorCode;
|
|
451
|
+
return (errorCode === reactNativeBlePlx.BleErrorCode.OperationTimedOut || errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled);
|
|
452
|
+
};
|
|
449
453
|
const tryToGetConfiguration = (device) => {
|
|
450
454
|
if (!device || !device.serviceUUIDs)
|
|
451
455
|
return null;
|
|
@@ -459,12 +463,13 @@ const tryToGetConfiguration = (device) => {
|
|
|
459
463
|
};
|
|
460
464
|
const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
461
465
|
if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
|
|
462
|
-
return device;
|
|
466
|
+
return { device, abandoned: false };
|
|
463
467
|
const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
|
|
464
468
|
let timeoutId;
|
|
465
469
|
let timedOut = false;
|
|
466
470
|
try {
|
|
467
471
|
const request = device.requestMTU(getRequestedBleMtu(), transactionId);
|
|
472
|
+
request.catch(() => undefined);
|
|
468
473
|
const mtuDevice = yield Promise.race([
|
|
469
474
|
request,
|
|
470
475
|
new Promise((_, reject) => {
|
|
@@ -474,7 +479,7 @@ const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __aw
|
|
|
474
479
|
}, BLE_MTU_REQUEST_TIMEOUT_MS);
|
|
475
480
|
}),
|
|
476
481
|
]);
|
|
477
|
-
return mtuDevice;
|
|
482
|
+
return { device: mtuDevice, abandoned: false };
|
|
478
483
|
}
|
|
479
484
|
catch (error) {
|
|
480
485
|
if (timedOut && cancelTransaction) {
|
|
@@ -504,7 +509,7 @@ const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __aw
|
|
|
504
509
|
actual: device.mtu,
|
|
505
510
|
error: error instanceof Error ? error.message : String(error),
|
|
506
511
|
});
|
|
507
|
-
return device;
|
|
512
|
+
return { device, abandoned: timedOut };
|
|
508
513
|
}
|
|
509
514
|
finally {
|
|
510
515
|
if (timeoutId)
|
|
@@ -513,7 +518,7 @@ const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __aw
|
|
|
513
518
|
});
|
|
514
519
|
const resolveNegotiatedMtu = (device, cancelTransaction) => shouldRefreshNegotiatedMtu(device.mtu)
|
|
515
520
|
? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
|
|
516
|
-
: Promise.resolve(device);
|
|
521
|
+
: Promise.resolve({ device, abandoned: false });
|
|
517
522
|
function remapError(error) {
|
|
518
523
|
var _a;
|
|
519
524
|
if (error instanceof reactNativeBlePlx.BleError) {
|
|
@@ -550,6 +555,8 @@ class ReactNativeBleTransport {
|
|
|
550
555
|
this.sessionProtocols = new Map();
|
|
551
556
|
this.confirmedProtocolV2 = new Set();
|
|
552
557
|
this.protocolReprobeFailures = new Map();
|
|
558
|
+
this.silentDetections = new Set();
|
|
559
|
+
this.protocolWakeAttempts = new Set();
|
|
553
560
|
this.staleBondErrors = new Map();
|
|
554
561
|
this.acquiringProtocolV2 = new Set();
|
|
555
562
|
this.protocolV2Assemblers = new Map();
|
|
@@ -1036,6 +1043,7 @@ class ReactNativeBleTransport {
|
|
|
1036
1043
|
}
|
|
1037
1044
|
}
|
|
1038
1045
|
let device = null;
|
|
1046
|
+
let mtuHandshakeRefused = false;
|
|
1039
1047
|
if (forceCleanRunPromise && this.runPromise) {
|
|
1040
1048
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1041
1049
|
this.runPromise.reject(error);
|
|
@@ -1095,6 +1103,8 @@ class ReactNativeBleTransport {
|
|
|
1095
1103
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
1096
1104
|
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
1097
1105
|
Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
|
|
1106
|
+
mtuHandshakeRefused = true;
|
|
1107
|
+
yield this.runBestEffortNativeOperation('connect cancelled: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
|
|
1098
1108
|
device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
|
|
1099
1109
|
}
|
|
1100
1110
|
else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
|
|
@@ -1123,6 +1133,8 @@ class ReactNativeBleTransport {
|
|
|
1123
1133
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
1124
1134
|
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
1125
1135
|
Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
|
|
1136
|
+
mtuHandshakeRefused = true;
|
|
1137
|
+
yield this.runBestEffortNativeOperation('connect cancelled: cancel device connection', () => disconnectedDevice.cancelConnection());
|
|
1126
1138
|
try {
|
|
1127
1139
|
device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
|
|
1128
1140
|
}
|
|
@@ -1157,7 +1169,22 @@ class ReactNativeBleTransport {
|
|
|
1157
1169
|
}
|
|
1158
1170
|
if (this.stopped)
|
|
1159
1171
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
1160
|
-
|
|
1172
|
+
if (mtuHandshakeRefused) {
|
|
1173
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] skipping MTU refresh after cancelled MTU connect', {
|
|
1174
|
+
connectIdSuffix: uuid.slice(-8),
|
|
1175
|
+
actual: device.mtu,
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
else {
|
|
1179
|
+
const negotiatedMtu = yield resolveNegotiatedMtu(device, transactionId => blePlxManager.cancelTransaction(transactionId));
|
|
1180
|
+
device = negotiatedMtu.device;
|
|
1181
|
+
if (negotiatedMtu.abandoned) {
|
|
1182
|
+
yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
|
|
1183
|
+
yield this.runBestEffortNativeOperation('mtu abandoned: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
|
|
1184
|
+
}));
|
|
1185
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'BLE MTU negotiation abandoned, reconnecting');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1161
1188
|
if (this.stopped)
|
|
1162
1189
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
|
|
1163
1190
|
const acquiredDevice = device;
|
|
@@ -1872,7 +1899,7 @@ class ReactNativeBleTransport {
|
|
|
1872
1899
|
return result;
|
|
1873
1900
|
}
|
|
1874
1901
|
catch (error) {
|
|
1875
|
-
if (timedOut ||
|
|
1902
|
+
if (timedOut || isAbandonedNativeOperationError(error)) {
|
|
1876
1903
|
const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1877
1904
|
if (resetManager) {
|
|
1878
1905
|
throw this.createWedgedBleSetupError();
|
|
@@ -2029,6 +2056,8 @@ class ReactNativeBleTransport {
|
|
|
2029
2056
|
this.acquiringProtocolV2.clear();
|
|
2030
2057
|
this.sessionProtocols.clear();
|
|
2031
2058
|
this.protocolReprobeFailures.clear();
|
|
2059
|
+
this.silentDetections.clear();
|
|
2060
|
+
this.protocolWakeAttempts.clear();
|
|
2032
2061
|
this.writeTimeoutCounts.clear();
|
|
2033
2062
|
this.connectionSetupTimeoutCounts.clear();
|
|
2034
2063
|
this.monitorTokens.clear();
|
|
@@ -2102,6 +2131,7 @@ class ReactNativeBleTransport {
|
|
|
2102
2131
|
!protocolHint &&
|
|
2103
2132
|
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
2104
2133
|
const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
2134
|
+
yield this.wakeSilentProtocolV1Device(uuid, probeOrder);
|
|
2105
2135
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
2106
2136
|
const protocol = probeOrder[i];
|
|
2107
2137
|
if (i > 0) {
|
|
@@ -2121,6 +2151,8 @@ class ReactNativeBleTransport {
|
|
|
2121
2151
|
this.confirmedProtocolV2.add(uuid);
|
|
2122
2152
|
}
|
|
2123
2153
|
this.protocolReprobeFailures.delete(uuid);
|
|
2154
|
+
this.silentDetections.delete(uuid);
|
|
2155
|
+
this.protocolWakeAttempts.delete(uuid);
|
|
2124
2156
|
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
|
|
2125
2157
|
deviceId: uuid,
|
|
2126
2158
|
protocol,
|
|
@@ -2129,6 +2161,7 @@ class ReactNativeBleTransport {
|
|
|
2129
2161
|
return protocol;
|
|
2130
2162
|
}
|
|
2131
2163
|
}
|
|
2164
|
+
this.silentDetections.add(uuid);
|
|
2132
2165
|
if (trustSessionProtocol) {
|
|
2133
2166
|
this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
|
|
2134
2167
|
}
|
|
@@ -2140,6 +2173,38 @@ class ReactNativeBleTransport {
|
|
|
2140
2173
|
throw this.createProtocolDetectionError();
|
|
2141
2174
|
});
|
|
2142
2175
|
}
|
|
2176
|
+
wakeSilentProtocolV1Device(uuid, probeOrder) {
|
|
2177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2178
|
+
if (!this._messages)
|
|
2179
|
+
return;
|
|
2180
|
+
if (!probeOrder.includes('V1'))
|
|
2181
|
+
return;
|
|
2182
|
+
if (!this.silentDetections.has(uuid))
|
|
2183
|
+
return;
|
|
2184
|
+
if (this.protocolWakeAttempts.has(uuid))
|
|
2185
|
+
return;
|
|
2186
|
+
if (!transportCache[uuid])
|
|
2187
|
+
return;
|
|
2188
|
+
this.protocolWakeAttempts.add(uuid);
|
|
2189
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] sending Protocol V1 Initialize wake', {
|
|
2190
|
+
connectIdSuffix: uuid.slice(-8),
|
|
2191
|
+
});
|
|
2192
|
+
try {
|
|
2193
|
+
this.probingProtocols.set(uuid, 'V1');
|
|
2194
|
+
yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
2195
|
+
}
|
|
2196
|
+
catch (error) {
|
|
2197
|
+
if (shouldRethrowProtocolProbeError(error)) {
|
|
2198
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
2199
|
+
throw error;
|
|
2200
|
+
}
|
|
2201
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 Initialize wake did not answer:', error);
|
|
2202
|
+
}
|
|
2203
|
+
finally {
|
|
2204
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2143
2208
|
resetProbeStateAfterProtocolProbe(uuid, protocol) {
|
|
2144
2209
|
var _a, _b, _c;
|
|
2145
2210
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -2457,11 +2522,11 @@ class ReactNativeBleTransport {
|
|
|
2457
2522
|
const transport = this.getCachedTransport(uuid);
|
|
2458
2523
|
if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
|
|
2459
2524
|
return;
|
|
2460
|
-
const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1, transactionId => { var _a; return (_a = this.blePlxManager) === null || _a === void 0 ? void 0 : _a.cancelTransaction(transactionId); });
|
|
2525
|
+
const { device: refreshedDevice, abandoned } = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1, transactionId => { var _a; return (_a = this.blePlxManager) === null || _a === void 0 ? void 0 : _a.cancelTransaction(transactionId); });
|
|
2461
2526
|
transport.device = refreshedDevice;
|
|
2462
2527
|
transport.mtuSize =
|
|
2463
2528
|
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
2464
|
-
if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
|
|
2529
|
+
if (abandoned || shouldRefreshNegotiatedMtu(transport.mtuSize)) {
|
|
2465
2530
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`);
|
|
2466
2531
|
}
|
|
2467
2532
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.115",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-core": "1.2.2-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.2-alpha.115",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.115",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.115",
|
|
26
26
|
"@onekeyfe/react-native-ble-utils": "0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "a115ca6998586c05c22996f278091778fd5471f1"
|
|
30
30
|
}
|
|
@@ -1126,21 +1126,85 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
1126
1126
|
await transport.release(uuid, true);
|
|
1127
1127
|
});
|
|
1128
1128
|
|
|
1129
|
-
test('
|
|
1129
|
+
test('drops the link when a stalled MTU negotiation is abandoned', async () => {
|
|
1130
1130
|
const { transport, uuid, device, bleManager } = createHarness();
|
|
1131
1131
|
device.mtu = 23;
|
|
1132
1132
|
device.requestMTU.mockImplementationOnce(() => new Promise(() => {}));
|
|
1133
|
+
const { resolveCharacteristics } = transport as any;
|
|
1133
1134
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1135
|
+
// cancelTransaction only disposes the JS subscription: RxAndroidBle releases the
|
|
1136
|
+
// connection's serial queue from onComplete/onError alone, so the abandoned exchange
|
|
1137
|
+
// keeps it until its own native timeout. Service discovery must not queue behind it.
|
|
1138
|
+
await expect(transport.acquire({ uuid })).rejects.toMatchObject({
|
|
1139
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
1137
1140
|
});
|
|
1138
1141
|
const transactionId = device.requestMTU.mock.calls[0]?.[1];
|
|
1139
1142
|
expect(transactionId).toEqual(expect.stringContaining(`${uuid}:mtu:connected:0:`));
|
|
1140
1143
|
expect(bleManager.cancelTransaction).toHaveBeenCalledWith(transactionId);
|
|
1144
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1145
|
+
expect(resolveCharacteristics).not.toHaveBeenCalled();
|
|
1146
|
+
}, 10_000);
|
|
1147
|
+
|
|
1148
|
+
test('skips the MTU refresh after an MTU-bearing connect was cancelled', async () => {
|
|
1149
|
+
const { transport, uuid, device } = createHarness();
|
|
1150
|
+
device.mtu = 23;
|
|
1151
|
+
let connected = false;
|
|
1152
|
+
device.isConnected.mockImplementation(() => Promise.resolve(connected));
|
|
1153
|
+
const cancelled = Object.assign(new Error('Operation was cancelled'), { errorCode: 2 });
|
|
1154
|
+
device.connect = jest
|
|
1155
|
+
.fn()
|
|
1156
|
+
.mockRejectedValueOnce(cancelled)
|
|
1157
|
+
.mockImplementation(() => {
|
|
1158
|
+
connected = true;
|
|
1159
|
+
return Promise.resolve(device);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
1163
|
+
uuid,
|
|
1164
|
+
protocolType: 'V2',
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// ble-plx applies the connect timeout to establishConnection -> refreshGatt ->
|
|
1168
|
+
// requestMtu as one chain, so the fallback means this peripheral did not finish the
|
|
1169
|
+
// MTU exchange. Asking again would only park a second abandoned exchange.
|
|
1170
|
+
expect(device.connect).toHaveBeenCalledTimes(2);
|
|
1171
|
+
expect(device.requestMTU).not.toHaveBeenCalled();
|
|
1141
1172
|
expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
|
|
1142
1173
|
await transport.release(uuid, true);
|
|
1143
|
-
}
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
test('cleans up a cancelled native GATT setup, not only a timed-out one', async () => {
|
|
1177
|
+
const { transport, uuid, bleManager } = createHarness();
|
|
1178
|
+
const cancelled = Object.assign(new Error('Operation was cancelled'), { errorCode: 2 });
|
|
1179
|
+
(transport as any).resolveCharacteristics = jest.fn(() => Promise.reject(cancelled));
|
|
1180
|
+
|
|
1181
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1182
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
test('spends one Initialize wake on the detection after a fully silent one', async () => {
|
|
1186
|
+
const { transport, uuid } = createHarness();
|
|
1187
|
+
const probes = transport as any;
|
|
1188
|
+
jest.spyOn(probes, 'probeProtocolV1').mockResolvedValue(false);
|
|
1189
|
+
jest.spyOn(probes, 'probeProtocolV2').mockResolvedValue(false);
|
|
1190
|
+
const callProtocolV1 = jest.spyOn(probes, 'callProtocolV1').mockResolvedValue({});
|
|
1191
|
+
|
|
1192
|
+
// Nothing has gone silent yet, so the device keeps whatever session it has.
|
|
1193
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1194
|
+
expect(callProtocolV1).not.toHaveBeenCalled();
|
|
1195
|
+
|
|
1196
|
+
// The previous detection answered on no protocol, which is what a sleeping Classic
|
|
1197
|
+
// looks like, so this one wakes on the fresh link before probing.
|
|
1198
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1199
|
+
expect(callProtocolV1).toHaveBeenCalledTimes(1);
|
|
1200
|
+
expect(callProtocolV1.mock.calls[0]?.[1]).toBe('Initialize');
|
|
1201
|
+
|
|
1202
|
+
// Still silent: the wake is not repeated, so a device that is simply away is not
|
|
1203
|
+
// pushed into a fresh wallet session on every poll.
|
|
1204
|
+
callProtocolV1.mockClear();
|
|
1205
|
+
await expect(transport.acquire({ uuid })).rejects.toBeDefined();
|
|
1206
|
+
expect(callProtocolV1).not.toHaveBeenCalled();
|
|
1207
|
+
});
|
|
1144
1208
|
|
|
1145
1209
|
test('accepts a stable low MTU without the delayed refresh loop', async () => {
|
|
1146
1210
|
const { transport, uuid, device } = createHarness();
|
package/src/index.ts
CHANGED
|
@@ -300,6 +300,21 @@ const shouldRethrowBleSetupError = (error: unknown): boolean =>
|
|
|
300
300
|
isConnectTimeoutError(error) || isWedgedBleSetupError(error);
|
|
301
301
|
const isNativeOperationTimeoutError = (error: unknown): boolean =>
|
|
302
302
|
(error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
|
|
303
|
+
/**
|
|
304
|
+
* ble-plx reports its own expired budget as OperationCancelled, not OperationTimedOut:
|
|
305
|
+
* safeConnectToDevice disposes the operation chain and its doFinally reports a
|
|
306
|
+
* cancellation. Either code means the native layer abandoned work that still owned
|
|
307
|
+
* connection/GATT state, so both have to go through the stalled-connection cleanup.
|
|
308
|
+
* Only used where nothing else recovers the link — the connect path does its own
|
|
309
|
+
* ordered teardown before retrying, and routing it here too would both duplicate that
|
|
310
|
+
* and spend the manager-reset budget on a connect that is about to be retried.
|
|
311
|
+
*/
|
|
312
|
+
const isAbandonedNativeOperationError = (error: unknown): boolean => {
|
|
313
|
+
const errorCode = (error as { errorCode?: unknown })?.errorCode;
|
|
314
|
+
return (
|
|
315
|
+
errorCode === BleErrorCode.OperationTimedOut || errorCode === BleErrorCode.OperationCancelled
|
|
316
|
+
);
|
|
317
|
+
};
|
|
303
318
|
|
|
304
319
|
export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
305
320
|
|
|
@@ -312,13 +327,26 @@ const tryToGetConfiguration = (device: Device) => {
|
|
|
312
327
|
return infos;
|
|
313
328
|
};
|
|
314
329
|
|
|
330
|
+
type NegotiatedMtuResult = {
|
|
331
|
+
device: Device;
|
|
332
|
+
/**
|
|
333
|
+
* True when the bounded wait gave up while the native MTU exchange was still
|
|
334
|
+
* outstanding. `cancelTransaction` only disposes the JS-side subscription:
|
|
335
|
+
* RxAndroidBle releases a connection's serial operation queue from onComplete /
|
|
336
|
+
* onError alone, so an abandoned exchange keeps that queue until its own native
|
|
337
|
+
* timeout. Any GATT work issued before then — service discovery above all —
|
|
338
|
+
* queues behind it and cannot settle inside its own budget.
|
|
339
|
+
*/
|
|
340
|
+
abandoned: boolean;
|
|
341
|
+
};
|
|
342
|
+
|
|
315
343
|
const requestNegotiatedMtu = async (
|
|
316
344
|
device: Device,
|
|
317
345
|
stage: 'connected' | 'highThroughput',
|
|
318
346
|
attempt: number,
|
|
319
347
|
cancelTransaction?: (transactionId: string) => Promise<void> | void
|
|
320
|
-
) => {
|
|
321
|
-
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return device;
|
|
348
|
+
): Promise<NegotiatedMtuResult> => {
|
|
349
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') return { device, abandoned: false };
|
|
322
350
|
|
|
323
351
|
const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
|
|
324
352
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -328,6 +356,10 @@ const requestNegotiatedMtu = async (
|
|
|
328
356
|
// iOS ignores the requested value but react-native-ble-plx returns a fresh
|
|
329
357
|
// Device snapshot whose MTU is derived from CoreBluetooth's maximum write length.
|
|
330
358
|
const request = device.requestMTU(getRequestedBleMtu(), transactionId);
|
|
359
|
+
// The timeout race may settle before the native request does. Attach a
|
|
360
|
+
// rejection handler so a late native cancellation cannot become an
|
|
361
|
+
// unhandled rejection after we continue with the current MTU.
|
|
362
|
+
request.catch(() => undefined);
|
|
331
363
|
const mtuDevice = await Promise.race([
|
|
332
364
|
request,
|
|
333
365
|
new Promise<never>((_, reject) => {
|
|
@@ -337,7 +369,7 @@ const requestNegotiatedMtu = async (
|
|
|
337
369
|
}, BLE_MTU_REQUEST_TIMEOUT_MS);
|
|
338
370
|
}),
|
|
339
371
|
]);
|
|
340
|
-
return mtuDevice;
|
|
372
|
+
return { device: mtuDevice, abandoned: false };
|
|
341
373
|
} catch (error) {
|
|
342
374
|
if (timedOut && cancelTransaction) {
|
|
343
375
|
try {
|
|
@@ -365,7 +397,7 @@ const requestNegotiatedMtu = async (
|
|
|
365
397
|
actual: device.mtu,
|
|
366
398
|
error: error instanceof Error ? error.message : String(error),
|
|
367
399
|
});
|
|
368
|
-
return device;
|
|
400
|
+
return { device, abandoned: timedOut };
|
|
369
401
|
} finally {
|
|
370
402
|
if (timeoutId) clearTimeout(timeoutId);
|
|
371
403
|
}
|
|
@@ -374,10 +406,10 @@ const requestNegotiatedMtu = async (
|
|
|
374
406
|
const resolveNegotiatedMtu = (
|
|
375
407
|
device: Device,
|
|
376
408
|
cancelTransaction?: (transactionId: string) => Promise<void> | void
|
|
377
|
-
) =>
|
|
409
|
+
): Promise<NegotiatedMtuResult> =>
|
|
378
410
|
shouldRefreshNegotiatedMtu(device.mtu)
|
|
379
411
|
? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
|
|
380
|
-
: Promise.resolve(device);
|
|
412
|
+
: Promise.resolve({ device, abandoned: false });
|
|
381
413
|
|
|
382
414
|
type IOBleErrorRemap = Error | BleError | null | undefined;
|
|
383
415
|
|
|
@@ -461,6 +493,17 @@ export default class ReactNativeBleTransport {
|
|
|
461
493
|
/** Consecutive detections that failed while trusting sessionProtocols. */
|
|
462
494
|
private protocolReprobeFailures: Map<string, number> = new Map();
|
|
463
495
|
|
|
496
|
+
/** Endpoints whose last detection got no answer on any protocol. */
|
|
497
|
+
private silentDetections = new Set<string>();
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Endpoints already sent a Protocol V1 wake. The wake starts a fresh wallet session
|
|
501
|
+
* on the device, and a failed detection tears the link down and reconnects on every
|
|
502
|
+
* poll, so neither set is cleared on teardown: one wake is spent per endpoint until a
|
|
503
|
+
* detection succeeds, which re-arms it for the next time the device sleeps.
|
|
504
|
+
*/
|
|
505
|
+
private protocolWakeAttempts = new Set<string>();
|
|
506
|
+
|
|
464
507
|
/**
|
|
465
508
|
* Native encryption/pairing failures seen before Protocol V2 probe starts.
|
|
466
509
|
* Pro2/Neo GATT connect can succeed on a stale iOS bond; the CCCD write then
|
|
@@ -1058,6 +1101,14 @@ export default class ReactNativeBleTransport {
|
|
|
1058
1101
|
}
|
|
1059
1102
|
|
|
1060
1103
|
let device: Device | null = null;
|
|
1104
|
+
/**
|
|
1105
|
+
* Set when an MTU-bearing connect had to fall back to `fallbackConnectOptions`.
|
|
1106
|
+
* ble-plx applies `connectOptions.timeout` to the whole establishConnection ->
|
|
1107
|
+
* refreshGatt -> requestMtu chain, so that fallback means the peripheral did not
|
|
1108
|
+
* finish the MTU exchange on this link. Asking again below would only park a
|
|
1109
|
+
* second abandoned exchange on the connection's serial queue.
|
|
1110
|
+
*/
|
|
1111
|
+
let mtuHandshakeRefused = false;
|
|
1061
1112
|
|
|
1062
1113
|
if (forceCleanRunPromise && this.runPromise) {
|
|
1063
1114
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
@@ -1124,6 +1175,13 @@ export default class ReactNativeBleTransport {
|
|
|
1124
1175
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
1125
1176
|
) {
|
|
1126
1177
|
Log?.debug('first try to reconnect without params');
|
|
1178
|
+
mtuHandshakeRefused = true;
|
|
1179
|
+
// The disposed chain can still own native connection state, and the retry
|
|
1180
|
+
// would otherwise run on top of a half-open GATT client.
|
|
1181
|
+
await this.runBestEffortNativeOperation(
|
|
1182
|
+
'connect cancelled: cancel manager connection',
|
|
1183
|
+
() => blePlxManager.cancelDeviceConnection(uuid)
|
|
1184
|
+
);
|
|
1127
1185
|
device = await this.connectWithTimeout(uuid, () =>
|
|
1128
1186
|
blePlxManager.connectToDevice(uuid, fallbackConnectOptions)
|
|
1129
1187
|
);
|
|
@@ -1158,6 +1216,13 @@ export default class ReactNativeBleTransport {
|
|
|
1158
1216
|
e.errorCode === BleErrorCode.OperationCancelled
|
|
1159
1217
|
) {
|
|
1160
1218
|
Log?.debug('second try to reconnect without params');
|
|
1219
|
+
mtuHandshakeRefused = true;
|
|
1220
|
+
// The disposed chain can still own native connection state, and the retry
|
|
1221
|
+
// would otherwise run on top of a half-open GATT client.
|
|
1222
|
+
await this.runBestEffortNativeOperation(
|
|
1223
|
+
'connect cancelled: cancel device connection',
|
|
1224
|
+
() => disconnectedDevice.cancelConnection()
|
|
1225
|
+
);
|
|
1161
1226
|
try {
|
|
1162
1227
|
device = await this.connectWithTimeout(uuid, () =>
|
|
1163
1228
|
disconnectedDevice.connect(fallbackConnectOptions)
|
|
@@ -1198,9 +1263,30 @@ export default class ReactNativeBleTransport {
|
|
|
1198
1263
|
throw ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'device is not connected');
|
|
1199
1264
|
}
|
|
1200
1265
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1266
|
+
if (mtuHandshakeRefused) {
|
|
1267
|
+
Log?.debug('[ReactNativeBleTransport] skipping MTU refresh after cancelled MTU connect', {
|
|
1268
|
+
connectIdSuffix: uuid.slice(-8),
|
|
1269
|
+
actual: device.mtu,
|
|
1270
|
+
});
|
|
1271
|
+
} else {
|
|
1272
|
+
const negotiatedMtu = await resolveNegotiatedMtu(device, transactionId =>
|
|
1273
|
+
blePlxManager.cancelTransaction(transactionId)
|
|
1274
|
+
);
|
|
1275
|
+
device = negotiatedMtu.device;
|
|
1276
|
+
if (negotiatedMtu.abandoned) {
|
|
1277
|
+
// Service discovery would queue behind the abandoned exchange and miss its own
|
|
1278
|
+
// budget. Drop the link so the next attempt starts on a queue nothing holds.
|
|
1279
|
+
await this.runNativeTeardown(uuid, blePlxManager, async () => {
|
|
1280
|
+
await this.runBestEffortNativeOperation('mtu abandoned: cancel manager connection', () =>
|
|
1281
|
+
blePlxManager.cancelDeviceConnection(uuid)
|
|
1282
|
+
);
|
|
1283
|
+
});
|
|
1284
|
+
throw ERRORS.TypedError(
|
|
1285
|
+
HardwareErrorCode.BleConnectedError,
|
|
1286
|
+
'BLE MTU negotiation abandoned, reconnecting'
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1204
1290
|
if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
1205
1291
|
const acquiredDevice = device;
|
|
1206
1292
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
@@ -2096,7 +2182,7 @@ export default class ReactNativeBleTransport {
|
|
|
2096
2182
|
succeeded = true;
|
|
2097
2183
|
return result;
|
|
2098
2184
|
} catch (error) {
|
|
2099
|
-
if (timedOut ||
|
|
2185
|
+
if (timedOut || isAbandonedNativeOperationError(error)) {
|
|
2100
2186
|
const resetManager = this.abandonStalledConnection(
|
|
2101
2187
|
uuid,
|
|
2102
2188
|
timedOut ? 'gatt-backstop' : 'gatt-native'
|
|
@@ -2300,6 +2386,8 @@ export default class ReactNativeBleTransport {
|
|
|
2300
2386
|
// Keep transport-lifetime V2 proof so the same endpoint can finish a no-probe
|
|
2301
2387
|
// firmware reconnect after the native BLE manager is recreated.
|
|
2302
2388
|
this.protocolReprobeFailures.clear();
|
|
2389
|
+
this.silentDetections.clear();
|
|
2390
|
+
this.protocolWakeAttempts.clear();
|
|
2303
2391
|
this.writeTimeoutCounts.clear();
|
|
2304
2392
|
this.connectionSetupTimeoutCounts.clear();
|
|
2305
2393
|
this.monitorTokens.clear();
|
|
@@ -2411,6 +2499,8 @@ export default class ReactNativeBleTransport {
|
|
|
2411
2499
|
reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
|
2412
2500
|
const probeOrder: ProtocolType[] = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
|
|
2413
2501
|
|
|
2502
|
+
await this.wakeSilentProtocolV1Device(uuid, probeOrder);
|
|
2503
|
+
|
|
2414
2504
|
for (let i = 0; i < probeOrder.length; i += 1) {
|
|
2415
2505
|
const protocol = probeOrder[i];
|
|
2416
2506
|
if (i > 0) {
|
|
@@ -2432,6 +2522,8 @@ export default class ReactNativeBleTransport {
|
|
|
2432
2522
|
this.confirmedProtocolV2.add(uuid);
|
|
2433
2523
|
}
|
|
2434
2524
|
this.protocolReprobeFailures.delete(uuid);
|
|
2525
|
+
this.silentDetections.delete(uuid);
|
|
2526
|
+
this.protocolWakeAttempts.delete(uuid);
|
|
2435
2527
|
Log?.debug('[ReactNativeBleTransport] protocol detected', {
|
|
2436
2528
|
deviceId: uuid,
|
|
2437
2529
|
protocol,
|
|
@@ -2441,6 +2533,10 @@ export default class ReactNativeBleTransport {
|
|
|
2441
2533
|
}
|
|
2442
2534
|
}
|
|
2443
2535
|
|
|
2536
|
+
// Nothing answered on any probed protocol. Remember it so the next detection, which
|
|
2537
|
+
// the caller reaches on a freshly reconnected link, can spend a wake first.
|
|
2538
|
+
this.silentDetections.add(uuid);
|
|
2539
|
+
|
|
2444
2540
|
if (trustSessionProtocol) {
|
|
2445
2541
|
// Still silent on its own protocol: count it, and let the streak expire the
|
|
2446
2542
|
// shortcut so a device that genuinely switched protocols is found again.
|
|
@@ -2454,6 +2550,47 @@ export default class ReactNativeBleTransport {
|
|
|
2454
2550
|
throw this.createProtocolDetectionError();
|
|
2455
2551
|
}
|
|
2456
2552
|
|
|
2553
|
+
/**
|
|
2554
|
+
* Sent once before probing when the previous detection on this endpoint got no answer
|
|
2555
|
+
* at all. On Classic-family firmware that silence is also what a sleeping device looks
|
|
2556
|
+
* like: its BLE co-processor keeps serving connect, GATT and MTU while the main MCU
|
|
2557
|
+
* sits in the screensaver loop, whose host-message filter accepts only Initialize and
|
|
2558
|
+
* the *Ack messages. GetFeatures and Ping are dropped there without a reply, so no
|
|
2559
|
+
* amount of probing can bring the device back.
|
|
2560
|
+
*
|
|
2561
|
+
* Initialize is the one message that breaks that loop, but it also starts a fresh
|
|
2562
|
+
* wallet session, which is why it is not the probe itself — a bare Initialize on every
|
|
2563
|
+
* acquire would drop a hidden-wallet session before Core can restore it. Requiring a
|
|
2564
|
+
* fully silent previous detection keeps it to devices with no session left to protect.
|
|
2565
|
+
*
|
|
2566
|
+
* The firmware consumes the wake to leave its loop and does not reliably answer it, so
|
|
2567
|
+
* the result is ignored: the probes that follow are what decide the protocol.
|
|
2568
|
+
*/
|
|
2569
|
+
private async wakeSilentProtocolV1Device(uuid: string, probeOrder: ProtocolType[]) {
|
|
2570
|
+
if (!this._messages) return;
|
|
2571
|
+
if (!probeOrder.includes('V1')) return;
|
|
2572
|
+
if (!this.silentDetections.has(uuid)) return;
|
|
2573
|
+
if (this.protocolWakeAttempts.has(uuid)) return;
|
|
2574
|
+
if (!transportCache[uuid]) return;
|
|
2575
|
+
this.protocolWakeAttempts.add(uuid);
|
|
2576
|
+
|
|
2577
|
+
Log?.debug('[ReactNativeBleTransport] sending Protocol V1 Initialize wake', {
|
|
2578
|
+
connectIdSuffix: uuid.slice(-8),
|
|
2579
|
+
});
|
|
2580
|
+
try {
|
|
2581
|
+
this.probingProtocols.set(uuid, 'V1');
|
|
2582
|
+
await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
if (shouldRethrowProtocolProbeError(error)) {
|
|
2585
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
2586
|
+
throw error;
|
|
2587
|
+
}
|
|
2588
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 Initialize wake did not answer:', error);
|
|
2589
|
+
} finally {
|
|
2590
|
+
this.clearProbeProtocol(uuid, 'V1');
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2457
2594
|
private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
|
|
2458
2595
|
const transport = transportCache[uuid];
|
|
2459
2596
|
await this.protocolV2Links.invalidateLink(
|
|
@@ -2833,7 +2970,7 @@ export default class ReactNativeBleTransport {
|
|
|
2833
2970
|
const transport = this.getCachedTransport(uuid);
|
|
2834
2971
|
if (!shouldRefreshNegotiatedMtu(transport.mtuSize)) return;
|
|
2835
2972
|
|
|
2836
|
-
const refreshedDevice = await requestNegotiatedMtu(
|
|
2973
|
+
const { device: refreshedDevice, abandoned } = await requestNegotiatedMtu(
|
|
2837
2974
|
transport.device,
|
|
2838
2975
|
'highThroughput',
|
|
2839
2976
|
1,
|
|
@@ -2843,7 +2980,7 @@ export default class ReactNativeBleTransport {
|
|
|
2843
2980
|
transport.mtuSize =
|
|
2844
2981
|
typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
|
|
2845
2982
|
|
|
2846
|
-
if (shouldRefreshNegotiatedMtu(transport.mtuSize)) {
|
|
2983
|
+
if (abandoned || shouldRefreshNegotiatedMtu(transport.mtuSize)) {
|
|
2847
2984
|
throw ERRORS.TypedError(
|
|
2848
2985
|
HardwareErrorCode.BleConnectedError,
|
|
2849
2986
|
`Protocol V2 high-throughput BLE MTU unavailable: ${String(transport.mtuSize)}`
|