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

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,10 +210,6 @@ class BleTransport {
210
210
  }
211
211
  writeWithRetry(data) {
212
212
  return __awaiter(this, void 0, void 0, function* () {
213
- if (reactNative.Platform.OS === 'ios' && this.writeCharacteristic.isWritableWithResponse) {
214
- yield this.writeCharacteristic.writeWithResponse(data);
215
- return;
216
- }
217
213
  yield this.writeCharacteristic.writeWithoutResponse(data);
218
214
  });
219
215
  }
@@ -226,36 +222,9 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
226
222
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
227
223
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
228
224
  const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
229
- const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
230
225
  const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
231
226
  const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
232
227
  const ANDROID_GATT_CONGESTED_STATUS = 143;
233
- const isAsciiWhitespace = (code) => code === 0x09 ||
234
- code === 0x0a ||
235
- code === 0x0b ||
236
- code === 0x0c ||
237
- code === 0x0d ||
238
- code === 0x20;
239
- const hasGattCongestedStatus = (text) => {
240
- let searchFrom = 0;
241
- while (searchFrom < text.length) {
242
- const statusIndex = text.indexOf('status', searchFrom);
243
- if (statusIndex < 0)
244
- return false;
245
- let cursor = statusIndex + 'status'.length;
246
- while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
247
- cursor += 1;
248
- if (text[cursor] === ':' || text[cursor] === '=') {
249
- cursor += 1;
250
- while (cursor < text.length && isAsciiWhitespace(text.charCodeAt(cursor)))
251
- cursor += 1;
252
- }
253
- if (text.startsWith(String(ANDROID_GATT_CONGESTED_STATUS), cursor))
254
- return true;
255
- searchFrom = statusIndex + 'status'.length;
256
- }
257
- return false;
258
- };
259
228
  const delay = (ms) => new Promise(resolve => {
260
229
  setTimeout(resolve, ms);
261
230
  });
@@ -270,11 +239,17 @@ const getFirmwareUploadWriteRetryType = (error) => {
270
239
  const text = [bleWriteError.reason, bleWriteError.message, bleWriteError.name]
271
240
  .filter(value => typeof value === 'string')
272
241
  .join(' ');
273
- return text.includes('GATT_CONGESTED') || hasGattCongestedStatus(text) ? 'congested' : null;
242
+ return /GATT_CONGESTED|status\s*[:=]?\s*143/.test(text) ? 'congested' : null;
274
243
  };
275
244
  const resolveFirmwareUploadRetryDelay = (attempt, baseDelayMs = 200, maxDelayMs = 1200) => Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
276
245
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
277
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;
278
253
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
279
254
  const IOS_NOTIFY_READY_DELAY_MS = 150;
280
255
  const ANDROID_NOTIFY_READY_DELAY_MS = 300;
@@ -310,11 +285,24 @@ function getDeviceDisplayName(device) {
310
285
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
311
286
  }
312
287
  const ANDROID_REQUEST_MTU = 256;
288
+ const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
313
289
  const connectOptions = {
314
290
  requestMTU: ANDROID_REQUEST_MTU,
315
- timeout: 3000,
291
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
316
292
  refreshGatt: 'OnConnected',
317
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_GATT_SETUP_TIMEOUT_MS = 10000;
299
+ const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
300
+ const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
301
+ const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
302
+ const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
303
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
304
+ error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
305
+ const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
318
306
  const tryToGetConfiguration = (device) => {
319
307
  if (!device || !device.serviceUUIDs)
320
308
  return null;
@@ -372,7 +360,12 @@ class ReactNativeBleTransport {
372
360
  this.runPromiseDeviceId = null;
373
361
  this.firmwareUploadWriteRecoveryIds = new Set();
374
362
  this.deviceProtocol = new Map();
363
+ this.probingProtocols = new Map();
364
+ this.writeTimeoutCounts = new Map();
365
+ this.connectionSetupTimeoutCounts = new Map();
375
366
  this.deviceProtocolHints = new Map();
367
+ this.sessionProtocols = new Map();
368
+ this.protocolReprobeFailures = new Map();
376
369
  this.protocolV2Assemblers = new Map();
377
370
  this.protocolV2FrameQueues = new Map();
378
371
  this.protocolV2FramePromises = new Map();
@@ -564,19 +557,19 @@ class ReactNativeBleTransport {
564
557
  const isConnected = yield device.isConnected().catch(() => false);
565
558
  if (!isConnected) {
566
559
  try {
567
- device = yield device.connect(connectOptions);
560
+ device = yield this.connectWithTimeout(uuid, () => device.connect(connectOptions));
568
561
  }
569
562
  catch (e) {
570
563
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
571
564
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
572
- device = yield device.connect();
565
+ device = yield this.connectWithTimeout(uuid, () => device.connect());
573
566
  }
574
567
  else if (e.errorCode !== reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
575
568
  throw e;
576
569
  }
577
570
  }
578
571
  }
579
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(device);
572
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, device);
580
573
  transport.device = device;
581
574
  transport.writeCharacteristic = writeCharacteristic;
582
575
  transport.notifyCharacteristic = notifyCharacteristic;
@@ -645,17 +638,12 @@ class ReactNativeBleTransport {
645
638
  return;
646
639
  }
647
640
  const displayName = getDeviceDisplayName(device);
648
- const isUnnamedIOSPeripheral = reactNative.Platform.OS === 'ios' && !(displayName === null || displayName === void 0 ? void 0 : displayName.trim());
649
- const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.name) ||
650
- hdShared.isPro2FindMyAdvertisementName(device === null || device === void 0 ? void 0 : device.localName);
651
- const isOneKey = !isUnnamedIOSPeripheral &&
652
- !isFindMyPeripheral &&
653
- hdShared.isOnekeyBluetoothDevice({
654
- id: device === null || device === void 0 ? void 0 : device.id,
655
- name: device === null || device === void 0 ? void 0 : device.name,
656
- localName: device === null || device === void 0 ? void 0 : device.localName,
657
- serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
658
- });
641
+ const isOneKey = hdShared.isOnekeyBluetoothDevice({
642
+ id: device === null || device === void 0 ? void 0 : device.id,
643
+ name: device === null || device === void 0 ? void 0 : device.name,
644
+ localName: device === null || device === void 0 ? void 0 : device.localName,
645
+ serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
646
+ });
659
647
  if (isOneKey) {
660
648
  addDevice(device);
661
649
  }
@@ -673,15 +661,12 @@ class ReactNativeBleTransport {
673
661
  const localName = 'localName' in device && typeof device.localName === 'string'
674
662
  ? device.localName
675
663
  : null;
676
- const isFindMyPeripheral = hdShared.isPro2FindMyAdvertisementName(device.name) ||
677
- hdShared.isPro2FindMyAdvertisementName(localName);
678
- if (!isFindMyPeripheral &&
679
- hdShared.isOnekeyBluetoothDevice({
680
- id: device.id,
681
- name: device.name,
682
- localName,
683
- serviceUuids: device.serviceUUIDs,
684
- })) {
664
+ if (hdShared.isOnekeyBluetoothDevice({
665
+ id: device.id,
666
+ name: device.name,
667
+ localName,
668
+ serviceUuids: device.serviceUUIDs,
669
+ })) {
685
670
  Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
686
671
  addDevice(device);
687
672
  }
@@ -713,7 +698,7 @@ class ReactNativeBleTransport {
713
698
  }
714
699
  installTransportForAcquire(uuid, device, characteristics) {
715
700
  return __awaiter(this, void 0, void 0, function* () {
716
- const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristics(device));
701
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
717
702
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
718
703
  if (reactNative.Platform.OS === 'android') {
719
704
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : transport$1.mtuSize;
@@ -796,14 +781,17 @@ class ReactNativeBleTransport {
796
781
  if (!device) {
797
782
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
798
783
  try {
799
- device = yield blePlxManager.connectToDevice(uuid, connectOptions);
784
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
800
785
  }
801
786
  catch (e) {
802
787
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
788
+ if (isConnectTimeoutError(e)) {
789
+ throw e;
790
+ }
803
791
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
804
792
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
805
793
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
806
- device = yield blePlxManager.connectToDevice(uuid);
794
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
807
795
  }
808
796
  else if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
809
797
  Log === null || Log === void 0 ? void 0 : Log.debug('device already connected');
@@ -819,23 +807,27 @@ class ReactNativeBleTransport {
819
807
  }
820
808
  if (!(yield device.isConnected())) {
821
809
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
810
+ const disconnectedDevice = device;
822
811
  try {
823
- device = yield device.connect(connectOptions);
812
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
824
813
  }
825
814
  catch (e) {
826
815
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
816
+ if (isConnectTimeoutError(e)) {
817
+ throw e;
818
+ }
827
819
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
828
820
  e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
829
821
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
830
822
  try {
831
- device = yield device.connect();
823
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
832
824
  }
833
825
  catch (e) {
834
826
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
835
827
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
836
828
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
837
- yield device.cancelConnection();
838
- device = yield device.connect();
829
+ yield disconnectedDevice.cancelConnection();
830
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
839
831
  }
840
832
  }
841
833
  }
@@ -846,7 +838,7 @@ class ReactNativeBleTransport {
846
838
  }
847
839
  device = yield requestAndroidMtu(device);
848
840
  const acquiredDevice = device;
849
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristics(acquiredDevice);
841
+ const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
850
842
  const protocolHint = expectedProtocol
851
843
  ? undefined
852
844
  : (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
@@ -891,7 +883,7 @@ class ReactNativeBleTransport {
891
883
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor error ignored for stale transport: ', uuid, notifyTransactionId);
892
884
  return;
893
885
  }
894
- if (this.deviceProtocol.get(uuid) === 'V2') {
886
+ if (this.getActiveProtocol(uuid) === 'V2') {
895
887
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
896
888
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
897
889
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
@@ -942,7 +934,7 @@ class ReactNativeBleTransport {
942
934
  }
943
935
  try {
944
936
  const data = buffer.Buffer.from(c.value, 'base64');
945
- const protocol = this.deviceProtocol.get(uuid);
937
+ const protocol = this.getActiveProtocol(uuid);
946
938
  if (!protocol) {
947
939
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data ignored before protocol detection: ', uuid);
948
940
  return;
@@ -970,7 +962,7 @@ class ReactNativeBleTransport {
970
962
  catch (error) {
971
963
  Log === null || Log === void 0 ? void 0 : Log.debug('monitor data error: ', error);
972
964
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
973
- if (this.deviceProtocol.get(uuid) === 'V2') {
965
+ if (this.getActiveProtocol(uuid) === 'V2') {
974
966
  this.rejectProtocolV2Frames(uuid, notifyError);
975
967
  }
976
968
  else if (this.runPromiseDeviceId === uuid) {
@@ -1026,6 +1018,7 @@ class ReactNativeBleTransport {
1026
1018
  delete transportCache[uuid];
1027
1019
  }
1028
1020
  this.deviceProtocol.delete(uuid);
1021
+ this.probingProtocols.delete(uuid);
1029
1022
  (_f = this.protocolV2Assemblers.get(uuid)) === null || _f === void 0 ? void 0 : _f.reset();
1030
1023
  this.protocolV2Assemblers.delete(uuid);
1031
1024
  this.resetProtocolV2Frames(uuid);
@@ -1074,8 +1067,19 @@ class ReactNativeBleTransport {
1074
1067
  const transport = this.getCachedTransport(uuid);
1075
1068
  const runPromise = hdShared.createDeferred();
1076
1069
  runPromise.promise.catch(() => undefined);
1070
+ const supersededRunPromise = this.runPromise;
1071
+ if (supersededRunPromise) {
1072
+ supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
1073
+ }
1077
1074
  this.runPromise = runPromise;
1078
1075
  this.runPromiseDeviceId = uuid;
1076
+ const releaseOwnershipIfCurrent = () => {
1077
+ if (this.runPromise === runPromise) {
1078
+ this.runPromise = null;
1079
+ this.runPromiseDeviceId = null;
1080
+ }
1081
+ };
1082
+ const isCurrentOwner = () => this.runPromise === runPromise;
1079
1083
  const messages = this._messages;
1080
1084
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1081
1085
  let timeout;
@@ -1096,6 +1100,9 @@ class ReactNativeBleTransport {
1096
1100
  }
1097
1101
  catch (e) {
1098
1102
  onError(e);
1103
+ if (isWedgedWriteError(e)) {
1104
+ throw e;
1105
+ }
1099
1106
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1100
1107
  }
1101
1108
  }
@@ -1123,6 +1130,9 @@ class ReactNativeBleTransport {
1123
1130
  }
1124
1131
  catch (e) {
1125
1132
  onError(e);
1133
+ if (isWedgedWriteError(e)) {
1134
+ throw e;
1135
+ }
1126
1136
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1127
1137
  }
1128
1138
  }
@@ -1133,8 +1143,8 @@ class ReactNativeBleTransport {
1133
1143
  });
1134
1144
  }
1135
1145
  if (name === 'EmmcFileWrite') {
1136
- yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
1137
- this.runPromise = null;
1146
+ yield writeChunkedData(buffers, data => this.writeBlePacket(uuid, data, payload => transport.writeWithRetry(payload), isCurrentOwner), e => {
1147
+ releaseOwnershipIfCurrent();
1138
1148
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1139
1149
  });
1140
1150
  }
@@ -1150,7 +1160,7 @@ class ReactNativeBleTransport {
1150
1160
  let attempt = 0;
1151
1161
  while (true) {
1152
1162
  try {
1153
- yield transport.writeWithRetry(data);
1163
+ yield this.writeBlePacket(uuid, data, payload => transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1154
1164
  return;
1155
1165
  }
1156
1166
  catch (error) {
@@ -1169,7 +1179,7 @@ class ReactNativeBleTransport {
1169
1179
  }
1170
1180
  }
1171
1181
  }), e => {
1172
- this.runPromise = null;
1182
+ releaseOwnershipIfCurrent();
1173
1183
  Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
1174
1184
  });
1175
1185
  }
@@ -1177,17 +1187,14 @@ class ReactNativeBleTransport {
1177
1187
  for (const o of buffers) {
1178
1188
  const outData = o.toString('base64');
1179
1189
  try {
1180
- 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
- }
1190
+ yield this.writeBlePacket(uuid, outData, payload => transport.writeCharacteristic.writeWithoutResponse(payload), isCurrentOwner);
1187
1191
  }
1188
1192
  catch (e) {
1189
1193
  Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
1190
- this.runPromise = null;
1194
+ releaseOwnershipIfCurrent();
1195
+ if (isWedgedWriteError(e)) {
1196
+ throw e;
1197
+ }
1191
1198
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
1192
1199
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
1193
1200
  }
@@ -1227,7 +1234,9 @@ class ReactNativeBleTransport {
1227
1234
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1228
1235
  }
1229
1236
  const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1237
+ const isStaleCall = this.runPromise !== runPromise;
1230
1238
  if (!isProbeTimeout &&
1239
+ !isStaleCall &&
1231
1240
  (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1232
1241
  yield this.disconnect(uuid);
1233
1242
  }
@@ -1298,7 +1307,10 @@ class ReactNativeBleTransport {
1298
1307
  delete transportCache[session];
1299
1308
  }
1300
1309
  this.deviceProtocol.delete(session);
1310
+ this.probingProtocols.delete(session);
1301
1311
  this.deviceProtocolHints.delete(session);
1312
+ this.sessionProtocols.delete(session);
1313
+ this.protocolReprobeFailures.delete(session);
1302
1314
  this.protocolV2Assemblers.delete(session);
1303
1315
  this.resetProtocolV2Frames(session);
1304
1316
  try {
@@ -1319,6 +1331,91 @@ class ReactNativeBleTransport {
1319
1331
  this.runPromise = null;
1320
1332
  this.runPromiseDeviceId = null;
1321
1333
  }
1334
+ connectWithTimeout(uuid, connect) {
1335
+ return __awaiter(this, void 0, void 0, function* () {
1336
+ let timer;
1337
+ let timedOut = false;
1338
+ const pending = connect();
1339
+ pending.catch(() => undefined);
1340
+ try {
1341
+ const result = yield Promise.race([
1342
+ pending,
1343
+ new Promise((_, reject) => {
1344
+ timer = setTimeout(() => {
1345
+ timedOut = true;
1346
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE connect timeout after ${BLE_CONNECT_TIMEOUT_MS}ms for ${uuid}`));
1347
+ }, BLE_CONNECT_TIMEOUT_MS);
1348
+ }),
1349
+ ]);
1350
+ return result;
1351
+ }
1352
+ catch (error) {
1353
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1354
+ this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1355
+ }
1356
+ throw error;
1357
+ }
1358
+ finally {
1359
+ if (timer)
1360
+ clearTimeout(timer);
1361
+ }
1362
+ });
1363
+ }
1364
+ resolveCharacteristicsWithTimeout(uuid, device) {
1365
+ return __awaiter(this, void 0, void 0, function* () {
1366
+ let timer;
1367
+ let timedOut = false;
1368
+ const pending = this.resolveCharacteristics(device);
1369
+ pending.catch(() => undefined);
1370
+ try {
1371
+ const result = yield Promise.race([
1372
+ pending,
1373
+ new Promise((_, reject) => {
1374
+ timer = setTimeout(() => {
1375
+ timedOut = true;
1376
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `BLE GATT setup timeout after ${BLE_GATT_SETUP_TIMEOUT_MS}ms for ${uuid}`));
1377
+ }, BLE_GATT_SETUP_TIMEOUT_MS);
1378
+ }),
1379
+ ]);
1380
+ this.connectionSetupTimeoutCounts.delete(uuid);
1381
+ return result;
1382
+ }
1383
+ catch (error) {
1384
+ if (timedOut || isNativeOperationTimeoutError(error)) {
1385
+ this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1386
+ }
1387
+ throw error;
1388
+ }
1389
+ finally {
1390
+ if (timer)
1391
+ clearTimeout(timer);
1392
+ }
1393
+ });
1394
+ }
1395
+ abandonStalledConnection(uuid, stage) {
1396
+ var _a, _b;
1397
+ const timeouts = ((_a = this.connectionSetupTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1398
+ this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1399
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
1400
+ stage,
1401
+ setupTimeoutsSinceSuccess: timeouts,
1402
+ });
1403
+ (_b = this.blePlxManager) === null || _b === void 0 ? void 0 : _b.cancelDeviceConnection(uuid).catch(() => {
1404
+ });
1405
+ const stalled = transportCache[uuid];
1406
+ if (stalled) {
1407
+ delete transportCache[uuid];
1408
+ }
1409
+ this.deviceProtocol.delete(uuid);
1410
+ this.probingProtocols.delete(uuid);
1411
+ this.protocolV2Assemblers.delete(uuid);
1412
+ this.resetProtocolV2Frames(uuid);
1413
+ if (timeouts >= BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1414
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1415
+ this.resetPlxManager();
1416
+ this.connectionSetupTimeoutCounts.delete(uuid);
1417
+ }
1418
+ }
1322
1419
  getCachedTransport(uuid) {
1323
1420
  const transport = transportCache[uuid];
1324
1421
  if (!transport) {
@@ -1326,6 +1423,82 @@ class ReactNativeBleTransport {
1326
1423
  }
1327
1424
  return transport;
1328
1425
  }
1426
+ writeBlePacket(uuid, data, write, isCurrentOwner) {
1427
+ return __awaiter(this, void 0, void 0, function* () {
1428
+ let timer;
1429
+ let timedOut = false;
1430
+ try {
1431
+ yield Promise.race([
1432
+ write(data),
1433
+ new Promise((_, reject) => {
1434
+ timer = setTimeout(() => {
1435
+ timedOut = true;
1436
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write timeout after ${BLE_WRITE_PACKET_TIMEOUT_MS}ms`));
1437
+ }, BLE_WRITE_PACKET_TIMEOUT_MS);
1438
+ }),
1439
+ ]);
1440
+ this.writeTimeoutCounts.delete(uuid);
1441
+ }
1442
+ catch (error) {
1443
+ if (timedOut) {
1444
+ if (isCurrentOwner && !isCurrentOwner()) {
1445
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] stale BLE write timed out, link kept:', uuid);
1446
+ }
1447
+ else {
1448
+ this.tearDownWedgedLink(uuid);
1449
+ }
1450
+ }
1451
+ throw error;
1452
+ }
1453
+ finally {
1454
+ if (timer)
1455
+ clearTimeout(timer);
1456
+ }
1457
+ });
1458
+ }
1459
+ tearDownWedgedLink(uuid) {
1460
+ var _a;
1461
+ const timeouts = ((_a = this.writeTimeoutCounts.get(uuid)) !== null && _a !== void 0 ? _a : 0) + 1;
1462
+ this.writeTimeoutCounts.set(uuid, timeouts);
1463
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE write timed out, tearing down link:', uuid, {
1464
+ consecutiveWriteTimeouts: timeouts,
1465
+ });
1466
+ const wedged = transportCache[uuid];
1467
+ this.disconnect(uuid).catch(error => {
1468
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] wedged link teardown failed (ignored):', error);
1469
+ });
1470
+ if (wedged && transportCache[uuid] === wedged) {
1471
+ delete transportCache[uuid];
1472
+ }
1473
+ this.deviceProtocol.delete(uuid);
1474
+ this.probingProtocols.delete(uuid);
1475
+ this.protocolV2Assemblers.delete(uuid);
1476
+ this.resetProtocolV2Frames(uuid);
1477
+ if (timeouts >= BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD) {
1478
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE writes wedged repeatedly, resetting BLE manager');
1479
+ this.resetPlxManager();
1480
+ this.writeTimeoutCounts.delete(uuid);
1481
+ }
1482
+ }
1483
+ resetPlxManager() {
1484
+ const manager = this.blePlxManager;
1485
+ this.blePlxManager = undefined;
1486
+ Object.keys(transportCache).forEach(key => {
1487
+ delete transportCache[key];
1488
+ });
1489
+ this.deviceProtocol.clear();
1490
+ this.probingProtocols.clear();
1491
+ this.sessionProtocols.clear();
1492
+ this.protocolReprobeFailures.clear();
1493
+ this.monitorTokens.clear();
1494
+ this.protocolV2Assemblers.clear();
1495
+ try {
1496
+ manager === null || manager === void 0 ? void 0 : manager.destroy();
1497
+ }
1498
+ catch (error) {
1499
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1500
+ }
1501
+ }
1329
1502
  createProtocolMismatchError(expected) {
1330
1503
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1331
1504
  }
@@ -1333,24 +1506,24 @@ class ReactNativeBleTransport {
1333
1506
  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
1507
  }
1335
1508
  clearProbeProtocol(uuid, protocol) {
1509
+ if (this.probingProtocols.get(uuid) === protocol) {
1510
+ this.probingProtocols.delete(uuid);
1511
+ }
1336
1512
  if (this.deviceProtocol.get(uuid) === protocol) {
1337
1513
  this.deviceProtocol.delete(uuid);
1338
1514
  }
1339
1515
  }
1516
+ getActiveProtocol(uuid) {
1517
+ var _a;
1518
+ return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1519
+ }
1340
1520
  detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1521
+ var _a;
1341
1522
  return __awaiter(this, void 0, void 0, function* () {
1342
- if (reactNative.Platform.OS === 'ios' && expectedProtocol) {
1343
- this.deviceProtocol.set(uuid, expectedProtocol);
1344
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1345
- deviceId: uuid,
1346
- protocol: expectedProtocol,
1347
- source: 'expected',
1348
- });
1349
- return expectedProtocol;
1350
- }
1351
1523
  if (expectedProtocol === 'V1') {
1352
1524
  if (yield this.probeProtocolV1(uuid)) {
1353
1525
  this.deviceProtocol.set(uuid, 'V1');
1526
+ this.sessionProtocols.set(uuid, 'V1');
1354
1527
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1355
1528
  deviceId: uuid,
1356
1529
  protocol: 'V1',
@@ -1363,6 +1536,7 @@ class ReactNativeBleTransport {
1363
1536
  if (expectedProtocol === 'V2') {
1364
1537
  if (yield this.probeProtocolV2(uuid)) {
1365
1538
  this.deviceProtocol.set(uuid, 'V2');
1539
+ this.sessionProtocols.set(uuid, 'V2');
1366
1540
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1367
1541
  deviceId: uuid,
1368
1542
  protocol: 'V2',
@@ -1372,7 +1546,13 @@ class ReactNativeBleTransport {
1372
1546
  }
1373
1547
  throw this.createProtocolMismatchError(expectedProtocol);
1374
1548
  }
1375
- const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1549
+ const sessionProtocol = this.sessionProtocols.get(uuid);
1550
+ const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1551
+ const fullProbeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1552
+ const trustSessionProtocol = sessionProtocol !== undefined &&
1553
+ !protocolHint &&
1554
+ reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1555
+ const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
1376
1556
  for (let i = 0; i < probeOrder.length; i += 1) {
1377
1557
  const protocol = probeOrder[i];
1378
1558
  if (i > 0) {
@@ -1387,6 +1567,8 @@ class ReactNativeBleTransport {
1387
1567
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1388
1568
  if (detected) {
1389
1569
  this.deviceProtocol.set(uuid, protocol);
1570
+ this.sessionProtocols.set(uuid, protocol);
1571
+ this.protocolReprobeFailures.delete(uuid);
1390
1572
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1391
1573
  deviceId: uuid,
1392
1574
  protocol,
@@ -1395,7 +1577,14 @@ class ReactNativeBleTransport {
1395
1577
  return protocol;
1396
1578
  }
1397
1579
  }
1580
+ if (trustSessionProtocol) {
1581
+ this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1582
+ }
1583
+ else {
1584
+ this.protocolReprobeFailures.delete(uuid);
1585
+ }
1398
1586
  this.deviceProtocol.delete(uuid);
1587
+ this.probingProtocols.delete(uuid);
1399
1588
  throw this.createProtocolDetectionError();
1400
1589
  });
1401
1590
  }
@@ -1447,13 +1636,17 @@ class ReactNativeBleTransport {
1447
1636
  return false;
1448
1637
  }
1449
1638
  try {
1450
- this.deviceProtocol.set(uuid, 'V1');
1639
+ this.probingProtocols.set(uuid, 'V1');
1451
1640
  yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1641
+ this.probingProtocols.delete(uuid);
1452
1642
  return true;
1453
1643
  }
1454
1644
  catch (error) {
1455
1645
  this.clearProbeProtocol(uuid, 'V1');
1456
1646
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1647
+ if (isWedgedWriteError(error)) {
1648
+ throw error;
1649
+ }
1457
1650
  return false;
1458
1651
  }
1459
1652
  });
@@ -1464,7 +1657,7 @@ class ReactNativeBleTransport {
1464
1657
  if (!this._messages || !this._messagesV2) {
1465
1658
  return false;
1466
1659
  }
1467
- this.deviceProtocol.set(uuid, 'V2');
1660
+ this.probingProtocols.set(uuid, 'V2');
1468
1661
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1469
1662
  const detected = yield transport.probeProtocolV2({
1470
1663
  call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
@@ -1480,6 +1673,9 @@ class ReactNativeBleTransport {
1480
1673
  if (!detected) {
1481
1674
  this.clearProbeProtocol(uuid, 'V2');
1482
1675
  }
1676
+ else {
1677
+ this.probingProtocols.delete(uuid);
1678
+ }
1483
1679
  return detected;
1484
1680
  });
1485
1681
  }
@@ -1551,10 +1747,8 @@ class ReactNativeBleTransport {
1551
1747
  }
1552
1748
  });
1553
1749
  }
1554
- writeProtocolV2Packet(transport, base64, context, assertCurrentGeneration) {
1750
+ writeProtocolV2Packet(uuid, transport, base64, context, assertCurrentGeneration) {
1555
1751
  return __awaiter(this, void 0, void 0, function* () {
1556
- const shouldUseWriteWithResponse = transport.writeCharacteristic.isWritableWithResponse &&
1557
- (context.writeWithResponse === true || (reactNative.Platform.OS === 'ios' && !context.highVolume));
1558
1752
  let attempt = 0;
1559
1753
  for (;;) {
1560
1754
  assertCurrentGeneration();
@@ -1562,12 +1756,15 @@ class ReactNativeBleTransport {
1562
1756
  throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
1563
1757
  }
1564
1758
  try {
1565
- if (shouldUseWriteWithResponse) {
1566
- yield transport.writeCharacteristic.writeWithResponse(base64);
1567
- }
1568
- else {
1569
- yield transport.writeCharacteristic.writeWithoutResponse(base64);
1570
- }
1759
+ yield this.writeBlePacket(uuid, base64, payload => transport.writeCharacteristic.writeWithoutResponse(payload), () => {
1760
+ try {
1761
+ assertCurrentGeneration();
1762
+ return !context.signal.aborted;
1763
+ }
1764
+ catch (_a) {
1765
+ return false;
1766
+ }
1767
+ });
1571
1768
  assertCurrentGeneration();
1572
1769
  return;
1573
1770
  }
@@ -1588,7 +1785,7 @@ class ReactNativeBleTransport {
1588
1785
  }
1589
1786
  });
1590
1787
  }
1591
- writeProtocolV2Frame(transport$1, frame, context, assertCurrentGeneration) {
1788
+ writeProtocolV2Frame(uuid, transport$1, frame, context, assertCurrentGeneration) {
1592
1789
  return __awaiter(this, void 0, void 0, function* () {
1593
1790
  const tuning = getProtocolV2BleTuning();
1594
1791
  const packetCapacity = resolveProtocolV2PacketCapacity({
@@ -1597,21 +1794,17 @@ class ReactNativeBleTransport {
1597
1794
  androidPacketLength: tuning.androidPacketLength,
1598
1795
  mtu: reactNative.Platform.OS === 'android' ? transport$1.mtuSize : undefined,
1599
1796
  });
1600
- const initialDelayMs = reactNative.Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
1601
- ? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
1602
- : 0;
1603
1797
  yield transport.writeProtocolV2BleFrame({
1604
1798
  frame,
1605
1799
  packetCapacity,
1606
1800
  assertActive: assertCurrentGeneration,
1607
1801
  signal: context.signal,
1608
1802
  abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
1609
- initialDelayMs,
1610
1803
  burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
1611
1804
  burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
1612
1805
  flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
1613
1806
  wait: delay,
1614
- writePacket: packet => this.writeProtocolV2Packet(transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1807
+ writePacket: packet => this.writeProtocolV2Packet(uuid, transport$1, buffer.Buffer.from(packet).toString('base64'), context, assertCurrentGeneration),
1615
1808
  });
1616
1809
  });
1617
1810
  }
@@ -1626,7 +1819,7 @@ class ReactNativeBleTransport {
1626
1819
  const tuning = getProtocolV2BleTuning();
1627
1820
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 high-volume write configured', {
1628
1821
  name,
1629
- writeMode: (options === null || options === void 0 ? void 0 : options.writeWithResponse) ? 'withResponse' : 'withoutResponse',
1822
+ writeMode: 'withoutResponse',
1630
1823
  packetCapacity: reactNative.Platform.OS === 'ios' ? tuning.iosPacketLength : tuning.androidPacketLength,
1631
1824
  });
1632
1825
  }
@@ -1660,7 +1853,7 @@ class ReactNativeBleTransport {
1660
1853
  writeFrame: (frame, context) => __awaiter(this, void 0, void 0, function* () {
1661
1854
  assertCurrentGeneration();
1662
1855
  const currentTransport = this.getCachedTransport(uuid);
1663
- yield this.writeProtocolV2Frame(currentTransport, frame, context, assertCurrentGeneration);
1856
+ yield this.writeProtocolV2Frame(uuid, currentTransport, frame, context, assertCurrentGeneration);
1664
1857
  }),
1665
1858
  readFrame: () => __awaiter(this, void 0, void 0, function* () {
1666
1859
  assertCurrentGeneration();
@@ -1681,10 +1874,16 @@ class ReactNativeBleTransport {
1681
1874
  };
1682
1875
  }
1683
1876
  getProtocolType(path) {
1684
- return this.deviceProtocol.get(path);
1877
+ return this.getActiveProtocol(path);
1685
1878
  }
1686
1879
  }
1687
1880
 
1881
+ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
1882
+ exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
1883
+ exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
1884
+ exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
1885
+ exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
1886
+ exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1688
1887
  exports.configureProtocolV2BleTuning = configureProtocolV2BleTuning;
1689
1888
  exports["default"] = ReactNativeBleTransport;
1690
1889
  exports.getFirmwareUploadWriteRetryType = getFirmwareUploadWriteRetryType;