@onekeyfe/hd-transport-react-native 1.2.0-alpha.134 → 1.2.0-alpha.136

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 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 };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -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, forceCleanRunPromise, expectedProtocol } = input;
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.release(uuid, true);
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.release(uuid, true);
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.release(uuid, true);
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, _f, _g;
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.notifyTransactionId) {
1079
- try {
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
- (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
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
- try {
1095
- yield ((_g = this.blePlxManager) === null || _g === void 0 ? void 0 : _g.cancelTransaction(uuid));
1096
- }
1097
- catch (e) {
1098
- Log === null || Log === void 0 ? void 0 : Log.debug('release: cancel transaction error (ignored): ', (e === null || e === void 0 ? void 0 : e.message) || e);
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
- var _a, _b, _c, _d, _e;
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, (_e = transport === null || transport === void 0 ? void 0 : transport.device) === null || _e === void 0 ? void 0 : _e.name, monitorToken);
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
- Object.keys(transportCache).forEach(key => {
1562
- delete transportCache[key];
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.134",
3
+ "version": "1.2.0-alpha.136",
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.134",
24
- "@onekeyfe/hd-shared": "1.2.0-alpha.134",
25
- "@onekeyfe/hd-transport": "1.2.0-alpha.134",
23
+ "@onekeyfe/hd-core": "1.2.0-alpha.136",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.136",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.136",
26
26
  "@onekeyfe/react-native-ble-utils": "^0.1.6",
27
27
  "react-native-ble-plx": "3.5.1"
28
28
  },
29
- "gitHead": "47de2b0244584f6a541a84b0f94e4676bf63221a"
29
+ "gitHead": "92945bc4aae3d6602dd44c2e028e902e6bce7b55"
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, forceCleanRunPromise, expectedProtocol } = input;
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.release(uuid, true);
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.release(uuid, true);
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.release(uuid, true);
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
- if (transport.notifyTransactionId) {
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
- try {
1247
- await this.blePlxManager?.cancelTransaction(uuid);
1248
- } catch (e) {
1249
- Log?.debug('release: cancel transaction error (ignored): ', e?.message || e);
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
- // Every cached transport belongs to the destroyed manager's peripherals.
1844
- Object.keys(transportCache).forEach(key => {
1845
- delete transportCache[key];
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
  },