@onekeyfe/hd-transport-react-native 1.2.0-alpha.134 → 1.2.0-alpha.135
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 +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -49
- package/package.json +5 -5
- package/src/__tests__/protocolV2Link.test.ts +91 -0
- package/src/index.ts +168 -49
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ declare const getFirmwareUploadWriteRetryType: (error: unknown) => FirmwareUploa
|
|
|
53
53
|
* A healthy packet completes in milliseconds, so this only fires on a dead link.
|
|
54
54
|
*/
|
|
55
55
|
declare const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
|
|
56
|
+
declare const BLE_NATIVE_TEARDOWN_TIMEOUT_MS = 3000;
|
|
56
57
|
/** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
|
|
57
58
|
declare const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
|
|
58
59
|
type ProtocolV2BleTuning = {
|
|
@@ -132,6 +133,8 @@ declare class ReactNativeBleTransport {
|
|
|
132
133
|
private androidHighPriorityDevices;
|
|
133
134
|
private androidPriorityResetTimers;
|
|
134
135
|
private nextMonitorToken;
|
|
136
|
+
/** Serializes transport lifecycle changes for the same physical device. */
|
|
137
|
+
private lifecycleOperations;
|
|
135
138
|
constructor(options: TransportOptions);
|
|
136
139
|
init(logger: any, emitter: EventEmitter): void;
|
|
137
140
|
configure(signedData: any): void;
|
|
@@ -153,14 +156,20 @@ declare class ReactNativeBleTransport {
|
|
|
153
156
|
uuid: string;
|
|
154
157
|
protocolType: ProtocolType;
|
|
155
158
|
}>;
|
|
159
|
+
private acquireUnlocked;
|
|
156
160
|
_monitorCharacteristic(characteristic: Characteristic, uuid: string, monitorToken: number, notifyTransactionId: string): Subscription;
|
|
157
161
|
release(uuid: string, onclose?: boolean): Promise<boolean>;
|
|
162
|
+
private releaseUnlocked;
|
|
158
163
|
private releaseNative;
|
|
159
164
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
160
165
|
call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
|
|
161
166
|
private callProtocolV1;
|
|
162
167
|
stop(): void;
|
|
163
168
|
disconnect(session: string): Promise<void>;
|
|
169
|
+
private disconnectUnlocked;
|
|
170
|
+
private runNativeTeardown;
|
|
171
|
+
private runBestEffortNativeOperation;
|
|
172
|
+
private runLifecycleOperation;
|
|
164
173
|
cancel(): void;
|
|
165
174
|
/** Run a native connect under the JS backstop budget. */
|
|
166
175
|
private connectWithTimeout;
|
|
@@ -215,4 +224,4 @@ declare class ReactNativeBleTransport {
|
|
|
215
224
|
getProtocolType(path: string): ProtocolType | undefined;
|
|
216
225
|
}
|
|
217
226
|
|
|
218
|
-
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 };
|
|
227
|
+
export { BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD, BLE_CONNECT_TIMEOUT_MS, BLE_GATT_SETUP_TIMEOUT_MS, BLE_NATIVE_TEARDOWN_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,EAG5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAChB,KAAK,oBAAoB,EAIzB,KAAK,YAAY,EAKjB,KAAK,oBAAoB,EAI1B,MAAM,wBAAwB,CAAC;AA0BhC,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;AAgBjE,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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -277,6 +277,7 @@ const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs
|
|
|
277
277
|
const PROTOCOL_PROBE_TIMEOUT_MS = 3000;
|
|
278
278
|
const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
|
|
279
279
|
const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
|
|
280
|
+
const BLE_NATIVE_TEARDOWN_TIMEOUT_MS = 3000;
|
|
280
281
|
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
281
282
|
const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
|
|
282
283
|
typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
|
|
@@ -434,6 +435,7 @@ class ReactNativeBleTransport {
|
|
|
434
435
|
this.androidHighPriorityDevices = new Set();
|
|
435
436
|
this.androidPriorityResetTimers = new Map();
|
|
436
437
|
this.nextMonitorToken = 1;
|
|
438
|
+
this.lifecycleOperations = new Map();
|
|
437
439
|
this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
|
|
438
440
|
}
|
|
439
441
|
init(logger, emitter) {
|
|
@@ -791,12 +793,18 @@ class ReactNativeBleTransport {
|
|
|
791
793
|
});
|
|
792
794
|
}
|
|
793
795
|
acquire(input) {
|
|
794
|
-
var _a, _b;
|
|
795
796
|
return __awaiter(this, void 0, void 0, function* () {
|
|
796
|
-
const { uuid
|
|
797
|
+
const { uuid } = input;
|
|
797
798
|
if (!uuid) {
|
|
798
799
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
|
|
799
800
|
}
|
|
801
|
+
return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
acquireUnlocked(input) {
|
|
805
|
+
var _a, _b;
|
|
806
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
807
|
+
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
800
808
|
const cachedTransport = transportCache[uuid];
|
|
801
809
|
if (cachedTransport) {
|
|
802
810
|
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
@@ -808,7 +816,7 @@ class ReactNativeBleTransport {
|
|
|
808
816
|
return { uuid, protocolType: cachedProtocol };
|
|
809
817
|
}
|
|
810
818
|
Log === null || Log === void 0 ? void 0 : Log.debug('transport not reusable, will release: ', uuid);
|
|
811
|
-
yield this.
|
|
819
|
+
yield this.releaseUnlocked(uuid, true);
|
|
812
820
|
}
|
|
813
821
|
let device = null;
|
|
814
822
|
if (forceCleanRunPromise && this.runPromise) {
|
|
@@ -909,7 +917,7 @@ class ReactNativeBleTransport {
|
|
|
909
917
|
const protocolHint = expectedProtocol
|
|
910
918
|
? undefined
|
|
911
919
|
: (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
912
|
-
yield this.
|
|
920
|
+
yield this.releaseUnlocked(uuid, true);
|
|
913
921
|
if (protocolHint) {
|
|
914
922
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
915
923
|
}
|
|
@@ -929,7 +937,7 @@ class ReactNativeBleTransport {
|
|
|
929
937
|
return { uuid, protocolType };
|
|
930
938
|
}
|
|
931
939
|
catch (error) {
|
|
932
|
-
yield this.
|
|
940
|
+
yield this.releaseUnlocked(uuid, true);
|
|
933
941
|
throw error;
|
|
934
942
|
}
|
|
935
943
|
});
|
|
@@ -1040,15 +1048,21 @@ class ReactNativeBleTransport {
|
|
|
1040
1048
|
return subscription;
|
|
1041
1049
|
}
|
|
1042
1050
|
release(uuid, onclose = false) {
|
|
1051
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1052
|
+
return this.runLifecycleOperation(uuid, () => this.releaseUnlocked(uuid, onclose));
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
releaseUnlocked(uuid, onclose = false) {
|
|
1043
1056
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1044
1057
|
yield this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
1045
1058
|
return this.releaseNative(uuid, onclose);
|
|
1046
1059
|
});
|
|
1047
1060
|
}
|
|
1048
1061
|
releaseNative(uuid, onclose = false) {
|
|
1049
|
-
var _a, _b, _c, _d, _e
|
|
1062
|
+
var _a, _b, _c, _d, _e;
|
|
1050
1063
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1051
1064
|
const transport = transportCache[uuid];
|
|
1065
|
+
const manager = this.blePlxManager;
|
|
1052
1066
|
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1053
1067
|
const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
|
|
1054
1068
|
this.runPromise.reject(error);
|
|
@@ -1064,7 +1078,6 @@ class ReactNativeBleTransport {
|
|
|
1064
1078
|
this.resetProtocolV2Frames(uuid);
|
|
1065
1079
|
return Promise.resolve(true);
|
|
1066
1080
|
}
|
|
1067
|
-
yield this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1068
1081
|
if (transport) {
|
|
1069
1082
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1070
1083
|
this.monitorTokens.delete(uuid);
|
|
@@ -1075,28 +1088,28 @@ class ReactNativeBleTransport {
|
|
|
1075
1088
|
Log === null || Log === void 0 ? void 0 : Log.debug('release: removing notify subscription, characteristic: ', (_c = transport.notifyCharacteristic) === null || _c === void 0 ? void 0 : _c.uuid);
|
|
1076
1089
|
(_d = transport.notifySubscription) === null || _d === void 0 ? void 0 : _d.remove();
|
|
1077
1090
|
transport.notifySubscription = undefined;
|
|
1078
|
-
if (transport
|
|
1079
|
-
|
|
1080
|
-
yield ((_e = this.blePlxManager) === null || _e === void 0 ? void 0 : _e.cancelTransaction(transport.notifyTransactionId));
|
|
1081
|
-
}
|
|
1082
|
-
catch (e) {
|
|
1083
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel notify transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
|
|
1084
|
-
}
|
|
1091
|
+
if (transportCache[uuid] === transport) {
|
|
1092
|
+
delete transportCache[uuid];
|
|
1085
1093
|
}
|
|
1086
|
-
delete transportCache[uuid];
|
|
1087
1094
|
}
|
|
1088
1095
|
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
1089
1096
|
this.deviceProtocol.delete(uuid);
|
|
1090
1097
|
this.probingProtocols.delete(uuid);
|
|
1091
|
-
(
|
|
1098
|
+
(_e = this.protocolV2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
|
|
1092
1099
|
this.protocolV2Assemblers.delete(uuid);
|
|
1093
1100
|
this.resetProtocolV2Frames(uuid);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1101
|
+
yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
|
|
1102
|
+
const operations = [
|
|
1103
|
+
this.runBestEffortNativeOperation('release: restore connection priority', () => this.restoreAndroidConnectionPriority(uuid, transport)),
|
|
1104
|
+
];
|
|
1105
|
+
if ((transport === null || transport === void 0 ? void 0 : transport.notifyTransactionId) && manager) {
|
|
1106
|
+
operations.push(this.runBestEffortNativeOperation('release: cancel notify transaction', () => manager.cancelTransaction(transport.notifyTransactionId)));
|
|
1107
|
+
}
|
|
1108
|
+
if (manager) {
|
|
1109
|
+
operations.push(this.runBestEffortNativeOperation('release: cancel transaction', () => manager.cancelTransaction(uuid)));
|
|
1110
|
+
}
|
|
1111
|
+
yield Promise.all(operations);
|
|
1112
|
+
}));
|
|
1100
1113
|
return Promise.resolve(true);
|
|
1101
1114
|
});
|
|
1102
1115
|
}
|
|
@@ -1331,10 +1344,16 @@ class ReactNativeBleTransport {
|
|
|
1331
1344
|
this.stopped = true;
|
|
1332
1345
|
}
|
|
1333
1346
|
disconnect(session) {
|
|
1334
|
-
|
|
1347
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1348
|
+
return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
disconnectUnlocked(session) {
|
|
1352
|
+
var _a, _b, _c;
|
|
1335
1353
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1336
1354
|
yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1337
1355
|
const transport = transportCache[session];
|
|
1356
|
+
const manager = this.blePlxManager;
|
|
1338
1357
|
const monitorToken = (_a = transport === null || transport === void 0 ? void 0 : transport.monitorToken) !== null && _a !== void 0 ? _a : this.monitorTokens.get(session);
|
|
1339
1358
|
if (transport === null || transport === void 0 ? void 0 : transport.disconnectSubscription) {
|
|
1340
1359
|
try {
|
|
@@ -1356,29 +1375,7 @@ class ReactNativeBleTransport {
|
|
|
1356
1375
|
Log === null || Log === void 0 ? void 0 : Log.error('disconnect: remove notify subscription error: ', e);
|
|
1357
1376
|
}
|
|
1358
1377
|
}
|
|
1359
|
-
if (session) {
|
|
1360
|
-
try {
|
|
1361
|
-
yield ((_c = this.blePlxManager) === null || _c === void 0 ? void 0 : _c.cancelTransaction(session));
|
|
1362
|
-
}
|
|
1363
|
-
catch (e) {
|
|
1364
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
if (transport === null || transport === void 0 ? void 0 : transport.device) {
|
|
1368
|
-
try {
|
|
1369
|
-
yield transport.device.cancelConnection();
|
|
1370
|
-
}
|
|
1371
|
-
catch (e) {
|
|
1372
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: device.cancelConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
try {
|
|
1376
|
-
yield ((_d = this.blePlxManager) === null || _d === void 0 ? void 0 : _d.cancelDeviceConnection(session));
|
|
1377
|
-
}
|
|
1378
|
-
catch (e) {
|
|
1379
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('resetSession: manager.cancelDeviceConnection error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
|
|
1380
|
-
}
|
|
1381
|
-
if (transportCache[session]) {
|
|
1378
|
+
if (!transport || transportCache[session] === transport) {
|
|
1382
1379
|
delete transportCache[session];
|
|
1383
1380
|
}
|
|
1384
1381
|
this.deviceProtocol.delete(session);
|
|
@@ -1389,7 +1386,7 @@ class ReactNativeBleTransport {
|
|
|
1389
1386
|
this.protocolV2Assemblers.delete(session);
|
|
1390
1387
|
this.resetProtocolV2Frames(session);
|
|
1391
1388
|
try {
|
|
1392
|
-
this.emitDeviceDisconnect(session, (
|
|
1389
|
+
this.emitDeviceDisconnect(session, (_c = transport === null || transport === void 0 ? void 0 : transport.device) === null || _c === void 0 ? void 0 : _c.name, monitorToken);
|
|
1393
1390
|
}
|
|
1394
1391
|
catch (e) {
|
|
1395
1392
|
Log === null || Log === void 0 ? void 0 : Log.error('resetSession: emit disconnect event error: ', e);
|
|
@@ -1397,9 +1394,81 @@ class ReactNativeBleTransport {
|
|
|
1397
1394
|
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1398
1395
|
this.monitorTokens.delete(session);
|
|
1399
1396
|
}
|
|
1397
|
+
yield this.runNativeTeardown(session, manager, () => __awaiter(this, void 0, void 0, function* () {
|
|
1398
|
+
const operations = [];
|
|
1399
|
+
if (manager) {
|
|
1400
|
+
operations.push(this.runBestEffortNativeOperation('disconnect: cancel transaction', () => manager.cancelTransaction(session)));
|
|
1401
|
+
operations.push(this.runBestEffortNativeOperation('disconnect: cancel device connection', () => manager.cancelDeviceConnection(session)));
|
|
1402
|
+
}
|
|
1403
|
+
if (transport === null || transport === void 0 ? void 0 : transport.device) {
|
|
1404
|
+
operations.push(this.runBestEffortNativeOperation('disconnect: device cancel connection', () => transport.device.cancelConnection()));
|
|
1405
|
+
}
|
|
1406
|
+
yield Promise.all(operations);
|
|
1407
|
+
}));
|
|
1400
1408
|
yield new Promise(resolve => setTimeout(() => resolve(), 100));
|
|
1401
1409
|
});
|
|
1402
1410
|
}
|
|
1411
|
+
runNativeTeardown(uuid, manager, teardown) {
|
|
1412
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1413
|
+
let timer;
|
|
1414
|
+
let timedOut = false;
|
|
1415
|
+
const pending = Promise.resolve()
|
|
1416
|
+
.then(teardown)
|
|
1417
|
+
.catch(error => {
|
|
1418
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('BLE native teardown error (ignored): ', (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
1419
|
+
});
|
|
1420
|
+
try {
|
|
1421
|
+
yield Promise.race([
|
|
1422
|
+
pending,
|
|
1423
|
+
new Promise(resolve => {
|
|
1424
|
+
timer = setTimeout(() => {
|
|
1425
|
+
timedOut = true;
|
|
1426
|
+
resolve();
|
|
1427
|
+
}, BLE_NATIVE_TEARDOWN_TIMEOUT_MS);
|
|
1428
|
+
}),
|
|
1429
|
+
]);
|
|
1430
|
+
}
|
|
1431
|
+
finally {
|
|
1432
|
+
if (timer)
|
|
1433
|
+
clearTimeout(timer);
|
|
1434
|
+
}
|
|
1435
|
+
if (timedOut) {
|
|
1436
|
+
Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE native teardown timed out:', uuid);
|
|
1437
|
+
if (this.blePlxManager === manager) {
|
|
1438
|
+
this.resetPlxManager();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
runBestEffortNativeOperation(label, operation) {
|
|
1444
|
+
return Promise.resolve()
|
|
1445
|
+
.then(operation)
|
|
1446
|
+
.catch(error => {
|
|
1447
|
+
Log === null || Log === void 0 ? void 0 : Log.debug(`${label} error (ignored): `, (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
runLifecycleOperation(uuid, operation) {
|
|
1451
|
+
var _a;
|
|
1452
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1453
|
+
const previousOperation = (_a = this.lifecycleOperations.get(uuid)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
1454
|
+
let completeOperation;
|
|
1455
|
+
const operationGate = new Promise(resolve => {
|
|
1456
|
+
completeOperation = resolve;
|
|
1457
|
+
});
|
|
1458
|
+
const operationTail = previousOperation.catch(() => undefined).then(() => operationGate);
|
|
1459
|
+
this.lifecycleOperations.set(uuid, operationTail);
|
|
1460
|
+
yield previousOperation.catch(() => undefined);
|
|
1461
|
+
try {
|
|
1462
|
+
return yield operation();
|
|
1463
|
+
}
|
|
1464
|
+
finally {
|
|
1465
|
+
completeOperation();
|
|
1466
|
+
if (this.lifecycleOperations.get(uuid) === operationTail) {
|
|
1467
|
+
this.lifecycleOperations.delete(uuid);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1403
1472
|
cancel() {
|
|
1404
1473
|
Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
|
|
1405
1474
|
if (this.runPromise) ;
|
|
@@ -1558,8 +1627,34 @@ class ReactNativeBleTransport {
|
|
|
1558
1627
|
resetPlxManager() {
|
|
1559
1628
|
const manager = this.blePlxManager;
|
|
1560
1629
|
this.blePlxManager = undefined;
|
|
1561
|
-
|
|
1562
|
-
|
|
1630
|
+
const reason = 'React Native BLE manager reset';
|
|
1631
|
+
Object.entries(transportCache).forEach(([uuid, cachedTransport]) => {
|
|
1632
|
+
var _a, _b, _c, _d;
|
|
1633
|
+
try {
|
|
1634
|
+
(_a = cachedTransport.disconnectSubscription) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1635
|
+
}
|
|
1636
|
+
catch (error) {
|
|
1637
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect subscription removal failed:', error);
|
|
1638
|
+
}
|
|
1639
|
+
cachedTransport.disconnectSubscription = undefined;
|
|
1640
|
+
try {
|
|
1641
|
+
(_b = cachedTransport.notifySubscription) === null || _b === void 0 ? void 0 : _b.remove();
|
|
1642
|
+
}
|
|
1643
|
+
catch (error) {
|
|
1644
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset notify subscription removal failed:', error);
|
|
1645
|
+
}
|
|
1646
|
+
cachedTransport.notifySubscription = undefined;
|
|
1647
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1648
|
+
try {
|
|
1649
|
+
this.emitDeviceDisconnect(uuid, (_c = cachedTransport.device) === null || _c === void 0 ? void 0 : _c.name, (_d = cachedTransport.monitorToken) !== null && _d !== void 0 ? _d : this.monitorTokens.get(uuid));
|
|
1650
|
+
}
|
|
1651
|
+
catch (error) {
|
|
1652
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('BLE manager reset disconnect event failed:', error);
|
|
1653
|
+
}
|
|
1654
|
+
delete transportCache[uuid];
|
|
1655
|
+
});
|
|
1656
|
+
this.protocolV2Links.invalidateAllLinks(reason).catch(error => {
|
|
1657
|
+
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager link invalidation failed:', error);
|
|
1563
1658
|
});
|
|
1564
1659
|
this.deviceProtocol.clear();
|
|
1565
1660
|
this.probingProtocols.clear();
|
|
@@ -2061,6 +2156,8 @@ class ReactNativeBleTransport {
|
|
|
2061
2156
|
}),
|
|
2062
2157
|
reset: (reason) => {
|
|
2063
2158
|
var _a;
|
|
2159
|
+
if (this.monitorTokens.get(uuid) !== generation)
|
|
2160
|
+
return;
|
|
2064
2161
|
(_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
2065
2162
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
2066
2163
|
},
|
|
@@ -2077,6 +2174,7 @@ class ReactNativeBleTransport {
|
|
|
2077
2174
|
exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
2078
2175
|
exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
|
|
2079
2176
|
exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
|
|
2177
|
+
exports.BLE_NATIVE_TEARDOWN_TIMEOUT_MS = BLE_NATIVE_TEARDOWN_TIMEOUT_MS;
|
|
2080
2178
|
exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
|
|
2081
2179
|
exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
|
|
2082
2180
|
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.135",
|
|
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.135",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.135",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.135",
|
|
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": "321e900381de2c1a9c583e90e8abfc2f8dab8266"
|
|
30
30
|
}
|
|
@@ -7,6 +7,7 @@ import transportPackage, {
|
|
|
7
7
|
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
8
8
|
|
|
9
9
|
import ReactNativeBleTransport, {
|
|
10
|
+
BLE_NATIVE_TEARDOWN_TIMEOUT_MS,
|
|
10
11
|
BLE_WRITE_PACKET_TIMEOUT_MS,
|
|
11
12
|
configureProtocolV2BleTuning,
|
|
12
13
|
getFirmwareUploadWriteRetryType,
|
|
@@ -174,6 +175,8 @@ const createHarness = ({
|
|
|
174
175
|
devices: jest.fn(() => Promise.resolve([device])),
|
|
175
176
|
connectedDevices: jest.fn(() => Promise.resolve([])),
|
|
176
177
|
cancelTransaction: jest.fn(() => Promise.resolve()),
|
|
178
|
+
cancelDeviceConnection: jest.fn(() => Promise.resolve()),
|
|
179
|
+
destroy: jest.fn(),
|
|
177
180
|
};
|
|
178
181
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
179
182
|
const emitter = new EventEmitter();
|
|
@@ -192,6 +195,7 @@ const createHarness = ({
|
|
|
192
195
|
logger,
|
|
193
196
|
uuid,
|
|
194
197
|
device,
|
|
198
|
+
bleManager,
|
|
195
199
|
sentSeqs,
|
|
196
200
|
writeCharacteristic,
|
|
197
201
|
setShouldRespond(value: boolean) {
|
|
@@ -420,6 +424,93 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
420
424
|
await transport.release(uuid, true);
|
|
421
425
|
});
|
|
422
426
|
|
|
427
|
+
test('waits for an in-flight release before reacquiring the same device', async () => {
|
|
428
|
+
const { transport, uuid } = createHarness();
|
|
429
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
430
|
+
const releaseGate = createDeferred<void>();
|
|
431
|
+
const releaseStarted = createDeferred<void>();
|
|
432
|
+
const { protocolV2Links } = transport as any;
|
|
433
|
+
const invalidateLink = protocolV2Links.invalidateLink.bind(protocolV2Links);
|
|
434
|
+
jest
|
|
435
|
+
.spyOn(protocolV2Links, 'invalidateLink')
|
|
436
|
+
.mockImplementationOnce(async (...args: unknown[]) => {
|
|
437
|
+
releaseStarted.resolve();
|
|
438
|
+
await releaseGate.promise;
|
|
439
|
+
return invalidateLink(...args);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const release = transport.release(uuid, true);
|
|
443
|
+
await releaseStarted.promise;
|
|
444
|
+
let reacquired = false;
|
|
445
|
+
const acquire = transport.acquire({ uuid, expectedProtocol: 'V2' }).then(result => {
|
|
446
|
+
reacquired = true;
|
|
447
|
+
return result;
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
await Promise.resolve();
|
|
451
|
+
expect(reacquired).toBe(false);
|
|
452
|
+
|
|
453
|
+
releaseGate.resolve();
|
|
454
|
+
await release;
|
|
455
|
+
await expect(acquire).resolves.toEqual({ uuid, protocolType: 'V2' });
|
|
456
|
+
await expect(transport.call(uuid, 'Ping', { message: 'after-release' })).resolves.toMatchObject(
|
|
457
|
+
{
|
|
458
|
+
type: 'Success',
|
|
459
|
+
message: { message: 'ok' },
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test(
|
|
465
|
+
'releases the lifecycle queue when native teardown never settles',
|
|
466
|
+
async () => {
|
|
467
|
+
const { transport, uuid, device, bleManager } = createHarness();
|
|
468
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
469
|
+
const otherUuid = 'rn-pro2-other-id';
|
|
470
|
+
const otherDevice = {
|
|
471
|
+
...device,
|
|
472
|
+
id: otherUuid,
|
|
473
|
+
name: 'OneKey Pro 2 Other',
|
|
474
|
+
localName: 'OneKey Pro 2 Other',
|
|
475
|
+
};
|
|
476
|
+
await (transport as any).installTransportForAcquire(otherUuid, otherDevice);
|
|
477
|
+
(transport as any).deviceProtocol.set(otherUuid, 'V2');
|
|
478
|
+
const disconnectEvents: Array<{ connectId: string }> = [];
|
|
479
|
+
transport.emitter?.on(TRANSPORT_EVENT.DEVICE_DISCONNECT, event => {
|
|
480
|
+
disconnectEvents.push(event);
|
|
481
|
+
});
|
|
482
|
+
const stalledNativeCleanup = createDeferred<void>();
|
|
483
|
+
bleManager.cancelTransaction
|
|
484
|
+
.mockImplementationOnce(() => stalledNativeCleanup.promise)
|
|
485
|
+
.mockResolvedValue(undefined);
|
|
486
|
+
const resetPlxManager = jest.spyOn(transport as any, 'resetPlxManager');
|
|
487
|
+
const nextLifecycleOperation = jest.fn().mockResolvedValue('next-operation');
|
|
488
|
+
|
|
489
|
+
const release = transport.release(uuid, true);
|
|
490
|
+
const nextOperation = (transport as any).runLifecycleOperation(uuid, nextLifecycleOperation);
|
|
491
|
+
|
|
492
|
+
await expect(release).resolves.toBe(true);
|
|
493
|
+
await expect(nextOperation).resolves.toBe('next-operation');
|
|
494
|
+
expect(resetPlxManager).toHaveBeenCalledTimes(1);
|
|
495
|
+
expect(nextLifecycleOperation).toHaveBeenCalledTimes(1);
|
|
496
|
+
expect(disconnectEvents).toContainEqual(expect.objectContaining({ connectId: otherUuid }));
|
|
497
|
+
expect(() => (transport as any).getCachedTransport(otherUuid)).toThrow();
|
|
498
|
+
|
|
499
|
+
await (transport as any).installTransportForAcquire(uuid, device);
|
|
500
|
+
(transport as any).deviceProtocol.set(uuid, 'V2');
|
|
501
|
+
|
|
502
|
+
stalledNativeCleanup.resolve();
|
|
503
|
+
await Promise.resolve();
|
|
504
|
+
await expect(
|
|
505
|
+
transport.call(uuid, 'Ping', { message: 'after-stale-cleanup' })
|
|
506
|
+
).resolves.toMatchObject({
|
|
507
|
+
type: 'Success',
|
|
508
|
+
message: { message: 'ok' },
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
BLE_NATIVE_TEARDOWN_TIMEOUT_MS + 5_000
|
|
512
|
+
);
|
|
513
|
+
|
|
423
514
|
test('falls back to the other active probe on iOS when protocol metadata is absent', async () => {
|
|
424
515
|
const { transport, uuid } = createHarness({ deviceName: 'OneKey' });
|
|
425
516
|
const probeProtocolV1 = jest
|
package/src/index.ts
CHANGED
|
@@ -149,6 +149,7 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10_000;
|
|
|
149
149
|
* A healthy packet completes in milliseconds, so this only fires on a dead link.
|
|
150
150
|
*/
|
|
151
151
|
export const BLE_WRITE_PACKET_TIMEOUT_MS = 10_000;
|
|
152
|
+
export const BLE_NATIVE_TEARDOWN_TIMEOUT_MS = 3_000;
|
|
152
153
|
const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
|
|
153
154
|
const isWedgedWriteError = (error: unknown): boolean =>
|
|
154
155
|
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleWriteCharacteristicError &&
|
|
@@ -422,6 +423,9 @@ export default class ReactNativeBleTransport {
|
|
|
422
423
|
|
|
423
424
|
private nextMonitorToken = 1;
|
|
424
425
|
|
|
426
|
+
/** Serializes transport lifecycle changes for the same physical device. */
|
|
427
|
+
private lifecycleOperations: Map<string, Promise<void>> = new Map();
|
|
428
|
+
|
|
425
429
|
constructor(options: TransportOptions) {
|
|
426
430
|
this.scanTimeout = options.scanTimeout ?? DEVICE_SCAN_TIMEOUT_MS;
|
|
427
431
|
}
|
|
@@ -860,12 +864,18 @@ export default class ReactNativeBleTransport {
|
|
|
860
864
|
}
|
|
861
865
|
|
|
862
866
|
async acquire(input: BleAcquireInput) {
|
|
863
|
-
const { uuid
|
|
867
|
+
const { uuid } = input;
|
|
864
868
|
|
|
865
869
|
if (!uuid) {
|
|
866
870
|
throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
|
|
867
871
|
}
|
|
868
872
|
|
|
873
|
+
return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private async acquireUnlocked(input: BleAcquireInput) {
|
|
877
|
+
const { uuid, forceCleanRunPromise, expectedProtocol } = input;
|
|
878
|
+
|
|
869
879
|
const cachedTransport = transportCache[uuid];
|
|
870
880
|
if (cachedTransport) {
|
|
871
881
|
const cachedProtocol = this.deviceProtocol.get(uuid);
|
|
@@ -884,7 +894,7 @@ export default class ReactNativeBleTransport {
|
|
|
884
894
|
* connection, clean it up before creating a new transport instance.
|
|
885
895
|
*/
|
|
886
896
|
Log?.debug('transport not reusable, will release: ', uuid);
|
|
887
|
-
await this.
|
|
897
|
+
await this.releaseUnlocked(uuid, true);
|
|
888
898
|
}
|
|
889
899
|
|
|
890
900
|
let device: Device | null = null;
|
|
@@ -1010,7 +1020,7 @@ export default class ReactNativeBleTransport {
|
|
|
1010
1020
|
inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
|
|
1011
1021
|
|
|
1012
1022
|
// release transport before new transport instance
|
|
1013
|
-
await this.
|
|
1023
|
+
await this.releaseUnlocked(uuid, true);
|
|
1014
1024
|
if (protocolHint) {
|
|
1015
1025
|
this.deviceProtocolHints.set(uuid, protocolHint);
|
|
1016
1026
|
}
|
|
@@ -1036,7 +1046,7 @@ export default class ReactNativeBleTransport {
|
|
|
1036
1046
|
this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
|
|
1037
1047
|
return { uuid, protocolType };
|
|
1038
1048
|
} catch (error) {
|
|
1039
|
-
await this.
|
|
1049
|
+
await this.releaseUnlocked(uuid, true);
|
|
1040
1050
|
throw error;
|
|
1041
1051
|
}
|
|
1042
1052
|
}
|
|
@@ -1181,12 +1191,17 @@ export default class ReactNativeBleTransport {
|
|
|
1181
1191
|
}
|
|
1182
1192
|
|
|
1183
1193
|
async release(uuid: string, onclose = false) {
|
|
1194
|
+
return this.runLifecycleOperation(uuid, () => this.releaseUnlocked(uuid, onclose));
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
private async releaseUnlocked(uuid: string, onclose = false) {
|
|
1184
1198
|
await this.protocolV2Links.invalidateLink(uuid, 'React Native BLE transport released');
|
|
1185
1199
|
return this.releaseNative(uuid, onclose);
|
|
1186
1200
|
}
|
|
1187
1201
|
|
|
1188
1202
|
private async releaseNative(uuid: string, onclose = false) {
|
|
1189
1203
|
const transport = transportCache[uuid];
|
|
1204
|
+
const manager = this.blePlxManager;
|
|
1190
1205
|
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
1191
1206
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
1192
1207
|
this.runPromise.reject(error);
|
|
@@ -1203,8 +1218,6 @@ export default class ReactNativeBleTransport {
|
|
|
1203
1218
|
return Promise.resolve(true);
|
|
1204
1219
|
}
|
|
1205
1220
|
|
|
1206
|
-
await this.restoreAndroidConnectionPriority(uuid, transport);
|
|
1207
|
-
|
|
1208
1221
|
if (transport) {
|
|
1209
1222
|
if (this.monitorTokens.get(uuid) === transport.monitorToken) {
|
|
1210
1223
|
this.monitorTokens.delete(uuid);
|
|
@@ -1222,16 +1235,9 @@ export default class ReactNativeBleTransport {
|
|
|
1222
1235
|
);
|
|
1223
1236
|
transport.notifySubscription?.remove();
|
|
1224
1237
|
transport.notifySubscription = undefined;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
try {
|
|
1228
|
-
await this.blePlxManager?.cancelTransaction(transport.notifyTransactionId);
|
|
1229
|
-
} catch (e) {
|
|
1230
|
-
Log?.debug('release: cancel notify transaction error (ignored): ', e?.message || e);
|
|
1231
|
-
}
|
|
1238
|
+
if (transportCache[uuid] === transport) {
|
|
1239
|
+
delete transportCache[uuid];
|
|
1232
1240
|
}
|
|
1233
|
-
|
|
1234
|
-
delete transportCache[uuid];
|
|
1235
1241
|
}
|
|
1236
1242
|
|
|
1237
1243
|
this.protocolV2HighVolumeLogSignatures.delete(uuid);
|
|
@@ -1243,11 +1249,28 @@ export default class ReactNativeBleTransport {
|
|
|
1243
1249
|
this.protocolV2Assemblers.delete(uuid);
|
|
1244
1250
|
this.resetProtocolV2Frames(uuid);
|
|
1245
1251
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1252
|
+
await this.runNativeTeardown(uuid, manager, async () => {
|
|
1253
|
+
const operations: Promise<unknown>[] = [
|
|
1254
|
+
this.runBestEffortNativeOperation('release: restore connection priority', () =>
|
|
1255
|
+
this.restoreAndroidConnectionPriority(uuid, transport)
|
|
1256
|
+
),
|
|
1257
|
+
];
|
|
1258
|
+
if (transport?.notifyTransactionId && manager) {
|
|
1259
|
+
operations.push(
|
|
1260
|
+
this.runBestEffortNativeOperation('release: cancel notify transaction', () =>
|
|
1261
|
+
manager.cancelTransaction(transport.notifyTransactionId as string)
|
|
1262
|
+
)
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
if (manager) {
|
|
1266
|
+
operations.push(
|
|
1267
|
+
this.runBestEffortNativeOperation('release: cancel transaction', () =>
|
|
1268
|
+
manager.cancelTransaction(uuid)
|
|
1269
|
+
)
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
await Promise.all(operations);
|
|
1273
|
+
});
|
|
1251
1274
|
|
|
1252
1275
|
return Promise.resolve(true);
|
|
1253
1276
|
}
|
|
@@ -1552,8 +1575,13 @@ export default class ReactNativeBleTransport {
|
|
|
1552
1575
|
}
|
|
1553
1576
|
|
|
1554
1577
|
async disconnect(session: string) {
|
|
1578
|
+
return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
private async disconnectUnlocked(session: string) {
|
|
1555
1582
|
await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
|
|
1556
1583
|
const transport = transportCache[session];
|
|
1584
|
+
const manager = this.blePlxManager;
|
|
1557
1585
|
const monitorToken = transport?.monitorToken ?? this.monitorTokens.get(session);
|
|
1558
1586
|
|
|
1559
1587
|
// Clean up disconnect subscription first to prevent onDisconnected callback
|
|
@@ -1582,33 +1610,8 @@ export default class ReactNativeBleTransport {
|
|
|
1582
1610
|
}
|
|
1583
1611
|
}
|
|
1584
1612
|
|
|
1585
|
-
// cancel the ble transaction
|
|
1586
|
-
if (session) {
|
|
1587
|
-
try {
|
|
1588
|
-
await this.blePlxManager?.cancelTransaction(session);
|
|
1589
|
-
} catch (e) {
|
|
1590
|
-
Log?.debug('resetSession: cancel transaction error (ignored): ', e?.message || e);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
// disconnect the device via the device object
|
|
1595
|
-
if (transport?.device) {
|
|
1596
|
-
try {
|
|
1597
|
-
await transport.device.cancelConnection();
|
|
1598
|
-
} catch (e) {
|
|
1599
|
-
Log?.debug('resetSession: device.cancelConnection error (ignored): ', e?.message || e);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// disconnect the device via the ble manager
|
|
1604
|
-
try {
|
|
1605
|
-
await this.blePlxManager?.cancelDeviceConnection(session);
|
|
1606
|
-
} catch (e) {
|
|
1607
|
-
Log?.debug('resetSession: manager.cancelDeviceConnection error (ignored): ', e?.message || e);
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
1613
|
// clear the transport cache
|
|
1611
|
-
if (transportCache[session]) {
|
|
1614
|
+
if (!transport || transportCache[session] === transport) {
|
|
1612
1615
|
delete transportCache[session];
|
|
1613
1616
|
}
|
|
1614
1617
|
this.deviceProtocol.delete(session);
|
|
@@ -1628,10 +1631,97 @@ export default class ReactNativeBleTransport {
|
|
|
1628
1631
|
if (monitorToken !== undefined && this.monitorTokens.get(session) === monitorToken) {
|
|
1629
1632
|
this.monitorTokens.delete(session);
|
|
1630
1633
|
}
|
|
1634
|
+
|
|
1635
|
+
await this.runNativeTeardown(session, manager, async () => {
|
|
1636
|
+
const operations: Promise<unknown>[] = [];
|
|
1637
|
+
if (manager) {
|
|
1638
|
+
operations.push(
|
|
1639
|
+
this.runBestEffortNativeOperation('disconnect: cancel transaction', () =>
|
|
1640
|
+
manager.cancelTransaction(session)
|
|
1641
|
+
)
|
|
1642
|
+
);
|
|
1643
|
+
operations.push(
|
|
1644
|
+
this.runBestEffortNativeOperation('disconnect: cancel device connection', () =>
|
|
1645
|
+
manager.cancelDeviceConnection(session)
|
|
1646
|
+
)
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
if (transport?.device) {
|
|
1650
|
+
operations.push(
|
|
1651
|
+
this.runBestEffortNativeOperation('disconnect: device cancel connection', () =>
|
|
1652
|
+
transport.device.cancelConnection()
|
|
1653
|
+
)
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
await Promise.all(operations);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1631
1659
|
// eslint-disable-next-line no-promise-executor-return
|
|
1632
1660
|
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
|
|
1633
1661
|
}
|
|
1634
1662
|
|
|
1663
|
+
private async runNativeTeardown(
|
|
1664
|
+
uuid: string,
|
|
1665
|
+
manager: BlePlxManager | undefined,
|
|
1666
|
+
teardown: () => Promise<void>
|
|
1667
|
+
) {
|
|
1668
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1669
|
+
let timedOut = false;
|
|
1670
|
+
const pending = Promise.resolve()
|
|
1671
|
+
.then(teardown)
|
|
1672
|
+
.catch(error => {
|
|
1673
|
+
Log?.debug('BLE native teardown error (ignored): ', error?.message || error);
|
|
1674
|
+
});
|
|
1675
|
+
try {
|
|
1676
|
+
await Promise.race([
|
|
1677
|
+
pending,
|
|
1678
|
+
new Promise<void>(resolve => {
|
|
1679
|
+
timer = setTimeout(() => {
|
|
1680
|
+
timedOut = true;
|
|
1681
|
+
resolve();
|
|
1682
|
+
}, BLE_NATIVE_TEARDOWN_TIMEOUT_MS);
|
|
1683
|
+
}),
|
|
1684
|
+
]);
|
|
1685
|
+
} finally {
|
|
1686
|
+
if (timer) clearTimeout(timer);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
if (timedOut) {
|
|
1690
|
+
Log?.error('[ReactNativeBleTransport] BLE native teardown timed out:', uuid);
|
|
1691
|
+
if (this.blePlxManager === manager) {
|
|
1692
|
+
this.resetPlxManager();
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
private runBestEffortNativeOperation(label: string, operation: () => Promise<unknown>) {
|
|
1698
|
+
return Promise.resolve()
|
|
1699
|
+
.then(operation)
|
|
1700
|
+
.catch(error => {
|
|
1701
|
+
Log?.debug(`${label} error (ignored): `, error?.message || error);
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
private async runLifecycleOperation<T>(uuid: string, operation: () => Promise<T>): Promise<T> {
|
|
1706
|
+
const previousOperation = this.lifecycleOperations.get(uuid) ?? Promise.resolve();
|
|
1707
|
+
let completeOperation!: () => void;
|
|
1708
|
+
const operationGate = new Promise<void>(resolve => {
|
|
1709
|
+
completeOperation = resolve;
|
|
1710
|
+
});
|
|
1711
|
+
const operationTail = previousOperation.catch(() => undefined).then(() => operationGate);
|
|
1712
|
+
this.lifecycleOperations.set(uuid, operationTail);
|
|
1713
|
+
|
|
1714
|
+
await previousOperation.catch(() => undefined);
|
|
1715
|
+
try {
|
|
1716
|
+
return await operation();
|
|
1717
|
+
} finally {
|
|
1718
|
+
completeOperation();
|
|
1719
|
+
if (this.lifecycleOperations.get(uuid) === operationTail) {
|
|
1720
|
+
this.lifecycleOperations.delete(uuid);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1635
1725
|
cancel() {
|
|
1636
1726
|
Log?.debug('transport-react-native transport cancel');
|
|
1637
1727
|
if (this.runPromise) {
|
|
@@ -1840,9 +1930,37 @@ export default class ReactNativeBleTransport {
|
|
|
1840
1930
|
private resetPlxManager() {
|
|
1841
1931
|
const manager = this.blePlxManager;
|
|
1842
1932
|
this.blePlxManager = undefined;
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1933
|
+
const reason = 'React Native BLE manager reset';
|
|
1934
|
+
// Destroying the shared manager invalidates every peripheral it owns. Notify
|
|
1935
|
+
// each cached session before clearing generations so Core cannot retain a
|
|
1936
|
+
// silently stale connection for an unrelated device.
|
|
1937
|
+
Object.entries(transportCache).forEach(([uuid, cachedTransport]) => {
|
|
1938
|
+
try {
|
|
1939
|
+
cachedTransport.disconnectSubscription?.remove();
|
|
1940
|
+
} catch (error) {
|
|
1941
|
+
Log?.debug('BLE manager reset disconnect subscription removal failed:', error);
|
|
1942
|
+
}
|
|
1943
|
+
cachedTransport.disconnectSubscription = undefined;
|
|
1944
|
+
try {
|
|
1945
|
+
cachedTransport.notifySubscription?.remove();
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
Log?.debug('BLE manager reset notify subscription removal failed:', error);
|
|
1948
|
+
}
|
|
1949
|
+
cachedTransport.notifySubscription = undefined;
|
|
1950
|
+
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
1951
|
+
try {
|
|
1952
|
+
this.emitDeviceDisconnect(
|
|
1953
|
+
uuid,
|
|
1954
|
+
cachedTransport.device?.name,
|
|
1955
|
+
cachedTransport.monitorToken ?? this.monitorTokens.get(uuid)
|
|
1956
|
+
);
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
Log?.debug('BLE manager reset disconnect event failed:', error);
|
|
1959
|
+
}
|
|
1960
|
+
delete transportCache[uuid];
|
|
1961
|
+
});
|
|
1962
|
+
this.protocolV2Links.invalidateAllLinks(reason).catch(error => {
|
|
1963
|
+
Log?.debug('[ReactNativeBleTransport] BLE manager link invalidation failed:', error);
|
|
1846
1964
|
});
|
|
1847
1965
|
this.deviceProtocol.clear();
|
|
1848
1966
|
this.probingProtocols.clear();
|
|
@@ -2444,6 +2562,7 @@ export default class ReactNativeBleTransport {
|
|
|
2444
2562
|
return rxFrame;
|
|
2445
2563
|
},
|
|
2446
2564
|
reset: (reason: string) => {
|
|
2565
|
+
if (this.monitorTokens.get(uuid) !== generation) return;
|
|
2447
2566
|
this.protocolV2Assemblers.get(uuid)?.reset();
|
|
2448
2567
|
this.rejectProtocolV2Frames(uuid, new Error(reason));
|
|
2449
2568
|
},
|