@onekeyfe/hd-transport-react-native 1.2.0-alpha.64 → 1.2.0-alpha.65
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 +17 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -14
- package/package.json +5 -5
- package/src/__tests__/connectTimeout.test.ts +90 -0
- package/src/index.ts +69 -24
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ declare function getProtocolV2BleTuning(): {
|
|
|
64
64
|
* inside the native budget, so this only fires when the native timeout did not.
|
|
65
65
|
*/
|
|
66
66
|
declare const BLE_CONNECT_TIMEOUT_MS: number;
|
|
67
|
+
/**
|
|
68
|
+
* Service discovery and characteristic resolution run after connect() succeeds, but
|
|
69
|
+
* CoreBluetooth schedules them on the same serial queue. If that queue is wedged by a
|
|
70
|
+
* device reboot, these calls can remain pending forever unless they have their own
|
|
71
|
+
* budget.
|
|
72
|
+
*/
|
|
73
|
+
declare const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
|
|
67
74
|
/**
|
|
68
75
|
* How many times a known device may fail its own protocol before we probe the others
|
|
69
76
|
* again. Reconnect polling during a device reboot repeats this every few seconds, and
|
|
@@ -72,7 +79,7 @@ declare const BLE_CONNECT_TIMEOUT_MS: number;
|
|
|
72
79
|
* change a device's protocol, so the shortcut has to expire rather than stick.
|
|
73
80
|
*/
|
|
74
81
|
declare const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
75
|
-
/**
|
|
82
|
+
/** BLE setup timeouts since the last successful setup before the manager is recreated. */
|
|
76
83
|
declare const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
77
84
|
type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
78
85
|
declare class ReactNativeBleTransport {
|
|
@@ -99,8 +106,8 @@ declare class ReactNativeBleTransport {
|
|
|
99
106
|
private probingProtocols;
|
|
100
107
|
/** Consecutive write timeouts per device; reset by any write that completes. */
|
|
101
108
|
private writeTimeoutCounts;
|
|
102
|
-
/**
|
|
103
|
-
private
|
|
109
|
+
/** BLE setup timeouts per device since the last complete characteristic resolution. */
|
|
110
|
+
private connectionSetupTimeoutCounts;
|
|
104
111
|
private deviceProtocolHints;
|
|
105
112
|
/** Protocol this device actually answered on, kept across reconnects of one session. */
|
|
106
113
|
private sessionProtocols;
|
|
@@ -145,13 +152,14 @@ declare class ReactNativeBleTransport {
|
|
|
145
152
|
cancel(): void;
|
|
146
153
|
/** Run a native connect under the JS backstop budget. */
|
|
147
154
|
private connectWithTimeout;
|
|
155
|
+
/** Resolve the complete GATT shape under a budget so acquire() always settles. */
|
|
156
|
+
private resolveCharacteristicsWithTimeout;
|
|
148
157
|
/**
|
|
149
|
-
* Give up on a
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* queue that just stopped responding.
|
|
158
|
+
* Give up on a BLE setup operation the native layer did not settle. The abandoned
|
|
159
|
+
* operation still owns native connection/GATT state that can poison the next attempt,
|
|
160
|
+
* so it is cleared here without awaiting the same queue that stopped responding.
|
|
153
161
|
*/
|
|
154
|
-
private
|
|
162
|
+
private abandonStalledConnection;
|
|
155
163
|
private getCachedTransport;
|
|
156
164
|
/**
|
|
157
165
|
* Write one packet under a bounded budget. A write that never settles means the
|
|
@@ -190,4 +198,4 @@ declare class ReactNativeBleTransport {
|
|
|
190
198
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
191
199
|
}
|
|
192
200
|
|
|
193
|
-
export { BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD, BLE_CONNECT_TIMEOUT_MS, BLE_WRITE_PACKET_TIMEOUT_MS, BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD, IOneKeyDevice, PROTOCOL_REPROBE_FALLBACK_ATTEMPTS, ProtocolV2BleTuning, configureProtocolV2BleTuning, ReactNativeBleTransport as default, getFirmwareUploadWriteRetryType, getProtocolV2BleTuning, resetProtocolV2BleTuning };
|
|
201
|
+
export { BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD, BLE_CONNECT_TIMEOUT_MS, BLE_GATT_SETUP_TIMEOUT_MS, BLE_WRITE_PACKET_TIMEOUT_MS, BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD, IOneKeyDevice, PROTOCOL_REPROBE_FALLBACK_ATTEMPTS, ProtocolV2BleTuning, configureProtocolV2BleTuning, ReactNativeBleTransport as default, getFirmwareUploadWriteRetryType, getProtocolV2BleTuning, resetProtocolV2BleTuning };
|
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,EAIL,UAAU,IAAI,aAAa,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAEhB,KAAK,oBAAoB,EAIzB,KAAK,YAAY,EAKjB,KAAK,oBAAoB,EAG1B,MAAM,wBAAwB,CAAC;AAmBhC,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAK1C,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;AAgBjE,KAAK,4BAA4B,GAAG,WAAW,CAAC;AAChD,KAAK,0BAA0B,GAAG;IAChC,mBAAmB,EAAE,cAAc,CAAC;IACpC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AAOF,eAAO,MAAM,+BAA+B,UACnC,OAAO,KACb,4BAA4B,GAAG,IAsBjC,CAAC;AAcF,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAOlD,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;AAgCD,eAAO,MAAM,sBAAsB,QAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -295,12 +295,14 @@ const fallbackConnectOptions = {
|
|
|
295
295
|
timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
|
|
296
296
|
};
|
|
297
297
|
const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
298
|
+
const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
|
|
298
299
|
const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
299
300
|
const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
300
301
|
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
301
302
|
const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
|
|
302
303
|
typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
|
|
303
304
|
error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
305
|
+
const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
|
|
304
306
|
const tryToGetConfiguration = (device) => {
|
|
305
307
|
if (!device || !device.serviceUUIDs)
|
|
306
308
|
return null;
|
|
@@ -360,7 +362,7 @@ class ReactNativeBleTransport {
|
|
|
360
362
|
this.deviceProtocol = new Map();
|
|
361
363
|
this.probingProtocols = new Map();
|
|
362
364
|
this.writeTimeoutCounts = new Map();
|
|
363
|
-
this.
|
|
365
|
+
this.connectionSetupTimeoutCounts = new Map();
|
|
364
366
|
this.deviceProtocolHints = new Map();
|
|
365
367
|
this.sessionProtocols = new Map();
|
|
366
368
|
this.protocolReprobeFailures = new Map();
|
|
@@ -567,7 +569,7 @@ class ReactNativeBleTransport {
|
|
|
567
569
|
}
|
|
568
570
|
}
|
|
569
571
|
}
|
|
570
|
-
const { writeCharacteristic, notifyCharacteristic } = yield this.
|
|
572
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
|
|
571
573
|
transport.device = device;
|
|
572
574
|
transport.writeCharacteristic = writeCharacteristic;
|
|
573
575
|
transport.notifyCharacteristic = notifyCharacteristic;
|
|
@@ -696,7 +698,7 @@ class ReactNativeBleTransport {
|
|
|
696
698
|
}
|
|
697
699
|
installTransportForAcquire(uuid, device, characteristics) {
|
|
698
700
|
return __awaiter(this, void 0, void 0, function* () {
|
|
699
|
-
const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.
|
|
701
|
+
const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
700
702
|
const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
701
703
|
if (reactNative.Platform.OS === 'android') {
|
|
702
704
|
transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
|
|
@@ -836,7 +838,7 @@ class ReactNativeBleTransport {
|
|
|
836
838
|
}
|
|
837
839
|
device = yield requestAndroidMtu(device);
|
|
838
840
|
const acquiredDevice = device;
|
|
839
|
-
const { writeCharacteristic, notifyCharacteristic } = yield this.
|
|
841
|
+
const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
840
842
|
const protocolHint = expectedProtocol
|
|
841
843
|
? undefined
|
|
842
844
|
: (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
@@ -1345,12 +1347,42 @@ class ReactNativeBleTransport {
|
|
|
1345
1347
|
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1346
1348
|
}),
|
|
1347
1349
|
]);
|
|
1348
|
-
this.connectTimeoutCounts.delete(uuid);
|
|
1349
1350
|
return result;
|
|
1350
1351
|
}
|
|
1351
1352
|
catch (error) {
|
|
1352
|
-
if (timedOut) {
|
|
1353
|
-
this.
|
|
1353
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1354
|
+
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1355
|
+
}
|
|
1356
|
+
throw error;
|
|
1357
|
+
}
|
|
1358
|
+
finally {
|
|
1359
|
+
if (timer)
|
|
1360
|
+
clearTimeout(timer);
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
resolveCharacteristicsWithTimeout(uuid, device) {
|
|
1365
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1366
|
+
let timer;
|
|
1367
|
+
let timedOut = false;
|
|
1368
|
+
const pending = this.resolveCharacteristics(device);
|
|
1369
|
+
pending.catch(() => undefined);
|
|
1370
|
+
try {
|
|
1371
|
+
const result = yield Promise.race([
|
|
1372
|
+
pending,
|
|
1373
|
+
new Promise((_, reject) => {
|
|
1374
|
+
timer = setTimeout(() => {
|
|
1375
|
+
timedOut = true;
|
|
1376
|
+
reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
|
|
1377
|
+
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1378
|
+
}),
|
|
1379
|
+
]);
|
|
1380
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1381
|
+
return result;
|
|
1382
|
+
}
|
|
1383
|
+
catch (error) {
|
|
1384
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1385
|
+
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1354
1386
|
}
|
|
1355
1387
|
throw error;
|
|
1356
1388
|
}
|
|
@@ -1360,12 +1392,13 @@ class ReactNativeBleTransport {
|
|
|
1360
1392
|
}
|
|
1361
1393
|
});
|
|
1362
1394
|
}
|
|
1363
|
-
|
|
1395
|
+
abandonStalledConnection(uuid, stage) {
|
|
1364
1396
|
var _a, _b;
|
|
1365
|
-
const timeouts = ((_a = this.
|
|
1366
|
-
this.
|
|
1367
|
-
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE
|
|
1368
|
-
|
|
1397
|
+
const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
1398
|
+
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1399
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1400
|
+
stage,
|
|
1401
|
+
setupTimeoutsSinceSuccess: timeouts,
|
|
1369
1402
|
});
|
|
1370
1403
|
(_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
|
|
1371
1404
|
});
|
|
@@ -1378,9 +1411,9 @@ class ReactNativeBleTransport {
|
|
|
1378
1411
|
this.protocolV2Assemblers.delete(uuid);
|
|
1379
1412
|
this.resetProtocolV2Frames(uuid);
|
|
1380
1413
|
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1381
|
-
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE
|
|
1414
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1382
1415
|
this.resetPlxManager();
|
|
1383
|
-
this.
|
|
1416
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1384
1417
|
}
|
|
1385
1418
|
}
|
|
1386
1419
|
getCachedTransport(uuid) {
|
|
@@ -1847,6 +1880,7 @@ class ReactNativeBleTransport {
|
|
|
1847
1880
|
|
|
1848
1881
|
exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
1849
1882
|
exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
|
|
1883
|
+
exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
|
|
1850
1884
|
exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
|
|
1851
1885
|
exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
1852
1886
|
exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.65",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"lint:fix": "eslint . --fix"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@onekeyfe/hd-core": "1.2.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.65",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.65",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.65",
|
|
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": "2850b5b34935d4e97a1fe8a6459eb5a96a5cf0f6"
|
|
30
30
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
import { BleErrorCode } from 'react-native-ble-plx';
|
|
2
3
|
|
|
3
4
|
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
5
|
|
|
5
6
|
import ReactNativeBleTransport, {
|
|
6
7
|
BLE_CONNECT_TIMEOUT_MS,
|
|
7
8
|
BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD,
|
|
9
|
+
BLE_GATT_SETUP_TIMEOUT_MS,
|
|
8
10
|
} from '../index';
|
|
9
11
|
|
|
10
12
|
import messages from '@onekeyfe/hd-transport/messages.json';
|
|
@@ -31,6 +33,7 @@ jest.mock('react-native-ble-plx', () => ({
|
|
|
31
33
|
OperationStartFailed: 601,
|
|
32
34
|
DeviceMTUChangeFailed: 401,
|
|
33
35
|
OperationCancelled: 2,
|
|
36
|
+
OperationTimedOut: 3,
|
|
34
37
|
DeviceAlreadyConnected: 203,
|
|
35
38
|
},
|
|
36
39
|
BleManager: jest.fn(),
|
|
@@ -66,6 +69,14 @@ async function advanceUntil(settled: () => boolean, totalMs: number, stepMs = 25
|
|
|
66
69
|
/** A device whose native connect() never settles — the observed iOS failure mode. */
|
|
67
70
|
function createHarness(connectImpl: () => Promise<unknown>) {
|
|
68
71
|
const connect = jest.fn(connectImpl);
|
|
72
|
+
const writeCharacteristic = {
|
|
73
|
+
uuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
74
|
+
isWritableWithResponse: true,
|
|
75
|
+
};
|
|
76
|
+
const notifyCharacteristic = {
|
|
77
|
+
uuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
78
|
+
isNotifiable: true,
|
|
79
|
+
};
|
|
69
80
|
const device = {
|
|
70
81
|
id: UUID,
|
|
71
82
|
name: 'OneKey Classic',
|
|
@@ -74,6 +85,11 @@ function createHarness(connectImpl: () => Promise<unknown>) {
|
|
|
74
85
|
isConnected: jest.fn(() => Promise.resolve(false)),
|
|
75
86
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
76
87
|
connect,
|
|
88
|
+
discoverAllServicesAndCharacteristics: jest.fn(() => Promise.resolve()),
|
|
89
|
+
characteristicsForService: jest.fn(() =>
|
|
90
|
+
Promise.resolve([writeCharacteristic, notifyCharacteristic])
|
|
91
|
+
),
|
|
92
|
+
services: jest.fn(() => Promise.resolve([])),
|
|
77
93
|
onDisconnected: jest.fn(() => ({ remove: jest.fn() })),
|
|
78
94
|
};
|
|
79
95
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
@@ -189,4 +205,78 @@ describe('BLE connect timeout', () => {
|
|
|
189
205
|
expect(destroy).toHaveBeenCalledTimes(1);
|
|
190
206
|
expect((transport as unknown as { blePlxManager?: unknown }).blePlxManager).toBeUndefined();
|
|
191
207
|
});
|
|
208
|
+
|
|
209
|
+
test('native connect timeouts contribute to the same manager reset budget', async () => {
|
|
210
|
+
const { transport, bleManager } = createHarness(() => Promise.resolve());
|
|
211
|
+
const destroy = jest.fn();
|
|
212
|
+
(bleManager as unknown as { destroy: jest.Mock }).destroy = destroy;
|
|
213
|
+
const nativeTimeout = Object.assign(new Error('Operation timed out'), {
|
|
214
|
+
errorCode: BleErrorCode.OperationTimedOut,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
for (let attempt = 0; attempt < BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD; attempt += 1) {
|
|
218
|
+
// eslint-disable-next-line no-await-in-loop
|
|
219
|
+
await expect(
|
|
220
|
+
(transport as any).connectWithTimeout(UUID, () => Promise.reject(nativeTimeout))
|
|
221
|
+
).rejects.toBe(nativeTimeout);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
expect(destroy).toHaveBeenCalledTimes(1);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('GATT discovery is bounded and abandons the native connection', async () => {
|
|
228
|
+
const { transport, device, bleManager } = createHarness(() => Promise.resolve());
|
|
229
|
+
device.discoverAllServicesAndCharacteristics.mockImplementation(
|
|
230
|
+
() =>
|
|
231
|
+
new Promise(() => {
|
|
232
|
+
// never settles
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const errors: Array<{ errorCode?: unknown }> = [];
|
|
237
|
+
let settled = false;
|
|
238
|
+
(transport as any).resolveCharacteristicsWithTimeout(UUID, device).catch((error: unknown) => {
|
|
239
|
+
errors.push(error as { errorCode?: unknown });
|
|
240
|
+
settled = true;
|
|
241
|
+
});
|
|
242
|
+
await flush();
|
|
243
|
+
await advanceUntil(() => settled, BLE_GATT_SETUP_TIMEOUT_MS + 5000);
|
|
244
|
+
|
|
245
|
+
expect(errors).toHaveLength(1);
|
|
246
|
+
expect(errors[0]?.errorCode).toBe(HardwareErrorCode.BleConnectedError);
|
|
247
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(UUID);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('a successful GATT retry clears the timeout budget before an abandoned call settles', async () => {
|
|
251
|
+
const { transport, device, bleManager } = createHarness(() => Promise.resolve());
|
|
252
|
+
let resolveAbandonedDiscovery: (() => void) | undefined;
|
|
253
|
+
device.discoverAllServicesAndCharacteristics
|
|
254
|
+
.mockImplementationOnce(
|
|
255
|
+
() =>
|
|
256
|
+
new Promise<void>(resolve => {
|
|
257
|
+
resolveAbandonedDiscovery = resolve;
|
|
258
|
+
})
|
|
259
|
+
)
|
|
260
|
+
.mockResolvedValueOnce(undefined);
|
|
261
|
+
|
|
262
|
+
let firstSettled = false;
|
|
263
|
+
(transport as any).resolveCharacteristicsWithTimeout(UUID, device).catch(() => {
|
|
264
|
+
firstSettled = true;
|
|
265
|
+
});
|
|
266
|
+
await flush();
|
|
267
|
+
await advanceUntil(() => firstSettled, BLE_GATT_SETUP_TIMEOUT_MS + 5000);
|
|
268
|
+
|
|
269
|
+
await expect(
|
|
270
|
+
(transport as any).resolveCharacteristicsWithTimeout(UUID, device)
|
|
271
|
+
).resolves.toMatchObject({
|
|
272
|
+
writeCharacteristic: expect.any(Object),
|
|
273
|
+
notifyCharacteristic: expect.any(Object),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
resolveAbandonedDiscovery?.();
|
|
277
|
+
await flush();
|
|
278
|
+
|
|
279
|
+
expect(bleManager.cancelDeviceConnection).toHaveBeenCalledTimes(1);
|
|
280
|
+
expect((transport as any).connectionSetupTimeoutCounts.has(UUID)).toBe(false);
|
|
281
|
+
});
|
|
192
282
|
});
|
package/src/index.ts
CHANGED
|
@@ -199,6 +199,13 @@ const fallbackConnectOptions: Record<string, unknown> = {
|
|
|
199
199
|
* inside the native budget, so this only fires when the native timeout did not.
|
|
200
200
|
*/
|
|
201
201
|
export const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
202
|
+
/**
|
|
203
|
+
* Service discovery and characteristic resolution run after connect() succeeds, but
|
|
204
|
+
* CoreBluetooth schedules them on the same serial queue. If that queue is wedged by a
|
|
205
|
+
* device reboot, these calls can remain pending forever unless they have their own
|
|
206
|
+
* budget.
|
|
207
|
+
*/
|
|
208
|
+
export const BLE_GATT_SETUP_TIMEOUT_MS = 10_000;
|
|
202
209
|
/**
|
|
203
210
|
* How many times a known device may fail its own protocol before we probe the others
|
|
204
211
|
* again. Reconnect polling during a device reboot repeats this every few seconds, and
|
|
@@ -207,13 +214,15 @@ export const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
|
|
|
207
214
|
* change a device's protocol, so the shortcut has to expire rather than stick.
|
|
208
215
|
*/
|
|
209
216
|
export const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
|
|
210
|
-
/**
|
|
217
|
+
/** BLE setup timeouts since the last successful setup before the manager is recreated. */
|
|
211
218
|
export const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
212
219
|
const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
|
|
213
220
|
const isConnectTimeoutError = (error: unknown): boolean =>
|
|
214
221
|
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleConnectedError &&
|
|
215
222
|
typeof (error as { message?: unknown })?.message === 'string' &&
|
|
216
223
|
(error as { message: string }).message.startsWith(CONNECT_TIMEOUT_MESSAGE);
|
|
224
|
+
const isNativeOperationTimeoutError = (error: unknown): boolean =>
|
|
225
|
+
(error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
|
|
217
226
|
|
|
218
227
|
export type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
|
|
219
228
|
|
|
@@ -314,8 +323,8 @@ export default class ReactNativeBleTransport {
|
|
|
314
323
|
/** Consecutive write timeouts per device; reset by any write that completes. */
|
|
315
324
|
private writeTimeoutCounts: Map<string, number> = new Map();
|
|
316
325
|
|
|
317
|
-
/**
|
|
318
|
-
private
|
|
326
|
+
/** BLE setup timeouts per device since the last complete characteristic resolution. */
|
|
327
|
+
private connectionSetupTimeoutCounts: Map<string, number> = new Map();
|
|
319
328
|
|
|
320
329
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
321
330
|
|
|
@@ -555,9 +564,8 @@ export default class ReactNativeBleTransport {
|
|
|
555
564
|
}
|
|
556
565
|
}
|
|
557
566
|
|
|
558
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
559
|
-
device
|
|
560
|
-
);
|
|
567
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
568
|
+
await this.resolveCharacteristicsWithTimeout(uuid, device);
|
|
561
569
|
|
|
562
570
|
transport.device = device;
|
|
563
571
|
transport.writeCharacteristic = writeCharacteristic;
|
|
@@ -722,7 +730,7 @@ export default class ReactNativeBleTransport {
|
|
|
722
730
|
characteristics?: ResolvedBleCharacteristics
|
|
723
731
|
) {
|
|
724
732
|
const { writeCharacteristic, notifyCharacteristic } =
|
|
725
|
-
characteristics ?? (await this.
|
|
733
|
+
characteristics ?? (await this.resolveCharacteristicsWithTimeout(uuid, device));
|
|
726
734
|
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
727
735
|
if (Platform.OS === 'android') {
|
|
728
736
|
transport.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport.mtuSize;
|
|
@@ -897,9 +905,8 @@ export default class ReactNativeBleTransport {
|
|
|
897
905
|
|
|
898
906
|
device = await requestAndroidMtu(device);
|
|
899
907
|
const acquiredDevice = device;
|
|
900
|
-
const { writeCharacteristic, notifyCharacteristic } =
|
|
901
|
-
acquiredDevice
|
|
902
|
-
);
|
|
908
|
+
const { writeCharacteristic, notifyCharacteristic } =
|
|
909
|
+
await this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
|
|
903
910
|
|
|
904
911
|
const protocolHint = expectedProtocol
|
|
905
912
|
? undefined
|
|
@@ -1546,11 +1553,46 @@ export default class ReactNativeBleTransport {
|
|
|
1546
1553
|
}, BLE_CONNECT_TIMEOUT_MS);
|
|
1547
1554
|
}),
|
|
1548
1555
|
]);
|
|
1549
|
-
this.connectTimeoutCounts.delete(uuid);
|
|
1550
1556
|
return result;
|
|
1551
1557
|
} catch (error) {
|
|
1552
|
-
if (timedOut) {
|
|
1553
|
-
this.
|
|
1558
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1559
|
+
this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
|
|
1560
|
+
}
|
|
1561
|
+
throw error;
|
|
1562
|
+
} finally {
|
|
1563
|
+
if (timer) clearTimeout(timer);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/** Resolve the complete GATT shape under a budget so acquire() always settles. */
|
|
1568
|
+
private async resolveCharacteristicsWithTimeout(
|
|
1569
|
+
uuid: string,
|
|
1570
|
+
device: Device
|
|
1571
|
+
): Promise<ResolvedBleCharacteristics> {
|
|
1572
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1573
|
+
let timedOut = false;
|
|
1574
|
+
const pending = this.resolveCharacteristics(device);
|
|
1575
|
+
pending.catch(() => undefined);
|
|
1576
|
+
try {
|
|
1577
|
+
const result = await Promise.race([
|
|
1578
|
+
pending,
|
|
1579
|
+
new Promise<never>((_, reject) => {
|
|
1580
|
+
timer = setTimeout(() => {
|
|
1581
|
+
timedOut = true;
|
|
1582
|
+
reject(
|
|
1583
|
+
ERRORS.TypedError(
|
|
1584
|
+
HardwareErrorCode.BleConnectedError,
|
|
1585
|
+
`BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`
|
|
1586
|
+
)
|
|
1587
|
+
);
|
|
1588
|
+
}, BLE_GATT_SETUP_TIMEOUT_MS);
|
|
1589
|
+
}),
|
|
1590
|
+
]);
|
|
1591
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1592
|
+
return result;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
if (timedOut || isNativeOperationTimeoutError(error)) {
|
|
1595
|
+
this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
|
|
1554
1596
|
}
|
|
1555
1597
|
throw error;
|
|
1556
1598
|
} finally {
|
|
@@ -1559,16 +1601,19 @@ export default class ReactNativeBleTransport {
|
|
|
1559
1601
|
}
|
|
1560
1602
|
|
|
1561
1603
|
/**
|
|
1562
|
-
* Give up on a
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
* queue that just stopped responding.
|
|
1604
|
+
* Give up on a BLE setup operation the native layer did not settle. The abandoned
|
|
1605
|
+
* operation still owns native connection/GATT state that can poison the next attempt,
|
|
1606
|
+
* so it is cleared here without awaiting the same queue that stopped responding.
|
|
1566
1607
|
*/
|
|
1567
|
-
private
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1608
|
+
private abandonStalledConnection(
|
|
1609
|
+
uuid: string,
|
|
1610
|
+
stage: 'connect-backstop' | 'connect-native' | 'gatt-backstop' | 'gatt-native'
|
|
1611
|
+
) {
|
|
1612
|
+
const timeouts = (this.connectionSetupTimeoutCounts.get(uuid) ?? 0) + 1;
|
|
1613
|
+
this.connectionSetupTimeoutCounts.set(uuid, timeouts);
|
|
1614
|
+
Log?.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
|
|
1615
|
+
stage,
|
|
1616
|
+
setupTimeoutsSinceSuccess: timeouts,
|
|
1572
1617
|
});
|
|
1573
1618
|
|
|
1574
1619
|
this.blePlxManager?.cancelDeviceConnection(uuid).catch(() => {
|
|
@@ -1586,9 +1631,9 @@ export default class ReactNativeBleTransport {
|
|
|
1586
1631
|
if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
|
|
1587
1632
|
// BleManager.destroy() force-rejects every promise the native queue abandoned —
|
|
1588
1633
|
// the only JS-reachable way to settle them — and drops all cached peripherals.
|
|
1589
|
-
Log?.error('[ReactNativeBleTransport] BLE
|
|
1634
|
+
Log?.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
|
|
1590
1635
|
this.resetPlxManager();
|
|
1591
|
-
this.
|
|
1636
|
+
this.connectionSetupTimeoutCounts.delete(uuid);
|
|
1592
1637
|
}
|
|
1593
1638
|
}
|
|
1594
1639
|
|