@onekeyfe/hd-transport-react-native 1.2.0-alpha.56 → 1.2.0-alpha.62

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
@@ -210,7 +210,7 @@ class BleTransport {
210
210
  }
211
211
  writeWithRetry(data) {
212
212
  return __awaiter(this, void 0, void 0, function* () {
213
- yield this.writeCharacteristic.writeWithResponse(data);
213
+ yield this.writeCharacteristic.writeWithoutResponse(data);
214
214
  });
215
215
  }
216
216
  }
@@ -222,7 +222,6 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
222
222
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
223
223
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
224
224
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
225
- const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
226
225
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
227
226
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
228
227
  const ANDROID_GATT_CONGESTED_STATUS = 143;
@@ -245,6 +244,12 @@ const getFirmwareUploadWriteRetryType = (error) => {
245
244
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
246
245
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
247
246
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 10000;
247
+ const BLE_WRITE_PACKET_TIMEOUT_MS = 10000;
248
+ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
249
+ const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
250
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
251
+ error.message.startsWith(WEDGED_WRITE_MESSAGE);
252
+ const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
248
253
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
249
254
  const IOS_NOTIFY_READY_DELAY_MS = 150;
250
255
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
@@ -280,11 +285,21 @@ function getDeviceDisplayName(device) {
280
285
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
281
286
  }
282
287
  const ANDROID_REQUEST_MTU = 256;
288
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
283
289
  const connectOptions = {
284
290
  requestMTU: ANDROID_REQUEST_MTU,
285
- timeout: 3000,
291
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
286
292
  refreshGatt: 'OnConnected',
287
293
  };
294
+ const fallbackConnectOptions = {
295
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
296
+ };
297
+ const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
298
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
299
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
300
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
301
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
302
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
288
303
  const tryToGetConfiguration = (device) => {
289
304
  if (!device || !device.serviceUUIDs)
290
305
  return null;
@@ -342,6 +357,9 @@ class ReactNativeBleTransport {
342
357
  this.runPromiseDeviceId = null;
343
358
  this.firmwareUploadWriteRecoveryIds = new Set();
344
359
  this.deviceProtocol = new Map();
360
+ this.probingProtocols = new Map();
361
+ this.writeTimeoutCounts = new Map();
362
+ this.connectTimeoutCounts = new Map();
345
363
  this.deviceProtocolHints = new Map();
346
364
  this.protocolV2Assemblers = new Map();
347
365
  this.protocolV2FrameQueues = new Map();
@@ -534,12 +552,12 @@ class ReactNativeBleTransport {
534
552
  const isConnected = yield device.isConnected().catch(() => false);
535
553
  if (!isConnected) {
536
554
  try {
537
- device = yield device.connect(connectOptions);
555
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
538
556
  }
539
557
  catch (e) {
540
558
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
541
559
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
542
- device = yield device.connect();
560
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
543
561
  }
544
562
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
545
563
  throw e;
@@ -615,17 +633,12 @@ class ReactNativeBleTransport {
615
633
  return;
616
634
  }
617
635
  const displayName = getDeviceDisplayName(device);
618
- const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
619
- const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.name) ||
620
- hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.localName);
621
- const isOneKey = !isUnnamedIOSPeripheral &&
622
- !isFindMyPeripheral &&
623
- hdShared.isOnekeyBluetoothDevice({
624
- id: device === null || device === void 0 ? void 0 : device.id,
625
- name: device === null || device === void 0 ? void 0 : device.name,
626
- localName: device === null || device === void 0 ? void 0 : device.localName,
627
- serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
628
- });
636
+ const isOneKey = hdShared.isOnekeyBluetoothDevice({
637
+ id: device === null || device === void 0 ? void 0 : device.id,
638
+ name: device === null || device === void 0 ? void 0 : device.name,
639
+ localName: device === null || device === void 0 ? void 0 : device.localName,
640
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
641
+ });
629
642
  if (isOneKey) {
630
643
  addDevice(device);
631
644
  }
@@ -643,15 +656,12 @@ class ReactNativeBleTransport {
643
656
  const localName = 'localName' in device && typeof device.localName === 'string'
644
657
  ? device.localName
645
658
  : null;
646
- const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device.name) ||
647
- hdShared.isPro2FindMyAdvertisementName(localName);
648
- if (!isFindMyPeripheral &&
649
- hdShared.isOnekeyBluetoothDevice({
650
- id: device.id,
651
- name: device.name,
652
- localName,
653
- serviceUuids: device.serviceUUIDs,
654
- })) {
659
+ if (hdShared.isOnekeyBluetoothDevice({
660
+ id: device.id,
661
+ name: device.name,
662
+ localName,
663
+ serviceUuids: device.serviceUUIDs,
664
+ })) {
655
665
  Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
656
666
  addDevice(device);
657
667
  }
@@ -766,14 +776,17 @@ class ReactNativeBleTransport {
766
776
  if (!device) {
767
777
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
768
778
  try {
769
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
779
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
770
780
  }
771
781
  catch (e) {
772
782
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
783
+ if (isConnectTimeoutError(e)) {
784
+ throw e;
785
+ }
773
786
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
774
787
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
775
788
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
776
- device = yield blePlxManager.connectToDevice(uuid);
789
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
777
790
  }
778
791
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
779
792
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -789,23 +802,27 @@ class ReactNativeBleTransport {
789
802
  }
790
803
  if (!(yield device.isConnected())) {
791
804
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
805
+ const disconnectedDevice = device;
792
806
  try {
793
- device = yield device.connect(connectOptions);
807
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
794
808
  }
795
809
  catch (e) {
796
810
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
811
+ if (isConnectTimeoutError(e)) {
812
+ throw e;
813
+ }
797
814
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
798
815
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
799
816
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
800
817
  try {
801
- device = yield device.connect();
818
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
802
819
  }
803
820
  catch (e) {
804
821
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
805
822
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
806
823
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
807
- yield device.cancelConnection();
808
- device = yield device.connect();
824
+ yield disconnectedDevice.cancelConnection();
825
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
809
826
  }
810
827
  }
811
828
  }
@@ -861,7 +878,7 @@ class ReactNativeBleTransport {
861
878
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
862
879
  return;
863
880
  }
864
- if (this.deviceProtocol.get(uuid) === 'V2') {
881
+ if (this.getActiveProtocol(uuid) === 'V2') {
865
882
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
866
883
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
867
884
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -912,7 +929,7 @@ class ReactNativeBleTransport {
912
929
  }
913
930
  try {
914
931
  const data = buffer.Buffer.from(c.value, 'base64');
915
- const protocol = this.deviceProtocol.get(uuid);
932
+ const protocol = this.getActiveProtocol(uuid);
916
933
  if (!protocol) {
917
934
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
918
935
  return;
@@ -940,7 +957,7 @@ class ReactNativeBleTransport {
940
957
  catch (error) {
941
958
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
942
959
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
943
- if (this.deviceProtocol.get(uuid) === 'V2') {
960
+ if (this.getActiveProtocol(uuid) === 'V2') {
944
961
  this.rejectProtocolV2Frames(uuid, notifyError);
945
962
  }
946
963
  else if (this.runPromiseDeviceId === uuid) {
@@ -996,6 +1013,7 @@ class ReactNativeBleTransport {
996
1013
  delete transportCache[uuid];
997
1014
  }
998
1015
  this.deviceProtocol.delete(uuid);
1016
+ this.probingProtocols.delete(uuid);
999
1017
  (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1000
1018
  this.protocolV2Assemblers.delete(uuid);
1001
1019
  this.resetProtocolV2Frames(uuid);
@@ -1044,8 +1062,19 @@ class ReactNativeBleTransport {
1044
1062
  const transport = this.getCachedTransport(uuid);
1045
1063
  const runPromise = hdShared.createDeferred();
1046
1064
  runPromise.promise.catch(() => undefined);
1065
+ const supersededRunPromise = this.runPromise;
1066
+ if (supersededRunPromise) {
1067
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1068
+ }
1047
1069
  this.runPromise = runPromise;
1048
1070
  this.runPromiseDeviceId = uuid;
1071
+ const releaseOwnershipIfCurrent = () => {
1072
+ if (this.runPromise === runPromise) {
1073
+ this.runPromise = null;
1074
+ this.runPromiseDeviceId = null;
1075
+ }
1076
+ };
1077
+ const isCurrentOwner = () => this.runPromise === runPromise;
1049
1078
  const messages = this._messages;
1050
1079
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1051
1080
  let timeout;
@@ -1066,6 +1095,9 @@ class ReactNativeBleTransport {
1066
1095
  }
1067
1096
  catch (e) {
1068
1097
  onError(e);
1098
+ if (isWedgedWriteError(e)) {
1099
+ throw e;
1100
+ }
1069
1101
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1070
1102
  }
1071
1103
  }
@@ -1093,6 +1125,9 @@ class ReactNativeBleTransport {
1093
1125
  }
1094
1126
  catch (e) {
1095
1127
  onError(e);
1128
+ if (isWedgedWriteError(e)) {
1129
+ throw e;
1130
+ }
1096
1131
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1097
1132
  }
1098
1133
  }
@@ -1103,8 +1138,8 @@ class ReactNativeBleTransport {
1103
1138
  });
1104
1139
  }
1105
1140
  if (name === 'EmmcFileWrite') {
1106
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1107
- this.runPromise = null;
1141
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1142
+ releaseOwnershipIfCurrent();
1108
1143
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1109
1144
  });
1110
1145
  }
@@ -1120,7 +1155,7 @@ class ReactNativeBleTransport {
1120
1155
  let attempt = 0;
1121
1156
  while (true) {
1122
1157
  try {
1123
- yield transport.writeCharacteristic.writeWithResponse(data);
1158
+ yield this.writeBlePacket(uuid, data, payload => transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1124
1159
  return;
1125
1160
  }
1126
1161
  catch (error) {
@@ -1139,7 +1174,7 @@ class ReactNativeBleTransport {
1139
1174
  }
1140
1175
  }
1141
1176
  }), e => {
1142
- this.runPromise = null;
1177
+ releaseOwnershipIfCurrent();
1143
1178
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1144
1179
  });
1145
1180
  }
@@ -1147,17 +1182,14 @@ class ReactNativeBleTransport {
1147
1182
  for (const o of buffers) {
1148
1183
  const outData = o.toString('base64');
1149
1184
  try {
1150
- const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
1151
- if (shouldUseWriteWithResponse) {
1152
- yield transport.writeCharacteristic.writeWithResponse(outData);
1153
- }
1154
- else {
1155
- yield transport.writeCharacteristic.writeWithoutResponse(outData);
1156
- }
1185
+ yield this.writeBlePacket(uuid, outData, payload => transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1157
1186
  }
1158
1187
  catch (e) {
1159
1188
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1160
- this.runPromise = null;
1189
+ releaseOwnershipIfCurrent();
1190
+ if (isWedgedWriteError(e)) {
1191
+ throw e;
1192
+ }
1161
1193
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1162
1194
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1163
1195
  }
@@ -1197,7 +1229,9 @@ class ReactNativeBleTransport {
1197
1229
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1198
1230
  }
1199
1231
  const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1232
+ const isStaleCall = this.runPromise !== runPromise;
1200
1233
  if (!isProbeTimeout &&
1234
+ !isStaleCall &&
1201
1235
  (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1202
1236
  yield this.disconnect(uuid);
1203
1237
  }
@@ -1268,6 +1302,7 @@ class ReactNativeBleTransport {
1268
1302
  delete transportCache[session];
1269
1303
  }
1270
1304
  this.deviceProtocol.delete(session);
1305
+ this.probingProtocols.delete(session);
1271
1306
  this.deviceProtocolHints.delete(session);
1272
1307
  this.protocolV2Assemblers.delete(session);
1273
1308
  this.resetProtocolV2Frames(session);
@@ -1289,6 +1324,60 @@ class ReactNativeBleTransport {
1289
1324
  this.runPromise = null;
1290
1325
  this.runPromiseDeviceId = null;
1291
1326
  }
1327
+ connectWithTimeout(uuid, connect) {
1328
+ return __awaiter(this, void 0, void 0, function* () {
1329
+ let timer;
1330
+ let timedOut = false;
1331
+ const pending = connect();
1332
+ pending.catch(() => undefined);
1333
+ try {
1334
+ const result = yield Promise.race([
1335
+ pending,
1336
+ new Promise((_, reject) => {
1337
+ timer = setTimeout(() => {
1338
+ timedOut = true;
1339
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1340
+ }, BLE_CONNECT_TIMEOUT_MS);
1341
+ }),
1342
+ ]);
1343
+ this.connectTimeoutCounts.delete(uuid);
1344
+ return result;
1345
+ }
1346
+ catch (error) {
1347
+ if (timedOut) {
1348
+ this.abandonStalledConnect(uuid);
1349
+ }
1350
+ throw error;
1351
+ }
1352
+ finally {
1353
+ if (timer)
1354
+ clearTimeout(timer);
1355
+ }
1356
+ });
1357
+ }
1358
+ abandonStalledConnect(uuid) {
1359
+ var _a, _b;
1360
+ const timeouts = ((_a = this.connectTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1361
+ this.connectTimeoutCounts.set(uuid, timeouts);
1362
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE connect timed out:', uuid, {
1363
+ consecutiveConnectTimeouts: timeouts,
1364
+ });
1365
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1366
+ });
1367
+ const stalled = transportCache[uuid];
1368
+ if (stalled) {
1369
+ delete transportCache[uuid];
1370
+ }
1371
+ this.deviceProtocol.delete(uuid);
1372
+ this.probingProtocols.delete(uuid);
1373
+ this.protocolV2Assemblers.delete(uuid);
1374
+ this.resetProtocolV2Frames(uuid);
1375
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1376
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE connects wedged repeatedly, resetting BLE manager');
1377
+ this.resetPlxManager();
1378
+ this.connectTimeoutCounts.delete(uuid);
1379
+ }
1380
+ }
1292
1381
  getCachedTransport(uuid) {
1293
1382
  const transport = transportCache[uuid];
1294
1383
  if (!transport) {
@@ -1296,6 +1385,80 @@ class ReactNativeBleTransport {
1296
1385
  }
1297
1386
  return transport;
1298
1387
  }
1388
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1389
+ return __awaiter(this, void 0, void 0, function* () {
1390
+ let timer;
1391
+ let timedOut = false;
1392
+ try {
1393
+ yield Promise.race([
1394
+ write(data),
1395
+ new Promise((_, reject) => {
1396
+ timer = setTimeout(() => {
1397
+ timedOut = true;
1398
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1399
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1400
+ }),
1401
+ ]);
1402
+ this.writeTimeoutCounts.delete(uuid);
1403
+ }
1404
+ catch (error) {
1405
+ if (timedOut) {
1406
+ if (isCurrentOwner && !isCurrentOwner()) {
1407
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1408
+ }
1409
+ else {
1410
+ this.tearDownWedgedLink(uuid);
1411
+ }
1412
+ }
1413
+ throw error;
1414
+ }
1415
+ finally {
1416
+ if (timer)
1417
+ clearTimeout(timer);
1418
+ }
1419
+ });
1420
+ }
1421
+ tearDownWedgedLink(uuid) {
1422
+ var _a;
1423
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1424
+ this.writeTimeoutCounts.set(uuid, timeouts);
1425
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1426
+ consecutiveWriteTimeouts: timeouts,
1427
+ });
1428
+ const wedged = transportCache[uuid];
1429
+ this.disconnect(uuid).catch(error => {
1430
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1431
+ });
1432
+ if (wedged && transportCache[uuid] === wedged) {
1433
+ delete transportCache[uuid];
1434
+ }
1435
+ this.deviceProtocol.delete(uuid);
1436
+ this.probingProtocols.delete(uuid);
1437
+ this.protocolV2Assemblers.delete(uuid);
1438
+ this.resetProtocolV2Frames(uuid);
1439
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1440
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1441
+ this.resetPlxManager();
1442
+ this.writeTimeoutCounts.delete(uuid);
1443
+ }
1444
+ }
1445
+ resetPlxManager() {
1446
+ const manager = this.blePlxManager;
1447
+ this.blePlxManager = undefined;
1448
+ Object.keys(transportCache).forEach(key => {
1449
+ delete transportCache[key];
1450
+ });
1451
+ this.deviceProtocol.clear();
1452
+ this.probingProtocols.clear();
1453
+ this.monitorTokens.clear();
1454
+ this.protocolV2Assemblers.clear();
1455
+ try {
1456
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1457
+ }
1458
+ catch (error) {
1459
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1460
+ }
1461
+ }
1299
1462
  createProtocolMismatchError(expected) {
1300
1463
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1301
1464
  }
@@ -1303,30 +1466,19 @@ class ReactNativeBleTransport {
1303
1466
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1304
1467
  }
1305
1468
  clearProbeProtocol(uuid, protocol) {
1469
+ if (this.probingProtocols.get(uuid) === protocol) {
1470
+ this.probingProtocols.delete(uuid);
1471
+ }
1306
1472
  if (this.deviceProtocol.get(uuid) === protocol) {
1307
1473
  this.deviceProtocol.delete(uuid);
1308
1474
  }
1309
1475
  }
1310
- detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1476
+ getActiveProtocol(uuid) {
1311
1477
  var _a;
1478
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1479
+ }
1480
+ detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1312
1481
  return __awaiter(this, void 0, void 0, function* () {
1313
- if (reactNative.Platform.OS === 'ios') {
1314
- const protocol = (_a = expectedProtocol !== null && expectedProtocol !== void 0 ? expectedProtocol : protocolHint) !== null && _a !== void 0 ? _a : 'V1';
1315
- let source = 'ios-legacy-default';
1316
- if (expectedProtocol) {
1317
- source = 'expected';
1318
- }
1319
- else if (protocolHint) {
1320
- source = 'hint';
1321
- }
1322
- this.deviceProtocol.set(uuid, protocol);
1323
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1324
- deviceId: uuid,
1325
- protocol,
1326
- source,
1327
- });
1328
- return protocol;
1329
- }
1330
1482
  if (expectedProtocol === 'V1') {
1331
1483
  if (yield this.probeProtocolV1(uuid)) {
1332
1484
  this.deviceProtocol.set(uuid, 'V1');
@@ -1375,6 +1527,7 @@ class ReactNativeBleTransport {
1375
1527
  }
1376
1528
  }
1377
1529
  this.deviceProtocol.delete(uuid);
1530
+ this.probingProtocols.delete(uuid);
1378
1531
  throw this.createProtocolDetectionError();
1379
1532
  });
1380
1533
  }
@@ -1426,13 +1579,17 @@ class ReactNativeBleTransport {
1426
1579
  return false;
1427
1580
  }
1428
1581
  try {
1429
- this.deviceProtocol.set(uuid, 'V1');
1582
+ this.probingProtocols.set(uuid, 'V1');
1430
1583
  yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1584
+ this.probingProtocols.delete(uuid);
1431
1585
  return true;
1432
1586
  }
1433
1587
  catch (error) {
1434
1588
  this.clearProbeProtocol(uuid, 'V1');
1435
1589
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1590
+ if (isWedgedWriteError(error)) {
1591
+ throw error;
1592
+ }
1436
1593
  return false;
1437
1594
  }
1438
1595
  });
@@ -1443,7 +1600,7 @@ class ReactNativeBleTransport {
1443
1600
  if (!this._messages || !this._messagesV2) {
1444
1601
  return false;
1445
1602
  }
1446
- this.deviceProtocol.set(uuid, 'V2');
1603
+ this.probingProtocols.set(uuid, 'V2');
1447
1604
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1448
1605
  const detected = yield transport.probeProtocolV2({
1449
1606
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1459,6 +1616,9 @@ class ReactNativeBleTransport {
1459
1616
  if (!detected) {
1460
1617
  this.clearProbeProtocol(uuid, 'V2');
1461
1618
  }
1619
+ else {
1620
+ this.probingProtocols.delete(uuid);
1621
+ }
1462
1622
  return detected;
1463
1623
  });
1464
1624
  }
@@ -1530,10 +1690,8 @@ class ReactNativeBleTransport {
1530
1690
  }
1531
1691
  });
1532
1692
  }
1533
- writeProtocolV2Packet(transport, base64, context, assertCurrentGeneration) {
1693
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1534
1694
  return __awaiter(this, void 0, void 0, function* () {
1535
- const shouldUseWriteWithResponse = transport.writeCharacteristic.isWritableWithResponse &&
1536
- (context.writeWithResponse === true || (reactNative.Platform.OS === 'ios' && !context.highVolume));
1537
1695
  let attempt = 0;
1538
1696
  for (;;) {
1539
1697
  assertCurrentGeneration();
@@ -1541,12 +1699,15 @@ class ReactNativeBleTransport {
1541
1699
  throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1542
1700
  }
1543
1701
  try {
1544
- if (shouldUseWriteWithResponse) {
1545
- yield transport.writeCharacteristic.writeWithResponse(base64);
1546
- }
1547
- else {
1548
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1549
- }
1702
+ yield this.writeBlePacket(uuid, base64, payload => transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1703
+ try {
1704
+ assertCurrentGeneration();
1705
+ return !context.signal.aborted;
1706
+ }
1707
+ catch (_a) {
1708
+ return false;
1709
+ }
1710
+ });
1550
1711
  assertCurrentGeneration();
1551
1712
  return;
1552
1713
  }
@@ -1567,7 +1728,7 @@ class ReactNativeBleTransport {
1567
1728
  }
1568
1729
  });
1569
1730
  }
1570
- writeProtocolV2Frame(transport$1, frame, context, assertCurrentGeneration) {
1731
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1571
1732
  return __awaiter(this, void 0, void 0, function* () {
1572
1733
  const tuning = getProtocolV2BleTuning();
1573
1734
  const packetCapacity = resolveProtocolV2PacketCapacity({
@@ -1576,21 +1737,17 @@ class ReactNativeBleTransport {
1576
1737
  androidPacketLength: tuning.androidPacketLength,
1577
1738
  mtu: reactNative.Platform.OS === 'android' ? transport$1.mtuSize : undefined,
1578
1739
  });
1579
- const initialDelayMs = reactNative.Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
1580
- ? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
1581
- : 0;
1582
1740
  yield transport.writeProtocolV2BleFrame({
1583
1741
  frame,
1584
1742
  packetCapacity,
1585
1743
  assertActive: assertCurrentGeneration,
1586
1744
  signal: context.signal,
1587
1745
  abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1588
- initialDelayMs,
1589
1746
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1590
1747
  burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
1591
1748
  flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
1592
1749
  wait: delay,
1593
- writePacket: packet => this.writeProtocolV2Packet(transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1750
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1594
1751
  });
1595
1752
  });
1596
1753
  }
@@ -1605,7 +1762,7 @@ class ReactNativeBleTransport {
1605
1762
  const tuning = getProtocolV2BleTuning();
1606
1763
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1607
1764
  name,
1608
- writeMode: (options === null || options === void 0 ? void 0 : options.writeWithResponse) ? 'withResponse' : 'withoutResponse',
1765
+ writeMode: 'withoutResponse',
1609
1766
  packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1610
1767
  });
1611
1768
  }
@@ -1639,7 +1796,7 @@ class ReactNativeBleTransport {
1639
1796
  writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1640
1797
  assertCurrentGeneration();
1641
1798
  const currentTransport = this.getCachedTransport(uuid);
1642
- yield this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
1799
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1643
1800
  }),
1644
1801
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1645
1802
  assertCurrentGeneration();
@@ -1660,10 +1817,14 @@ class ReactNativeBleTransport {
1660
1817
  };
1661
1818
  }
1662
1819
  getProtocolType(path) {
1663
- return this.deviceProtocol.get(path);
1820
+ return this.getActiveProtocol(path);
1664
1821
  }
1665
1822
  }
1666
1823
 
1824
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
1825
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
1826
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
1827
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
1667
1828
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1668
1829
  exports["default"] = ReactNativeBleTransport;
1669
1830
  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
  };