@onekeyfe/hd-transport-react-native 1.2.2-alpha.12 → 1.2.2-alpha.120

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
@@ -57,36 +57,73 @@ const bleLogger = {
57
57
  };
58
58
 
59
59
  const Logger = bleLogger;
60
+ const bondFailureReasons = {
61
+ 1: 'authentication_failed',
62
+ 2: 'rejected',
63
+ 3: 'canceled',
64
+ 4: 'device_unreachable',
65
+ 5: 'discovery_in_progress',
66
+ 6: 'timeout',
67
+ 7: 'repeated_attempts',
68
+ 8: 'remote_canceled',
69
+ 9: 'removed',
70
+ };
60
71
  const getConnectedDeviceIds = (serviceUuids) => BleUtils__default["default"].getConnectedPeripherals(serviceUuids);
61
72
  const pairDevice = (macAddress) => BleUtils__default["default"].pairDevice(macAddress);
62
- const onDeviceBondState = (bleMacAddress) => new Promise((resolve, reject) => {
73
+ const onDeviceBondState = (bleMacAddress, signal) => new Promise((resolve, reject) => {
74
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
75
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
76
+ return;
77
+ }
63
78
  const cleanup = () => {
64
79
  if (timeout) {
65
80
  clearTimeout(timeout);
66
81
  }
67
82
  if (cleanupListener)
68
83
  cleanupListener();
84
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
85
+ };
86
+ const onAbort = () => {
87
+ cleanup();
88
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
69
89
  };
90
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
70
91
  const timeout = setTimeout(() => {
71
92
  cleanup();
72
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
93
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', {
94
+ phase: 'bond',
95
+ reason: 'timeout',
96
+ }));
73
97
  }, 60 * 1000);
74
98
  const cleanupListener = BleUtils__default["default"].onDeviceBondState(peripheral => {
75
- var _a;
99
+ var _a, _b;
76
100
  if (((_a = peripheral.id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== bleMacAddress.toLowerCase()) {
77
101
  return;
78
102
  }
79
103
  const { bondState } = peripheral;
80
104
  const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
81
- const hasCanceled = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
105
+ const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
82
106
  Logger.debug('onDeviceBondState bondState:', bondState);
83
107
  if (hasBonded) {
84
108
  cleanup();
85
109
  resolve(peripheral);
86
110
  }
87
- else if (hasCanceled) {
111
+ else if (hasFailed) {
88
112
  cleanup();
89
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'bonding canceled'));
113
+ const nativeReason = 'reason' in bondState && typeof bondState.reason === 'number'
114
+ ? bondState.reason
115
+ : undefined;
116
+ const reason = nativeReason === undefined ? 'unknown' : (_b = bondFailureReasons[nativeReason]) !== null && _b !== void 0 ? _b : 'unknown';
117
+ const params = Object.assign({ phase: 'bond', reason }, (nativeReason === undefined ? {} : { nativeReason }));
118
+ if (reason === 'timeout') {
119
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', params));
120
+ }
121
+ else if (reason === 'canceled') {
122
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'Bluetooth pairing canceled', params));
123
+ }
124
+ else {
125
+ reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed', params));
126
+ }
90
127
  }
91
128
  });
92
129
  });
@@ -194,6 +231,27 @@ const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject)
194
231
  }, ms);
195
232
  });
196
233
 
234
+ const BLE_PLX_DEVICE_DISCONNECTED = 201;
235
+ const IOS_PERIPHERAL_DISCONNECTED = 7;
236
+ const nativeErrorText$1 = (error) => [error.reason, error.message]
237
+ .filter((value) => typeof value === 'string')
238
+ .join(' ');
239
+ const isNativeBleDisconnectError = (error) => {
240
+ if (!error || typeof error !== 'object') {
241
+ return false;
242
+ }
243
+ const nativeError = error;
244
+ return (nativeError.errorCode === BLE_PLX_DEVICE_DISCONNECTED ||
245
+ nativeError.iosErrorCode === IOS_PERIPHERAL_DISCONNECTED);
246
+ };
247
+ const toBleDisconnectHardwareError = (error) => {
248
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
249
+ return error;
250
+ }
251
+ const nativeError = (error !== null && error !== void 0 ? error : {});
252
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, nativeErrorText$1(nativeError) || undefined);
253
+ };
254
+
197
255
  const ATT_INSUFFICIENT_AUTHENTICATION = 5;
198
256
  const ATT_INSUFFICIENT_ENCRYPTION = 15;
199
257
  const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
@@ -207,6 +265,8 @@ const isNativeBleStaleBondError = (error) => {
207
265
  const nativeError = error;
208
266
  if (nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
209
267
  nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
268
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
269
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
210
270
  nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION) {
211
271
  return true;
212
272
  }
@@ -256,6 +316,7 @@ class BleTransport {
256
316
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
257
317
  const Log = bleLogger;
258
318
  const transportCache = {};
319
+ let bleManagerResetPromise;
259
320
  const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
260
321
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
261
322
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
@@ -314,6 +375,17 @@ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
314
375
  const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
315
376
  typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
316
377
  error.message.startsWith(WEDGED_WRITE_MESSAGE);
378
+ const shouldRethrowProtocolProbeError = (error) => {
379
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
380
+ return (hdShared.isBleStaleBondHardwareError(error) ||
381
+ isNativeBleDisconnectError(error) ||
382
+ code === hdShared.HardwareErrorCode.BleDeviceNotBonded ||
383
+ code === hdShared.HardwareErrorCode.BleDeviceBondedCanceled ||
384
+ code === hdShared.HardwareErrorCode.BleDeviceDisconnected ||
385
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyError ||
386
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure ||
387
+ code === hdShared.HardwareErrorCode.BleWriteCharacteristicError);
388
+ };
317
389
  const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
318
390
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
319
391
  const IOS_NOTIFY_READY_DELAY_MS = 150;
@@ -404,10 +476,10 @@ const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void
404
476
  }
405
477
  });
406
478
  const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
407
- function remapError(error, mapProtocolV2StaleBond) {
479
+ function remapError(error) {
408
480
  var _a;
409
481
  if (error instanceof reactNativeBlePlx.BleError) {
410
- if (mapProtocolV2StaleBond && isNativeBleStaleBondError(error)) {
482
+ if (isNativeBleStaleBondError(error)) {
411
483
  throw toBleStaleBondHardwareError(error);
412
484
  }
413
485
  if ((error === null || error === void 0 ? void 0 : error.attErrorCode) === 22) {
@@ -427,6 +499,7 @@ class ReactNativeBleTransport {
427
499
  this.name = 'ReactNativeBleTransport';
428
500
  this.configured = false;
429
501
  this.stopped = false;
502
+ this.bondAbortController = new AbortController();
430
503
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
431
504
  this.runPromise = null;
432
505
  this.runPromiseDeviceId = null;
@@ -456,11 +529,20 @@ class ReactNativeBleTransport {
456
529
  },
457
530
  classifyError: () => 'link-fatal',
458
531
  onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
459
- var _b;
532
+ var _b, _c;
460
533
  (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
461
534
  this.rejectProtocolV2Frames(uuid, new Error(reason));
462
535
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
463
536
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
537
+ if (this.probingProtocols.get(uuid) !== 'V2') {
538
+ const transport = transportCache[uuid];
539
+ try {
540
+ this.emitDeviceDisconnect(uuid, (_c = transport === null || transport === void 0 ? void 0 : transport.device) === null || _c === void 0 ? void 0 : _c.name, transport === null || transport === void 0 ? void 0 : transport.monitorToken);
541
+ }
542
+ catch (_d) {
543
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 disconnect listener failed');
544
+ }
545
+ }
464
546
  yield this.releaseNative(uuid, true);
465
547
  }
466
548
  }),
@@ -472,6 +554,7 @@ class ReactNativeBleTransport {
472
554
  this.androidPriorityResetTimers = new Map();
473
555
  this.nextMonitorToken = 1;
474
556
  this.lifecycleOperations = new Map();
557
+ this.scanCleanups = new Set();
475
558
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
476
559
  }
477
560
  init(logger, emitter) {
@@ -500,10 +583,38 @@ class ReactNativeBleTransport {
500
583
  listen() {
501
584
  }
502
585
  getPlxManager() {
503
- if (this.blePlxManager)
504
- return Promise.resolve(this.blePlxManager);
505
- this.blePlxManager = new reactNativeBlePlx.BleManager();
506
- return Promise.resolve(this.blePlxManager);
586
+ return __awaiter(this, void 0, void 0, function* () {
587
+ while (bleManagerResetPromise) {
588
+ yield this.waitForManagerReset();
589
+ }
590
+ if (this.stopped)
591
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
592
+ if (!this.blePlxManager)
593
+ this.blePlxManager = new reactNativeBlePlx.BleManager();
594
+ return this.blePlxManager;
595
+ });
596
+ }
597
+ waitForManagerReset() {
598
+ return __awaiter(this, void 0, void 0, function* () {
599
+ if (!bleManagerResetPromise)
600
+ return;
601
+ let timeout;
602
+ try {
603
+ yield Promise.race([
604
+ bleManagerResetPromise,
605
+ new Promise((_, reject) => {
606
+ timeout = setTimeout(() => reject(this.createWedgedBleSetupError()), BLE_CONNECT_TIMEOUT_MS);
607
+ }),
608
+ ]);
609
+ }
610
+ catch (_a) {
611
+ throw this.createWedgedBleSetupError();
612
+ }
613
+ finally {
614
+ if (timeout)
615
+ clearTimeout(timeout);
616
+ }
617
+ });
507
618
  }
508
619
  resolveCharacteristics(device) {
509
620
  return __awaiter(this, void 0, void 0, function* () {
@@ -668,30 +779,55 @@ class ReactNativeBleTransport {
668
779
  }
669
780
  enumerate() {
670
781
  return __awaiter(this, void 0, void 0, function* () {
671
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
672
- const deviceList = [];
673
- const blePlxManager = yield this.getPlxManager();
674
- try {
675
- yield subscribeBleOn(blePlxManager);
676
- }
677
- catch (error) {
678
- Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
679
- reject(error);
680
- return;
681
- }
682
- if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
683
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
684
- const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
685
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
686
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
687
- ]);
688
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
689
- if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
690
- resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
691
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
692
- return;
693
- }
782
+ const scanStartedAt = Date.now();
783
+ let firstDeviceMs;
784
+ const blePlxManager = yield this.getPlxManager();
785
+ yield subscribeBleOn(blePlxManager);
786
+ if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
787
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
788
+ const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
789
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
790
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
791
+ ]);
792
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
793
+ if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
794
+ resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
795
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
694
796
  }
797
+ }
798
+ if (this.stopped)
799
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
800
+ return new Promise((resolve, reject) => {
801
+ const deviceList = [];
802
+ let finished = false;
803
+ let scanCleanup;
804
+ const finishScan = (error) => {
805
+ if (scanCleanup)
806
+ return scanCleanup;
807
+ finished = true;
808
+ clearScanTimer();
809
+ scanCleanup = this.runNativeTeardown('scan', blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
810
+ yield blePlxManager.stopDeviceScan();
811
+ })).then(() => {
812
+ this.scanCleanups.delete(cancelScan);
813
+ if (error)
814
+ reject(error);
815
+ else
816
+ resolve(deviceList);
817
+ });
818
+ return scanCleanup;
819
+ };
820
+ const cancelScan = () => finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
821
+ this.scanCleanups.add(cancelScan);
822
+ const clearScanTimer = timer.timeout(() => {
823
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] scan completed', {
824
+ elapsedMs: Date.now() - scanStartedAt,
825
+ firstDeviceMs,
826
+ deviceCount: deviceList.length,
827
+ scanWindowMs: this.scanTimeout,
828
+ });
829
+ finishScan();
830
+ }, this.scanTimeout);
695
831
  blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
696
832
  allowDuplicates: true,
697
833
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
@@ -700,19 +836,17 @@ class ReactNativeBleTransport {
700
836
  if (error) {
701
837
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
702
838
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
703
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
839
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
704
840
  }
705
841
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothUnauthorized) {
706
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
842
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
707
843
  }
708
844
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.LocationServicesDisabled) {
709
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
710
- }
711
- else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) {
712
- timer.timeout(() => { }, this.scanTimeout);
845
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
713
846
  }
847
+ else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) ;
714
848
  else {
715
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
849
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
716
850
  }
717
851
  return;
718
852
  }
@@ -737,6 +871,8 @@ class ReactNativeBleTransport {
737
871
  });
738
872
  }
739
873
  });
874
+ if (finished)
875
+ return;
740
876
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
741
877
  for (const device of devices) {
742
878
  const localName = 'localName' in device && typeof device.localName === 'string'
@@ -752,10 +888,11 @@ class ReactNativeBleTransport {
752
888
  addDevice(device);
753
889
  }
754
890
  }
755
- });
891
+ }, error => Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral failed:', error));
756
892
  const addDevice = (device) => {
757
893
  var _a;
758
- if (deviceList.every(d => d.id !== device.id)) {
894
+ if (!finished && deviceList.every(d => d.id !== device.id)) {
895
+ firstDeviceMs !== null && firstDeviceMs !== void 0 ? firstDeviceMs : (firstDeviceMs = Date.now() - scanStartedAt);
759
896
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
760
897
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
761
898
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
@@ -765,16 +902,14 @@ class ReactNativeBleTransport {
765
902
  });
766
903
  }
767
904
  };
768
- timer.timeout(() => {
769
- blePlxManager.stopDeviceScan();
770
- resolve(deviceList);
771
- }, this.scanTimeout);
772
- }));
905
+ });
773
906
  });
774
907
  }
775
908
  installTransportForAcquire(uuid, device, characteristics) {
776
909
  return __awaiter(this, void 0, void 0, function* () {
777
910
  const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
911
+ if (this.stopped)
912
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
778
913
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
779
914
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
780
915
  const monitorToken = this.nextMonitorToken;
@@ -795,17 +930,23 @@ class ReactNativeBleTransport {
795
930
  else if (reactNative.Platform.OS === 'android') {
796
931
  yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
797
932
  }
933
+ if (this.stopped)
934
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
798
935
  const initialMtu = transport$1.mtuSize;
799
936
  let refreshAttempts = 0;
800
937
  if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
801
938
  shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
802
939
  refreshAttempts += 1;
803
940
  let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
941
+ if (this.stopped)
942
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
804
943
  transport$1.device = refreshedDevice;
805
944
  transport$1.mtuSize =
806
945
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
807
946
  if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
808
947
  yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
948
+ if (this.stopped)
949
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
809
950
  refreshAttempts += 1;
810
951
  refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
811
952
  transport$1.device = refreshedDevice;
@@ -813,6 +954,8 @@ class ReactNativeBleTransport {
813
954
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
814
955
  }
815
956
  }
957
+ if (this.stopped)
958
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
816
959
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
817
960
  platform: reactNative.Platform.OS,
818
961
  requested: getRequestedBleMtu(),
@@ -835,6 +978,8 @@ class ReactNativeBleTransport {
835
978
  acquireUnlocked(input) {
836
979
  var _a;
837
980
  return __awaiter(this, void 0, void 0, function* () {
981
+ if (this.stopped)
982
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
838
983
  const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
839
984
  const shouldMapProtocolV2StaleBond = expectedProtocol
840
985
  ? expectedProtocol === 'V2'
@@ -869,6 +1014,8 @@ class ReactNativeBleTransport {
869
1014
  if (isCachedDeviceConnected &&
870
1015
  cachedProtocol &&
871
1016
  (!expectedProtocol || cachedProtocol === expectedProtocol)) {
1017
+ if (this.stopped)
1018
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
872
1019
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
873
1020
  return { uuid, protocolType: cachedProtocol };
874
1021
  }
@@ -892,15 +1039,27 @@ class ReactNativeBleTransport {
892
1039
  Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
893
1040
  throw error;
894
1041
  }
1042
+ if (this.stopped)
1043
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
895
1044
  if (reactNative.Platform.OS === 'android') {
896
- const bondState = yield pairDevice(uuid);
897
- if (bondState.bonding) {
898
- yield onDeviceBondState(uuid);
1045
+ try {
1046
+ const bondState = yield pairDevice(uuid);
1047
+ if (bondState.bonding) {
1048
+ yield onDeviceBondState(uuid, this.bondAbortController.signal);
1049
+ }
1050
+ else if (!bondState.bonded) {
1051
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1052
+ }
899
1053
  }
900
- else if (!bondState.bonded) {
901
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1054
+ catch (error) {
1055
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1056
+ yield this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
1057
+ }));
1058
+ throw error;
902
1059
  }
903
1060
  }
1061
+ if (this.stopped)
1062
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
904
1063
  if (!device) {
905
1064
  const devices = yield blePlxManager.devices([uuid]);
906
1065
  [device] = devices;
@@ -931,7 +1090,7 @@ class ReactNativeBleTransport {
931
1090
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleAlreadyConnected);
932
1091
  }
933
1092
  else {
934
- remapError(e, shouldMapProtocolV2StaleBond);
1093
+ remapError(e);
935
1094
  }
936
1095
  }
937
1096
  }
@@ -955,23 +1114,44 @@ class ReactNativeBleTransport {
955
1114
  try {
956
1115
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
957
1116
  }
958
- catch (e) {
959
- Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', e);
960
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1117
+ catch (fallbackError) {
1118
+ Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect error: ', fallbackError);
1119
+ if (fallbackError.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
961
1120
  Log === null || Log === void 0 ? void 0 : Log.debug('last try to reconnect');
962
1121
  yield disconnectedDevice.cancelConnection();
963
1122
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
964
1123
  }
1124
+ else {
1125
+ remapError(fallbackError);
1126
+ }
965
1127
  }
966
1128
  }
967
1129
  else {
968
- remapError(e, shouldMapProtocolV2StaleBond);
1130
+ remapError(e);
969
1131
  }
970
1132
  }
971
1133
  }
1134
+ if (this.stopped)
1135
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1136
+ if (reactNative.Platform.OS === 'android' && !(yield device.isConnected().catch(() => false))) {
1137
+ const disconnectedDevice = device;
1138
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1139
+ yield Promise.all([
1140
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1141
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () => disconnectedDevice.cancelConnection()),
1142
+ ]);
1143
+ }));
1144
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'device is not connected');
1145
+ }
1146
+ if (this.stopped)
1147
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
972
1148
  device = yield resolveNegotiatedMtu(device);
1149
+ if (this.stopped)
1150
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
973
1151
  const acquiredDevice = device;
974
1152
  const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1153
+ if (this.stopped)
1154
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
975
1155
  const protocolHint = expectedProtocol
976
1156
  ? undefined
977
1157
  : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
@@ -1014,6 +1194,8 @@ class ReactNativeBleTransport {
1014
1194
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
1015
1195
  yield this.installTransportForAcquire(uuid, acquiredDevice);
1016
1196
  }));
1197
+ if (this.stopped)
1198
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1017
1199
  const currentTransport = transportCache[uuid];
1018
1200
  if (!currentTransport) {
1019
1201
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
@@ -1022,12 +1204,7 @@ class ReactNativeBleTransport {
1022
1204
  return { uuid, protocolType };
1023
1205
  }
1024
1206
  catch (error) {
1025
- if (hdShared.isBleStaleBondHardwareError(error)) {
1026
- yield this.disconnectUnlocked(uuid);
1027
- }
1028
- else {
1029
- yield this.releaseUnlocked(uuid, true);
1030
- }
1207
+ yield this.disconnectUnlocked(uuid);
1031
1208
  throw error;
1032
1209
  }
1033
1210
  finally {
@@ -1435,7 +1612,43 @@ class ReactNativeBleTransport {
1435
1612
  });
1436
1613
  }
1437
1614
  stop() {
1615
+ var _a;
1616
+ if (this.stopPromise)
1617
+ return this.stopPromise;
1438
1618
  this.stopped = true;
1619
+ this.bondAbortController.abort();
1620
+ const deviceIds = new Set([
1621
+ ...this.monitorTokens.keys(),
1622
+ ...this.sessionProtocols.keys(),
1623
+ ...this.lifecycleOperations.keys(),
1624
+ ...(this.runPromiseDeviceId ? [this.runPromiseDeviceId] : []),
1625
+ ]);
1626
+ const scans = Array.from(this.scanCleanups, cleanup => cleanup());
1627
+ this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
1628
+ this.androidPriorityResetTimers.clear();
1629
+ this.androidHighPriorityDevices.clear();
1630
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1631
+ (_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.reject(error);
1632
+ this.runPromise = null;
1633
+ this.runPromiseDeviceId = null;
1634
+ deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1635
+ const manager = this.blePlxManager;
1636
+ const pendingConnections = manager
1637
+ ? Array.from(this.lifecycleOperations.keys(), uuid => this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1638
+ yield this.runBestEffortNativeOperation('stop: cancel pending device connection', () => manager.cancelDeviceConnection(uuid));
1639
+ })))
1640
+ : [];
1641
+ this.stopPromise = Promise.all([
1642
+ ...scans,
1643
+ ...pendingConnections,
1644
+ ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1645
+ ]).then(() => __awaiter(this, void 0, void 0, function* () {
1646
+ yield this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
1647
+ yield this.waitForManagerReset();
1648
+ this.blePlxManager = undefined;
1649
+ this.emitter = undefined;
1650
+ }));
1651
+ return this.stopPromise;
1439
1652
  }
1440
1653
  disconnect(session) {
1441
1654
  return __awaiter(this, void 0, void 0, function* () {
@@ -1565,13 +1778,27 @@ class ReactNativeBleTransport {
1565
1778
  });
1566
1779
  }
1567
1780
  cancel() {
1568
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1569
- if (this.runPromise) ;
1570
- this.runPromise = null;
1571
- this.runPromiseDeviceId = null;
1781
+ return __awaiter(this, void 0, void 0, function* () {
1782
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1783
+ const pending = this.runPromise;
1784
+ const deviceId = this.runPromiseDeviceId;
1785
+ if (pending) {
1786
+ pending.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled));
1787
+ if (this.runPromise === pending) {
1788
+ this.runPromise = null;
1789
+ this.runPromiseDeviceId = null;
1790
+ }
1791
+ if (deviceId)
1792
+ yield this.disconnect(deviceId);
1793
+ }
1794
+ });
1572
1795
  }
1573
1796
  connectWithTimeout(uuid, connect) {
1574
1797
  return __awaiter(this, void 0, void 0, function* () {
1798
+ if (this.stopped)
1799
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1800
+ const startedAt = Date.now();
1801
+ let succeeded = false;
1575
1802
  let timer;
1576
1803
  let timedOut = false;
1577
1804
  const pending = connect();
@@ -1586,6 +1813,7 @@ class ReactNativeBleTransport {
1586
1813
  }, BLE_CONNECT_TIMEOUT_MS);
1587
1814
  }),
1588
1815
  ]);
1816
+ succeeded = true;
1589
1817
  return result;
1590
1818
  }
1591
1819
  catch (error) {
@@ -1600,11 +1828,19 @@ class ReactNativeBleTransport {
1600
1828
  finally {
1601
1829
  if (timer)
1602
1830
  clearTimeout(timer);
1831
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] connect completed', {
1832
+ connectIdSuffix: uuid.slice(-8),
1833
+ elapsedMs: Date.now() - startedAt,
1834
+ succeeded,
1835
+ backstopExpired: timedOut,
1836
+ });
1603
1837
  }
1604
1838
  });
1605
1839
  }
1606
1840
  resolveCharacteristicsWithTimeout(uuid, device) {
1607
1841
  return __awaiter(this, void 0, void 0, function* () {
1842
+ const startedAt = Date.now();
1843
+ let succeeded = false;
1608
1844
  let timer;
1609
1845
  let timedOut = false;
1610
1846
  const pending = this.resolveCharacteristics(device);
@@ -1620,6 +1856,7 @@ class ReactNativeBleTransport {
1620
1856
  }),
1621
1857
  ]);
1622
1858
  this.connectionSetupTimeoutCounts.delete(uuid);
1859
+ succeeded = true;
1623
1860
  return result;
1624
1861
  }
1625
1862
  catch (error) {
@@ -1634,6 +1871,12 @@ class ReactNativeBleTransport {
1634
1871
  finally {
1635
1872
  if (timer)
1636
1873
  clearTimeout(timer);
1874
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] GATT setup completed', {
1875
+ connectIdSuffix: uuid.slice(-8),
1876
+ elapsedMs: Date.now() - startedAt,
1877
+ succeeded,
1878
+ backstopExpired: timedOut,
1879
+ });
1637
1880
  }
1638
1881
  });
1639
1882
  }
@@ -1735,6 +1978,8 @@ class ReactNativeBleTransport {
1735
1978
  }
1736
1979
  }
1737
1980
  resetPlxManager() {
1981
+ if (bleManagerResetPromise)
1982
+ return;
1738
1983
  const manager = this.blePlxManager;
1739
1984
  this.blePlxManager = undefined;
1740
1985
  const reason = 'React Native BLE manager reset';
@@ -1776,12 +2021,20 @@ class ReactNativeBleTransport {
1776
2021
  this.connectionSetupTimeoutCounts.clear();
1777
2022
  this.monitorTokens.clear();
1778
2023
  this.protocolV2Assemblers.clear();
2024
+ let reset;
1779
2025
  try {
1780
- manager === null || manager === void 0 ? void 0 : manager.destroy();
2026
+ reset = Promise.resolve(manager === null || manager === void 0 ? void 0 : manager.destroy());
1781
2027
  }
1782
2028
  catch (error) {
1783
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
2029
+ reset = Promise.reject(error);
1784
2030
  }
2031
+ bleManagerResetPromise = reset;
2032
+ reset.then(() => {
2033
+ if (bleManagerResetPromise === reset)
2034
+ bleManagerResetPromise = undefined;
2035
+ }, error => {
2036
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE manager destroy failed:', error);
2037
+ });
1785
2038
  }
1786
2039
  createProtocolMismatchError(expected) {
1787
2040
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
@@ -1931,7 +2184,7 @@ class ReactNativeBleTransport {
1931
2184
  catch (error) {
1932
2185
  this.clearProbeProtocol(uuid, 'V1');
1933
2186
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1934
- if (isWedgedWriteError(error)) {
2187
+ if (shouldRethrowProtocolProbeError(error)) {
1935
2188
  throw error;
1936
2189
  }
1937
2190
  return false;
@@ -1957,7 +2210,7 @@ class ReactNativeBleTransport {
1957
2210
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1958
2211
  this.resetProtocolV2Frames(uuid);
1959
2212
  },
1960
- shouldRethrow: hdShared.isBleStaleBondHardwareError,
2213
+ shouldRethrow: shouldRethrowProtocolProbeError,
1961
2214
  });
1962
2215
  if (!detected) {
1963
2216
  this.clearProbeProtocol(uuid, 'V2');
@@ -2084,6 +2337,9 @@ class ReactNativeBleTransport {
2084
2337
  this.rememberStaleBondError(uuid, bondError);
2085
2338
  throw bondError;
2086
2339
  }
2340
+ if (isNativeBleDisconnectError(error)) {
2341
+ throw toBleDisconnectHardwareError(error);
2342
+ }
2087
2343
  if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2088
2344
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2089
2345
  throw error;
@@ -2126,6 +2382,7 @@ class ReactNativeBleTransport {
2126
2382
  if (!this._messages || !this._messagesV2) {
2127
2383
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
2128
2384
  }
2385
+ const isProtocolProbe = this.probingProtocols.get(uuid) === 'V2';
2129
2386
  const callOptions = options;
2130
2387
  const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
2131
2388
  if (highThroughputWrite) {
@@ -2166,6 +2423,14 @@ class ReactNativeBleTransport {
2166
2423
  }
2167
2424
  catch (e) {
2168
2425
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
2426
+ if (!isProtocolProbe &&
2427
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
2428
+ !this.monitorTokens.has(uuid)) {
2429
+ yield this.runLifecycleOperation(uuid, () => __awaiter(this, void 0, void 0, function* () {
2430
+ if (!this.monitorTokens.has(uuid))
2431
+ yield this.disconnectUnlocked(uuid);
2432
+ }));
2433
+ }
2169
2434
  throw e;
2170
2435
  }
2171
2436
  finally {