@onekeyfe/hd-transport-react-native 1.2.0-alpha.66 → 1.2.0-alpha.68

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.js CHANGED
@@ -275,6 +275,12 @@ const getFirmwareUploadWriteRetryType = (error) => {
275
275
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
276
276
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
277
277
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
278
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
279
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
280
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
281
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
282
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
283
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
278
284
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
279
285
  const IOS_NOTIFY_READY_DELAY_MS = 150;
280
286
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
@@ -310,11 +316,24 @@ function getDeviceDisplayName(device) {
310
316
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
311
317
  }
312
318
  const ANDROID_REQUEST_MTU = 256;
319
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
313
320
  const connectOptions = {
314
321
  requestMTU: ANDROID_REQUEST_MTU,
315
- timeout: 3000,
322
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
316
323
  refreshGatt: 'OnConnected',
317
324
  };
325
+ const fallbackConnectOptions = {
326
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
327
+ };
328
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
329
+ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
330
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
331
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
332
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
333
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
334
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
335
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
336
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
318
337
  const tryToGetConfiguration = (device) => {
319
338
  if (!device || !device.serviceUUIDs)
320
339
  return null;
@@ -372,7 +391,12 @@ class ReactNativeBleTransport {
372
391
  this.runPromiseDeviceId = null;
373
392
  this.firmwareUploadWriteRecoveryIds = new Set();
374
393
  this.deviceProtocol = new Map();
394
+ this.probingProtocols = new Map();
395
+ this.writeTimeoutCounts = new Map();
396
+ this.connectionSetupTimeoutCounts = new Map();
375
397
  this.deviceProtocolHints = new Map();
398
+ this.sessionProtocols = new Map();
399
+ this.protocolReprobeFailures = new Map();
376
400
  this.protocolV2Assemblers = new Map();
377
401
  this.protocolV2FrameQueues = new Map();
378
402
  this.protocolV2FramePromises = new Map();
@@ -564,19 +588,19 @@ class ReactNativeBleTransport {
564
588
  const isConnected = yield device.isConnected().catch(() => false);
565
589
  if (!isConnected) {
566
590
  try {
567
- device = yield device.connect(connectOptions);
591
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
568
592
  }
569
593
  catch (e) {
570
594
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
571
595
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
572
- device = yield device.connect();
596
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
573
597
  }
574
598
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
575
599
  throw e;
576
600
  }
577
601
  }
578
602
  }
579
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
603
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
580
604
  transport.device = device;
581
605
  transport.writeCharacteristic = writeCharacteristic;
582
606
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -713,7 +737,7 @@ class ReactNativeBleTransport {
713
737
  }
714
738
  installTransportForAcquire(uuid, device, characteristics) {
715
739
  return __awaiter(this, void 0, void 0, function* () {
716
- const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristics(device));
740
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
717
741
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
718
742
  if (reactNative.Platform.OS === 'android') {
719
743
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
@@ -796,14 +820,17 @@ class ReactNativeBleTransport {
796
820
  if (!device) {
797
821
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
798
822
  try {
799
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
823
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
800
824
  }
801
825
  catch (e) {
802
826
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
827
+ if (isConnectTimeoutError(e)) {
828
+ throw e;
829
+ }
803
830
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
804
831
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
805
832
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
806
- device = yield blePlxManager.connectToDevice(uuid);
833
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
807
834
  }
808
835
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
809
836
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -819,23 +846,27 @@ class ReactNativeBleTransport {
819
846
  }
820
847
  if (!(yield device.isConnected())) {
821
848
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
849
+ const disconnectedDevice = device;
822
850
  try {
823
- device = yield device.connect(connectOptions);
851
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
824
852
  }
825
853
  catch (e) {
826
854
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
855
+ if (isConnectTimeoutError(e)) {
856
+ throw e;
857
+ }
827
858
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
828
859
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
829
860
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
830
861
  try {
831
- device = yield device.connect();
862
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
832
863
  }
833
864
  catch (e) {
834
865
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
835
866
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
836
867
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
837
- yield device.cancelConnection();
838
- device = yield device.connect();
868
+ yield disconnectedDevice.cancelConnection();
869
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
839
870
  }
840
871
  }
841
872
  }
@@ -846,7 +877,7 @@ class ReactNativeBleTransport {
846
877
  }
847
878
  device = yield requestAndroidMtu(device);
848
879
  const acquiredDevice = device;
849
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(acquiredDevice);
880
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
850
881
  const protocolHint = expectedProtocol
851
882
  ? undefined
852
883
  : (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
@@ -891,7 +922,7 @@ class ReactNativeBleTransport {
891
922
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
892
923
  return;
893
924
  }
894
- if (this.deviceProtocol.get(uuid) === 'V2') {
925
+ if (this.getActiveProtocol(uuid) === 'V2') {
895
926
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
896
927
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
897
928
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -942,7 +973,7 @@ class ReactNativeBleTransport {
942
973
  }
943
974
  try {
944
975
  const data = buffer.Buffer.from(c.value, 'base64');
945
- const protocol = this.deviceProtocol.get(uuid);
976
+ const protocol = this.getActiveProtocol(uuid);
946
977
  if (!protocol) {
947
978
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
948
979
  return;
@@ -970,7 +1001,7 @@ class ReactNativeBleTransport {
970
1001
  catch (error) {
971
1002
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
972
1003
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
973
- if (this.deviceProtocol.get(uuid) === 'V2') {
1004
+ if (this.getActiveProtocol(uuid) === 'V2') {
974
1005
  this.rejectProtocolV2Frames(uuid, notifyError);
975
1006
  }
976
1007
  else if (this.runPromiseDeviceId === uuid) {
@@ -1026,6 +1057,7 @@ class ReactNativeBleTransport {
1026
1057
  delete transportCache[uuid];
1027
1058
  }
1028
1059
  this.deviceProtocol.delete(uuid);
1060
+ this.probingProtocols.delete(uuid);
1029
1061
  (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1030
1062
  this.protocolV2Assemblers.delete(uuid);
1031
1063
  this.resetProtocolV2Frames(uuid);
@@ -1074,8 +1106,19 @@ class ReactNativeBleTransport {
1074
1106
  const transport = this.getCachedTransport(uuid);
1075
1107
  const runPromise = hdShared.createDeferred();
1076
1108
  runPromise.promise.catch(() => undefined);
1109
+ const supersededRunPromise = this.runPromise;
1110
+ if (supersededRunPromise) {
1111
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1112
+ }
1077
1113
  this.runPromise = runPromise;
1078
1114
  this.runPromiseDeviceId = uuid;
1115
+ const releaseOwnershipIfCurrent = () => {
1116
+ if (this.runPromise === runPromise) {
1117
+ this.runPromise = null;
1118
+ this.runPromiseDeviceId = null;
1119
+ }
1120
+ };
1121
+ const isCurrentOwner = () => this.runPromise === runPromise;
1079
1122
  const messages = this._messages;
1080
1123
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1081
1124
  let timeout;
@@ -1096,6 +1139,9 @@ class ReactNativeBleTransport {
1096
1139
  }
1097
1140
  catch (e) {
1098
1141
  onError(e);
1142
+ if (isWedgedWriteError(e)) {
1143
+ throw e;
1144
+ }
1099
1145
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1100
1146
  }
1101
1147
  }
@@ -1123,6 +1169,9 @@ class ReactNativeBleTransport {
1123
1169
  }
1124
1170
  catch (e) {
1125
1171
  onError(e);
1172
+ if (isWedgedWriteError(e)) {
1173
+ throw e;
1174
+ }
1126
1175
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1127
1176
  }
1128
1177
  }
@@ -1133,8 +1182,8 @@ class ReactNativeBleTransport {
1133
1182
  });
1134
1183
  }
1135
1184
  if (name === 'EmmcFileWrite') {
1136
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1137
- this.runPromise = null;
1185
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1186
+ releaseOwnershipIfCurrent();
1138
1187
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1139
1188
  });
1140
1189
  }
@@ -1150,7 +1199,7 @@ class ReactNativeBleTransport {
1150
1199
  let attempt = 0;
1151
1200
  while (true) {
1152
1201
  try {
1153
- yield transport.writeWithRetry(data);
1202
+ yield this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner);
1154
1203
  return;
1155
1204
  }
1156
1205
  catch (error) {
@@ -1169,7 +1218,7 @@ class ReactNativeBleTransport {
1169
1218
  }
1170
1219
  }
1171
1220
  }), e => {
1172
- this.runPromise = null;
1221
+ releaseOwnershipIfCurrent();
1173
1222
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1174
1223
  });
1175
1224
  }
@@ -1178,16 +1227,16 @@ class ReactNativeBleTransport {
1178
1227
  const outData = o.toString('base64');
1179
1228
  try {
1180
1229
  const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1181
- if (shouldUseWriteWithResponse) {
1182
- yield transport.writeCharacteristic.writeWithResponse(outData);
1183
- }
1184
- else {
1185
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1186
- }
1230
+ yield this.writeBlePacket(uuid, outData, payload => shouldUseWriteWithResponse
1231
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1232
+ : transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1187
1233
  }
1188
1234
  catch (e) {
1189
1235
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1190
- this.runPromise = null;
1236
+ releaseOwnershipIfCurrent();
1237
+ if (isWedgedWriteError(e)) {
1238
+ throw e;
1239
+ }
1191
1240
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1192
1241
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1193
1242
  }
@@ -1227,7 +1276,9 @@ class ReactNativeBleTransport {
1227
1276
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1228
1277
  }
1229
1278
  const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1279
+ const isStaleCall = this.runPromise !== runPromise;
1230
1280
  if (!isProbeTimeout &&
1281
+ !isStaleCall &&
1231
1282
  (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1232
1283
  yield this.disconnect(uuid);
1233
1284
  }
@@ -1298,7 +1349,10 @@ class ReactNativeBleTransport {
1298
1349
  delete transportCache[session];
1299
1350
  }
1300
1351
  this.deviceProtocol.delete(session);
1352
+ this.probingProtocols.delete(session);
1301
1353
  this.deviceProtocolHints.delete(session);
1354
+ this.sessionProtocols.delete(session);
1355
+ this.protocolReprobeFailures.delete(session);
1302
1356
  this.protocolV2Assemblers.delete(session);
1303
1357
  this.resetProtocolV2Frames(session);
1304
1358
  try {
@@ -1319,6 +1373,91 @@ class ReactNativeBleTransport {
1319
1373
  this.runPromise = null;
1320
1374
  this.runPromiseDeviceId = null;
1321
1375
  }
1376
+ connectWithTimeout(uuid, connect) {
1377
+ return __awaiter(this, void 0, void 0, function* () {
1378
+ let timer;
1379
+ let timedOut = false;
1380
+ const pending = connect();
1381
+ pending.catch(() => undefined);
1382
+ try {
1383
+ const result = yield Promise.race([
1384
+ pending,
1385
+ new Promise((_, reject) => {
1386
+ timer = setTimeout(() => {
1387
+ timedOut = true;
1388
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1389
+ }, BLE_CONNECT_TIMEOUT_MS);
1390
+ }),
1391
+ ]);
1392
+ return result;
1393
+ }
1394
+ catch (error) {
1395
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1396
+ this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1397
+ }
1398
+ throw error;
1399
+ }
1400
+ finally {
1401
+ if (timer)
1402
+ clearTimeout(timer);
1403
+ }
1404
+ });
1405
+ }
1406
+ resolveCharacteristicsWithTimeout(uuid, device) {
1407
+ return __awaiter(this, void 0, void 0, function* () {
1408
+ let timer;
1409
+ let timedOut = false;
1410
+ const pending = this.resolveCharacteristics(device);
1411
+ pending.catch(() => undefined);
1412
+ try {
1413
+ const result = yield Promise.race([
1414
+ pending,
1415
+ new Promise((_, reject) => {
1416
+ timer = setTimeout(() => {
1417
+ timedOut = true;
1418
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1419
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1420
+ }),
1421
+ ]);
1422
+ this.connectionSetupTimeoutCounts.delete(uuid);
1423
+ return result;
1424
+ }
1425
+ catch (error) {
1426
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1427
+ this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1428
+ }
1429
+ throw error;
1430
+ }
1431
+ finally {
1432
+ if (timer)
1433
+ clearTimeout(timer);
1434
+ }
1435
+ });
1436
+ }
1437
+ abandonStalledConnection(uuid, stage) {
1438
+ var _a, _b;
1439
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1440
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1441
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1442
+ stage,
1443
+ setupTimeoutsSinceSuccess: timeouts,
1444
+ });
1445
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1446
+ });
1447
+ const stalled = transportCache[uuid];
1448
+ if (stalled) {
1449
+ delete transportCache[uuid];
1450
+ }
1451
+ this.deviceProtocol.delete(uuid);
1452
+ this.probingProtocols.delete(uuid);
1453
+ this.protocolV2Assemblers.delete(uuid);
1454
+ this.resetProtocolV2Frames(uuid);
1455
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1456
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1457
+ this.resetPlxManager();
1458
+ this.connectionSetupTimeoutCounts.delete(uuid);
1459
+ }
1460
+ }
1322
1461
  getCachedTransport(uuid) {
1323
1462
  const transport = transportCache[uuid];
1324
1463
  if (!transport) {
@@ -1326,6 +1465,82 @@ class ReactNativeBleTransport {
1326
1465
  }
1327
1466
  return transport;
1328
1467
  }
1468
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1469
+ return __awaiter(this, void 0, void 0, function* () {
1470
+ let timer;
1471
+ let timedOut = false;
1472
+ try {
1473
+ yield Promise.race([
1474
+ write(data),
1475
+ new Promise((_, reject) => {
1476
+ timer = setTimeout(() => {
1477
+ timedOut = true;
1478
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1479
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1480
+ }),
1481
+ ]);
1482
+ this.writeTimeoutCounts.delete(uuid);
1483
+ }
1484
+ catch (error) {
1485
+ if (timedOut) {
1486
+ if (isCurrentOwner && !isCurrentOwner()) {
1487
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1488
+ }
1489
+ else {
1490
+ this.tearDownWedgedLink(uuid);
1491
+ }
1492
+ }
1493
+ throw error;
1494
+ }
1495
+ finally {
1496
+ if (timer)
1497
+ clearTimeout(timer);
1498
+ }
1499
+ });
1500
+ }
1501
+ tearDownWedgedLink(uuid) {
1502
+ var _a;
1503
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1504
+ this.writeTimeoutCounts.set(uuid, timeouts);
1505
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1506
+ consecutiveWriteTimeouts: timeouts,
1507
+ });
1508
+ const wedged = transportCache[uuid];
1509
+ this.disconnect(uuid).catch(error => {
1510
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1511
+ });
1512
+ if (wedged && transportCache[uuid] === wedged) {
1513
+ delete transportCache[uuid];
1514
+ }
1515
+ this.deviceProtocol.delete(uuid);
1516
+ this.probingProtocols.delete(uuid);
1517
+ this.protocolV2Assemblers.delete(uuid);
1518
+ this.resetProtocolV2Frames(uuid);
1519
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1520
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1521
+ this.resetPlxManager();
1522
+ this.writeTimeoutCounts.delete(uuid);
1523
+ }
1524
+ }
1525
+ resetPlxManager() {
1526
+ const manager = this.blePlxManager;
1527
+ this.blePlxManager = undefined;
1528
+ Object.keys(transportCache).forEach(key => {
1529
+ delete transportCache[key];
1530
+ });
1531
+ this.deviceProtocol.clear();
1532
+ this.probingProtocols.clear();
1533
+ this.sessionProtocols.clear();
1534
+ this.protocolReprobeFailures.clear();
1535
+ this.monitorTokens.clear();
1536
+ this.protocolV2Assemblers.clear();
1537
+ try {
1538
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1539
+ }
1540
+ catch (error) {
1541
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1542
+ }
1543
+ }
1329
1544
  createProtocolMismatchError(expected) {
1330
1545
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1331
1546
  }
@@ -1333,11 +1548,19 @@ class ReactNativeBleTransport {
1333
1548
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1334
1549
  }
1335
1550
  clearProbeProtocol(uuid, protocol) {
1551
+ if (this.probingProtocols.get(uuid) === protocol) {
1552
+ this.probingProtocols.delete(uuid);
1553
+ }
1336
1554
  if (this.deviceProtocol.get(uuid) === protocol) {
1337
1555
  this.deviceProtocol.delete(uuid);
1338
1556
  }
1339
1557
  }
1558
+ getActiveProtocol(uuid) {
1559
+ var _a;
1560
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1561
+ }
1340
1562
  detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1563
+ var _a;
1341
1564
  return __awaiter(this, void 0, void 0, function* () {
1342
1565
  if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
1343
1566
  this.deviceProtocol.set(uuid, expectedProtocol);
@@ -1351,6 +1574,7 @@ class ReactNativeBleTransport {
1351
1574
  if (expectedProtocol === 'V1') {
1352
1575
  if (yield this.probeProtocolV1(uuid)) {
1353
1576
  this.deviceProtocol.set(uuid, 'V1');
1577
+ this.sessionProtocols.set(uuid, 'V1');
1354
1578
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1355
1579
  deviceId: uuid,
1356
1580
  protocol: 'V1',
@@ -1363,6 +1587,7 @@ class ReactNativeBleTransport {
1363
1587
  if (expectedProtocol === 'V2') {
1364
1588
  if (yield this.probeProtocolV2(uuid)) {
1365
1589
  this.deviceProtocol.set(uuid, 'V2');
1590
+ this.sessionProtocols.set(uuid, 'V2');
1366
1591
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1367
1592
  deviceId: uuid,
1368
1593
  protocol: 'V2',
@@ -1372,7 +1597,13 @@ class ReactNativeBleTransport {
1372
1597
  }
1373
1598
  throw this.createProtocolMismatchError(expectedProtocol);
1374
1599
  }
1375
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1600
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1601
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1602
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1603
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1604
+ !protocolHint &&
1605
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1606
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1376
1607
  for (let i = 0; i < probeOrder.length; i += 1) {
1377
1608
  const protocol = probeOrder[i];
1378
1609
  if (i > 0) {
@@ -1387,6 +1618,8 @@ class ReactNativeBleTransport {
1387
1618
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1388
1619
  if (detected) {
1389
1620
  this.deviceProtocol.set(uuid, protocol);
1621
+ this.sessionProtocols.set(uuid, protocol);
1622
+ this.protocolReprobeFailures.delete(uuid);
1390
1623
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1391
1624
  deviceId: uuid,
1392
1625
  protocol,
@@ -1395,7 +1628,14 @@ class ReactNativeBleTransport {
1395
1628
  return protocol;
1396
1629
  }
1397
1630
  }
1631
+ if (trustSessionProtocol) {
1632
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1633
+ }
1634
+ else {
1635
+ this.protocolReprobeFailures.delete(uuid);
1636
+ }
1398
1637
  this.deviceProtocol.delete(uuid);
1638
+ this.probingProtocols.delete(uuid);
1399
1639
  throw this.createProtocolDetectionError();
1400
1640
  });
1401
1641
  }
@@ -1447,13 +1687,17 @@ class ReactNativeBleTransport {
1447
1687
  return false;
1448
1688
  }
1449
1689
  try {
1450
- this.deviceProtocol.set(uuid, 'V1');
1690
+ this.probingProtocols.set(uuid, 'V1');
1451
1691
  yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1692
+ this.probingProtocols.delete(uuid);
1452
1693
  return true;
1453
1694
  }
1454
1695
  catch (error) {
1455
1696
  this.clearProbeProtocol(uuid, 'V1');
1456
1697
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1698
+ if (isWedgedWriteError(error)) {
1699
+ throw error;
1700
+ }
1457
1701
  return false;
1458
1702
  }
1459
1703
  });
@@ -1464,7 +1708,7 @@ class ReactNativeBleTransport {
1464
1708
  if (!this._messages || !this._messagesV2) {
1465
1709
  return false;
1466
1710
  }
1467
- this.deviceProtocol.set(uuid, 'V2');
1711
+ this.probingProtocols.set(uuid, 'V2');
1468
1712
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1469
1713
  const detected = yield transport.probeProtocolV2({
1470
1714
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1480,6 +1724,9 @@ class ReactNativeBleTransport {
1480
1724
  if (!detected) {
1481
1725
  this.clearProbeProtocol(uuid, 'V2');
1482
1726
  }
1727
+ else {
1728
+ this.probingProtocols.delete(uuid);
1729
+ }
1483
1730
  return detected;
1484
1731
  });
1485
1732
  }
@@ -1551,7 +1798,7 @@ class ReactNativeBleTransport {
1551
1798
  }
1552
1799
  });
1553
1800
  }
1554
- writeProtocolV2Packet(transport, base64, context, assertCurrentGeneration) {
1801
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1555
1802
  return __awaiter(this, void 0, void 0, function* () {
1556
1803
  const shouldUseWriteWithResponse = transport.writeCharacteristic.isWritableWithResponse &&
1557
1804
  (context.writeWithResponse === true || (reactNative.Platform.OS === 'ios' && !context.highVolume));
@@ -1562,12 +1809,17 @@ class ReactNativeBleTransport {
1562
1809
  throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1563
1810
  }
1564
1811
  try {
1565
- if (shouldUseWriteWithResponse) {
1566
- yield transport.writeCharacteristic.writeWithResponse(base64);
1567
- }
1568
- else {
1569
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1570
- }
1812
+ yield this.writeBlePacket(uuid, base64, payload => shouldUseWriteWithResponse
1813
+ ? transport.writeCharacteristic.writeWithResponse(payload)
1814
+ : transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1815
+ try {
1816
+ assertCurrentGeneration();
1817
+ return !context.signal.aborted;
1818
+ }
1819
+ catch (_a) {
1820
+ return false;
1821
+ }
1822
+ });
1571
1823
  assertCurrentGeneration();
1572
1824
  return;
1573
1825
  }
@@ -1588,7 +1840,7 @@ class ReactNativeBleTransport {
1588
1840
  }
1589
1841
  });
1590
1842
  }
1591
- writeProtocolV2Frame(transport$1, frame, context, assertCurrentGeneration) {
1843
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1592
1844
  return __awaiter(this, void 0, void 0, function* () {
1593
1845
  const tuning = getProtocolV2BleTuning();
1594
1846
  const packetCapacity = resolveProtocolV2PacketCapacity({
@@ -1611,7 +1863,7 @@ class ReactNativeBleTransport {
1611
1863
  burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
1612
1864
  flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
1613
1865
  wait: delay,
1614
- writePacket: packet => this.writeProtocolV2Packet(transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1866
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1615
1867
  });
1616
1868
  });
1617
1869
  }
@@ -1660,7 +1912,7 @@ class ReactNativeBleTransport {
1660
1912
  writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1661
1913
  assertCurrentGeneration();
1662
1914
  const currentTransport = this.getCachedTransport(uuid);
1663
- yield this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
1915
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1664
1916
  }),
1665
1917
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1666
1918
  assertCurrentGeneration();
@@ -1681,10 +1933,16 @@ class ReactNativeBleTransport {
1681
1933
  };
1682
1934
  }
1683
1935
  getProtocolType(path) {
1684
- return this.deviceProtocol.get(path);
1936
+ return this.getActiveProtocol(path);
1685
1937
  }
1686
1938
  }
1687
1939
 
1940
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
1941
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
1942
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
1943
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
1944
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
1945
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1688
1946
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1689
1947
  exports["default"] = ReactNativeBleTransport;
1690
1948
  exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;
package/jest.config.js CHANGED
@@ -2,4 +2,9 @@ module.exports = {
2
2
  preset: '../../jest.config.js',
3
3
  testEnvironment: 'node',
4
4
  modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
5
+ moduleNameMapper: {
6
+ // The workspace symlinks resolve to prebuilt dist outputs that can lag behind src.
7
+ '^@onekeyfe/hd-transport$': '<rootDir>/../hd-transport/src/index.ts',
8
+ '^@onekeyfe/hd-shared$': '<rootDir>/../shared/src/index.ts',
9
+ },
5
10
  };