@onekeyfe/hd-transport-react-native 1.2.3-alpha.3 → 1.2.3-alpha.5

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
  });
@@ -176,24 +213,66 @@ const timer = process.env.NODE_ENV === 'development'
176
213
 
177
214
  const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject) => {
178
215
  let done = false;
216
+ let cancelTimeout = () => undefined;
217
+ let removeSubscription = () => undefined;
218
+ const finish = (error) => {
219
+ if (done)
220
+ return;
221
+ done = true;
222
+ cancelTimeout();
223
+ removeSubscription();
224
+ if (error)
225
+ reject(error);
226
+ else
227
+ resolve();
228
+ };
179
229
  const subscription = bleManager.onStateChange(state => {
180
230
  if (state === 'PoweredOn') {
181
- if (done)
182
- return;
183
- clearTimeout();
184
- done = true;
185
- subscription.remove();
186
- resolve();
231
+ finish();
232
+ }
233
+ else if (state === 'PoweredOff') {
234
+ finish(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff));
235
+ }
236
+ else if (state === 'Unsupported') {
237
+ finish(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported));
238
+ }
239
+ else if (state === 'Unauthorized') {
240
+ finish(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
187
241
  }
188
242
  }, true);
189
- const clearTimeout = timer.timeout(() => {
190
- if (done)
191
- return;
192
- subscription.remove();
193
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
194
- }, ms);
243
+ removeSubscription = () => subscription.remove();
244
+ if (done) {
245
+ removeSubscription();
246
+ }
247
+ else {
248
+ cancelTimeout = timer.timeout(() => {
249
+ finish(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError));
250
+ }, ms);
251
+ }
195
252
  });
196
253
 
254
+ const BLE_PLX_DEVICE_DISCONNECTED = 201;
255
+ const IOS_PERIPHERAL_DISCONNECTED = 7;
256
+ const nativeErrorText$1 = (error) => [error.reason, error.message]
257
+ .filter((value) => typeof value === 'string')
258
+ .join(' ');
259
+ const isNativeBleDisconnectError = (error) => {
260
+ if (!error || typeof error !== 'object') {
261
+ return false;
262
+ }
263
+ const nativeError = error;
264
+ return (nativeError.errorCode === BLE_PLX_DEVICE_DISCONNECTED ||
265
+ nativeError.iosErrorCode === IOS_PERIPHERAL_DISCONNECTED ||
266
+ nativeErrorText$1(nativeError).toLowerCase().includes('was disconnected'));
267
+ };
268
+ const toBleDisconnectHardwareError = (error) => {
269
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceDisconnected) {
270
+ return error;
271
+ }
272
+ const nativeError = (error !== null && error !== void 0 ? error : {});
273
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected, nativeErrorText$1(nativeError) || undefined);
274
+ };
275
+
197
276
  const ATT_INSUFFICIENT_AUTHENTICATION = 5;
198
277
  const ATT_INSUFFICIENT_ENCRYPTION = 15;
199
278
  const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
@@ -258,6 +337,7 @@ class BleTransport {
258
337
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
259
338
  const Log = bleLogger;
260
339
  const transportCache = {};
340
+ let bleManagerResetPromise;
261
341
  const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
262
342
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
263
343
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
@@ -316,6 +396,17 @@ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
316
396
  const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
317
397
  typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
318
398
  error.message.startsWith(WEDGED_WRITE_MESSAGE);
399
+ const shouldRethrowProtocolProbeError = (error) => {
400
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
401
+ return (hdShared.isBleStaleBondHardwareError(error) ||
402
+ isNativeBleDisconnectError(error) ||
403
+ code === hdShared.HardwareErrorCode.BleDeviceNotBonded ||
404
+ code === hdShared.HardwareErrorCode.BleDeviceBondedCanceled ||
405
+ code === hdShared.HardwareErrorCode.BleDeviceDisconnected ||
406
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyError ||
407
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure ||
408
+ code === hdShared.HardwareErrorCode.BleWriteCharacteristicError);
409
+ };
319
410
  const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
320
411
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
321
412
  const IOS_NOTIFY_READY_DELAY_MS = 150;
@@ -350,10 +441,10 @@ function getDeviceDisplayName(device) {
350
441
  }
351
442
  const IOS_REQUEST_MTU = 247;
352
443
  const ANDROID_REQUEST_MTU = 517;
353
- const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
354
444
  const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
355
445
  const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
356
446
  const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
447
+ const BLE_MTU_REQUEST_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS;
357
448
  const connectOptions = {
358
449
  requestMTU: getRequestedBleMtu(),
359
450
  timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
@@ -362,6 +453,29 @@ const connectOptions = {
362
453
  const fallbackConnectOptions = {
363
454
  timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
364
455
  };
456
+ const androidRefreshGattConnectOptions = {
457
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
458
+ refreshGatt: 'OnConnected',
459
+ };
460
+ const ANDROID_MTU_EXCHANGE_TIMEOUT_MS = 12000;
461
+ const ANDROID_LINK_DROP_QUIET_MS = 5000;
462
+ const ANDROID_LINK_DROP_POLL_MS = 250;
463
+ const isKnownDefaultMtu = (mtu) => typeof mtu === 'number' && Number.isFinite(mtu) && mtu <= 23;
464
+ const isMissingGattShapeError = (error) => {
465
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
466
+ const message = error === null || error === void 0 ? void 0 : error.message;
467
+ return (code === hdShared.HardwareErrorCode.BleServiceNotFound ||
468
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotFound ||
469
+ (typeof message === 'string' &&
470
+ (message.includes('BLECharacteristicNotFound') ||
471
+ message.includes('BLECharacteristicNotWritable') ||
472
+ message.includes('BLECharacteristicNotNotifiable'))));
473
+ };
474
+ const isStaleGattTableNotifyReason = (reason) => !!reason &&
475
+ (reason.includes('Cannot write client characteristic config descriptor') ||
476
+ reason.includes('Cannot find client characteristic config descriptor') ||
477
+ reason.includes('The handle is invalid') ||
478
+ reason.includes('Writing is not permitted'));
365
479
  const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
366
480
  const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
367
481
  const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
@@ -376,6 +490,11 @@ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? v
376
490
  error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
377
491
  const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
378
492
  const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
493
+ const isMtuOrCancelledConnectError = (error) => {
494
+ const errorCode = error === null || error === void 0 ? void 0 : error.errorCode;
495
+ return (errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
496
+ errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled);
497
+ };
379
498
  const tryToGetConfiguration = (device) => {
380
499
  if (!device || !device.serviceUUIDs)
381
500
  return null;
@@ -387,27 +506,67 @@ const tryToGetConfiguration = (device) => {
387
506
  return null;
388
507
  return infos;
389
508
  };
390
- const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
509
+ const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __awaiter(void 0, void 0, void 0, function* () {
391
510
  if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
392
- return device;
511
+ return { device, timedOut: false };
512
+ const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
513
+ let timeoutId;
514
+ let timedOut = false;
393
515
  try {
394
- const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
395
- return mtuDevice;
516
+ const request = device.requestMTU(getRequestedBleMtu(), transactionId);
517
+ request.catch(() => undefined);
518
+ const mtuDevice = yield Promise.race([
519
+ request,
520
+ new Promise((_, reject) => {
521
+ timeoutId = setTimeout(() => {
522
+ timedOut = true;
523
+ reject(new Error(`BLE MTU request timeout after ${BLE_MTU_REQUEST_TIMEOUT_MS}ms`));
524
+ }, BLE_MTU_REQUEST_TIMEOUT_MS);
525
+ }),
526
+ ]);
527
+ return { device: mtuDevice, timedOut: false };
396
528
  }
397
529
  catch (error) {
530
+ if (timedOut && cancelTransaction) {
531
+ try {
532
+ Promise.resolve(cancelTransaction(transactionId)).catch(cancelError => {
533
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
534
+ platform: reactNative.Platform.OS,
535
+ stage,
536
+ attempt,
537
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError),
538
+ });
539
+ });
540
+ }
541
+ catch (cancelError) {
542
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
543
+ platform: reactNative.Platform.OS,
544
+ stage,
545
+ attempt,
546
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError),
547
+ });
548
+ }
549
+ }
398
550
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
399
551
  platform: reactNative.Platform.OS,
400
552
  stage,
401
553
  attempt,
402
554
  actual: device.mtu,
555
+ timedOut,
403
556
  error: error instanceof Error ? error.message : String(error),
404
557
  });
405
- return device;
558
+ return { device, timedOut };
559
+ }
560
+ finally {
561
+ if (timeoutId)
562
+ clearTimeout(timeoutId);
406
563
  }
407
564
  });
408
- const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
565
+ const resolveNegotiatedMtu = (device, cancelTransaction) => shouldRefreshNegotiatedMtu(device.mtu)
566
+ ? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
567
+ : Promise.resolve({ device, timedOut: false });
409
568
  function remapError(error) {
410
- var _a;
569
+ var _a, _b;
411
570
  if (error instanceof reactNativeBlePlx.BleError) {
412
571
  if (isNativeBleStaleBondError(error)) {
413
572
  throw toBleStaleBondHardwareError(error);
@@ -416,12 +575,13 @@ function remapError(error) {
416
575
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondError);
417
576
  }
418
577
  }
419
- if (error instanceof Error &&
420
- error.message &&
421
- (error.message.includes('was disconnected') || error.message.includes('not found'))) {
578
+ if (isNativeBleDisconnectError(error)) {
579
+ throw toBleDisconnectHardwareError(error);
580
+ }
581
+ if (error instanceof Error && ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('not found'))) {
422
582
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
423
583
  }
424
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, (_a = error.reason) !== null && _a !== void 0 ? _a : error);
584
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, (_b = error.reason) !== null && _b !== void 0 ? _b : error);
425
585
  }
426
586
  class ReactNativeBleTransport {
427
587
  constructor(options) {
@@ -429,6 +589,7 @@ class ReactNativeBleTransport {
429
589
  this.name = 'ReactNativeBleTransport';
430
590
  this.configured = false;
431
591
  this.stopped = false;
592
+ this.bondAbortController = new AbortController();
432
593
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
433
594
  this.runPromise = null;
434
595
  this.runPromiseDeviceId = null;
@@ -441,6 +602,8 @@ class ReactNativeBleTransport {
441
602
  this.sessionProtocols = new Map();
442
603
  this.confirmedProtocolV2 = new Set();
443
604
  this.protocolReprobeFailures = new Map();
605
+ this.silentDetections = new Map();
606
+ this.androidGattCacheRefreshes = new Set();
444
607
  this.staleBondErrors = new Map();
445
608
  this.acquiringProtocolV2 = new Set();
446
609
  this.protocolV2Assemblers = new Map();
@@ -458,11 +621,20 @@ class ReactNativeBleTransport {
458
621
  },
459
622
  classifyError: () => 'link-fatal',
460
623
  onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
461
- var _b;
624
+ var _b, _c;
462
625
  (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
463
626
  this.rejectProtocolV2Frames(uuid, new Error(reason));
464
627
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
465
628
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
629
+ if (this.probingProtocols.get(uuid) !== 'V2') {
630
+ const transport = transportCache[uuid];
631
+ try {
632
+ 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);
633
+ }
634
+ catch (_d) {
635
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 disconnect listener failed');
636
+ }
637
+ }
466
638
  yield this.releaseNative(uuid, true);
467
639
  }
468
640
  }),
@@ -474,6 +646,7 @@ class ReactNativeBleTransport {
474
646
  this.androidPriorityResetTimers = new Map();
475
647
  this.nextMonitorToken = 1;
476
648
  this.lifecycleOperations = new Map();
649
+ this.scanCleanups = new Set();
477
650
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
478
651
  }
479
652
  init(logger, emitter) {
@@ -502,10 +675,38 @@ class ReactNativeBleTransport {
502
675
  listen() {
503
676
  }
504
677
  getPlxManager() {
505
- if (this.blePlxManager)
506
- return Promise.resolve(this.blePlxManager);
507
- this.blePlxManager = new reactNativeBlePlx.BleManager();
508
- return Promise.resolve(this.blePlxManager);
678
+ return __awaiter(this, void 0, void 0, function* () {
679
+ while (bleManagerResetPromise) {
680
+ yield this.waitForManagerReset();
681
+ }
682
+ if (this.stopped)
683
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
684
+ if (!this.blePlxManager)
685
+ this.blePlxManager = new reactNativeBlePlx.BleManager();
686
+ return this.blePlxManager;
687
+ });
688
+ }
689
+ waitForManagerReset() {
690
+ return __awaiter(this, void 0, void 0, function* () {
691
+ if (!bleManagerResetPromise)
692
+ return;
693
+ let timeout;
694
+ try {
695
+ yield Promise.race([
696
+ bleManagerResetPromise,
697
+ new Promise((_, reject) => {
698
+ timeout = setTimeout(() => reject(this.createWedgedBleSetupError()), BLE_CONNECT_TIMEOUT_MS);
699
+ }),
700
+ ]);
701
+ }
702
+ catch (_a) {
703
+ throw this.createWedgedBleSetupError();
704
+ }
705
+ finally {
706
+ if (timeout)
707
+ clearTimeout(timeout);
708
+ }
709
+ });
509
710
  }
510
711
  resolveCharacteristics(device) {
511
712
  return __awaiter(this, void 0, void 0, function* () {
@@ -670,30 +871,55 @@ class ReactNativeBleTransport {
670
871
  }
671
872
  enumerate() {
672
873
  return __awaiter(this, void 0, void 0, function* () {
673
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
674
- const deviceList = [];
675
- const blePlxManager = yield this.getPlxManager();
676
- try {
677
- yield subscribeBleOn(blePlxManager);
678
- }
679
- catch (error) {
680
- Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
681
- reject(error);
682
- return;
683
- }
684
- if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
685
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
686
- const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
687
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
688
- reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
689
- ]);
690
- Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
691
- if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
692
- resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
693
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
694
- return;
695
- }
874
+ const scanStartedAt = Date.now();
875
+ let firstDeviceMs;
876
+ const blePlxManager = yield this.getPlxManager();
877
+ yield subscribeBleOn(blePlxManager);
878
+ if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
879
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
880
+ const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
881
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
882
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
883
+ ]);
884
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
885
+ if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
886
+ resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
887
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
696
888
  }
889
+ }
890
+ if (this.stopped)
891
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
892
+ return new Promise((resolve, reject) => {
893
+ const deviceList = [];
894
+ let finished = false;
895
+ let scanCleanup;
896
+ const finishScan = (error) => {
897
+ if (scanCleanup)
898
+ return scanCleanup;
899
+ finished = true;
900
+ clearScanTimer();
901
+ scanCleanup = this.runNativeTeardown('scan', blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
902
+ yield blePlxManager.stopDeviceScan();
903
+ })).then(() => {
904
+ this.scanCleanups.delete(cancelScan);
905
+ if (error)
906
+ reject(error);
907
+ else
908
+ resolve(deviceList);
909
+ });
910
+ return scanCleanup;
911
+ };
912
+ const cancelScan = () => finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
913
+ this.scanCleanups.add(cancelScan);
914
+ const clearScanTimer = timer.timeout(() => {
915
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] scan completed', {
916
+ elapsedMs: Date.now() - scanStartedAt,
917
+ firstDeviceMs,
918
+ deviceCount: deviceList.length,
919
+ scanWindowMs: this.scanTimeout,
920
+ });
921
+ finishScan();
922
+ }, this.scanTimeout);
697
923
  blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
698
924
  allowDuplicates: true,
699
925
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
@@ -701,20 +927,21 @@ class ReactNativeBleTransport {
701
927
  var _a, _b;
702
928
  if (error) {
703
929
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
704
- if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
705
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
930
+ if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff) {
931
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff));
932
+ }
933
+ else if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothUnsupported) {
934
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported));
706
935
  }
707
936
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothUnauthorized) {
708
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
937
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
709
938
  }
710
939
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.LocationServicesDisabled) {
711
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
712
- }
713
- else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) {
714
- timer.timeout(() => { }, this.scanTimeout);
940
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
715
941
  }
942
+ else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) ;
716
943
  else {
717
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
944
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
718
945
  }
719
946
  return;
720
947
  }
@@ -739,6 +966,8 @@ class ReactNativeBleTransport {
739
966
  });
740
967
  }
741
968
  });
969
+ if (finished)
970
+ return;
742
971
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
743
972
  for (const device of devices) {
744
973
  const localName = 'localName' in device && typeof device.localName === 'string'
@@ -754,10 +983,11 @@ class ReactNativeBleTransport {
754
983
  addDevice(device);
755
984
  }
756
985
  }
757
- });
986
+ }, error => Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral failed:', error));
758
987
  const addDevice = (device) => {
759
988
  var _a;
760
- if (deviceList.every(d => d.id !== device.id)) {
989
+ if (!finished && deviceList.every(d => d.id !== device.id)) {
990
+ firstDeviceMs !== null && firstDeviceMs !== void 0 ? firstDeviceMs : (firstDeviceMs = Date.now() - scanStartedAt);
761
991
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
762
992
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
763
993
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
@@ -767,16 +997,14 @@ class ReactNativeBleTransport {
767
997
  });
768
998
  }
769
999
  };
770
- timer.timeout(() => {
771
- blePlxManager.stopDeviceScan();
772
- resolve(deviceList);
773
- }, this.scanTimeout);
774
- }));
1000
+ });
775
1001
  });
776
1002
  }
777
1003
  installTransportForAcquire(uuid, device, characteristics) {
778
1004
  return __awaiter(this, void 0, void 0, function* () {
779
1005
  const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
1006
+ if (this.stopped)
1007
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
780
1008
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
781
1009
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
782
1010
  const monitorToken = this.nextMonitorToken;
@@ -797,30 +1025,12 @@ class ReactNativeBleTransport {
797
1025
  else if (reactNative.Platform.OS === 'android') {
798
1026
  yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
799
1027
  }
800
- const initialMtu = transport$1.mtuSize;
801
- let refreshAttempts = 0;
802
- if ((reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android') &&
803
- shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
804
- refreshAttempts += 1;
805
- let refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 1);
806
- transport$1.device = refreshedDevice;
807
- transport$1.mtuSize =
808
- typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
809
- if (shouldRefreshNegotiatedMtu(transport$1.mtuSize)) {
810
- yield delay(BLE_MTU_REFRESH_RETRY_DELAY_MS);
811
- refreshAttempts += 1;
812
- refreshedDevice = yield requestNegotiatedMtu(transport$1.device, 'servicesAndNotifyReady', 2);
813
- transport$1.device = refreshedDevice;
814
- transport$1.mtuSize =
815
- typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport$1.mtuSize;
816
- }
817
- }
1028
+ if (this.stopped)
1029
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
818
1030
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
819
1031
  platform: reactNative.Platform.OS,
820
1032
  requested: getRequestedBleMtu(),
821
- initial: initialMtu,
822
1033
  actual: transport$1.mtuSize,
823
- refreshAttempts,
824
1034
  });
825
1035
  return transport$1;
826
1036
  });
@@ -837,6 +1047,8 @@ class ReactNativeBleTransport {
837
1047
  acquireUnlocked(input) {
838
1048
  var _a;
839
1049
  return __awaiter(this, void 0, void 0, function* () {
1050
+ if (this.stopped)
1051
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
840
1052
  const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
841
1053
  const shouldMapProtocolV2StaleBond = expectedProtocol
842
1054
  ? expectedProtocol === 'V2'
@@ -868,9 +1080,13 @@ class ReactNativeBleTransport {
868
1080
  const isCachedDeviceConnected = yield cachedTransport.device
869
1081
  .isConnected()
870
1082
  .catch(() => false);
1083
+ const isCachedAndroidLinkUsable = reactNative.Platform.OS !== 'android' || !this.androidGattCacheRefreshes.has(uuid);
871
1084
  if (isCachedDeviceConnected &&
1085
+ isCachedAndroidLinkUsable &&
872
1086
  cachedProtocol &&
873
1087
  (!expectedProtocol || cachedProtocol === expectedProtocol)) {
1088
+ if (this.stopped)
1089
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
874
1090
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
875
1091
  return { uuid, protocolType: cachedProtocol };
876
1092
  }
@@ -879,6 +1095,15 @@ class ReactNativeBleTransport {
879
1095
  }
880
1096
  }
881
1097
  let device = null;
1098
+ const isAndroid = reactNative.Platform.OS === 'android';
1099
+ const refreshAndroidGattCache = isAndroid && (!!skipProtocolProbe || this.androidGattCacheRefreshes.has(uuid));
1100
+ let nativeConnectOptions = connectOptions;
1101
+ if (isAndroid) {
1102
+ nativeConnectOptions = refreshAndroidGattCache
1103
+ ? androidRefreshGattConnectOptions
1104
+ : fallbackConnectOptions;
1105
+ }
1106
+ let androidRefreshConnectRan = false;
882
1107
  if (forceCleanRunPromise && this.runPromise) {
883
1108
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
884
1109
  this.runPromise.reject(error);
@@ -887,6 +1112,7 @@ class ReactNativeBleTransport {
887
1112
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
888
1113
  }
889
1114
  const blePlxManager = yield this.getPlxManager();
1115
+ let skipPostConnectMtu = false;
890
1116
  try {
891
1117
  yield subscribeBleOn(blePlxManager);
892
1118
  }
@@ -894,6 +1120,27 @@ class ReactNativeBleTransport {
894
1120
  Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
895
1121
  throw error;
896
1122
  }
1123
+ if (this.stopped)
1124
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1125
+ if (reactNative.Platform.OS === 'android') {
1126
+ try {
1127
+ const bondState = yield pairDevice(uuid);
1128
+ if (bondState.bonding) {
1129
+ yield onDeviceBondState(uuid, this.bondAbortController.signal);
1130
+ }
1131
+ else if (!bondState.bonded) {
1132
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1133
+ }
1134
+ }
1135
+ catch (error) {
1136
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1137
+ yield this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
1138
+ }));
1139
+ throw error;
1140
+ }
1141
+ }
1142
+ if (this.stopped)
1143
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
897
1144
  if (!device) {
898
1145
  const devices = yield blePlxManager.devices([uuid]);
899
1146
  [device] = devices;
@@ -907,15 +1154,16 @@ class ReactNativeBleTransport {
907
1154
  if (!device) {
908
1155
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
909
1156
  try {
910
- device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
1157
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, nativeConnectOptions));
1158
+ androidRefreshConnectRan = refreshAndroidGattCache;
911
1159
  }
912
1160
  catch (e) {
913
1161
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
914
1162
  if (shouldRethrowBleSetupError(e)) {
915
1163
  throw e;
916
1164
  }
917
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
918
- e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1165
+ if (isMtuOrCancelledConnectError(e)) {
1166
+ skipPostConnectMtu = true;
919
1167
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
920
1168
  device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
921
1169
  }
@@ -931,19 +1179,27 @@ class ReactNativeBleTransport {
931
1179
  if (!device) {
932
1180
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'unable to connect to device');
933
1181
  }
1182
+ if (refreshAndroidGattCache &&
1183
+ !androidRefreshConnectRan &&
1184
+ (yield device.isConnected().catch(() => false))) {
1185
+ yield this.dropAndroidLink(uuid, blePlxManager, device, 'gatt cache refresh');
1186
+ if (this.stopped)
1187
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1188
+ }
934
1189
  if (!(yield device.isConnected())) {
935
1190
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
936
1191
  const disconnectedDevice = device;
937
1192
  try {
938
- device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
1193
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(nativeConnectOptions));
1194
+ androidRefreshConnectRan = refreshAndroidGattCache;
939
1195
  }
940
1196
  catch (e) {
941
1197
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
942
1198
  if (shouldRethrowBleSetupError(e)) {
943
1199
  throw e;
944
1200
  }
945
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
946
- e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1201
+ if (isMtuOrCancelledConnectError(e)) {
1202
+ skipPostConnectMtu = true;
947
1203
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
948
1204
  try {
949
1205
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
@@ -965,33 +1221,70 @@ class ReactNativeBleTransport {
965
1221
  }
966
1222
  }
967
1223
  }
968
- if (reactNative.Platform.OS === 'android') {
969
- const connectedDevice = device;
970
- try {
971
- if (!(yield connectedDevice.isConnected().catch(() => false))) {
972
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, `Device ${uuid} is not connected before bonding`);
1224
+ if (this.stopped)
1225
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1226
+ if (reactNative.Platform.OS === 'android' && !(yield device.isConnected().catch(() => false))) {
1227
+ const disconnectedDevice = device;
1228
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1229
+ yield Promise.all([
1230
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1231
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () => disconnectedDevice.cancelConnection()),
1232
+ ]);
1233
+ }));
1234
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'device is not connected');
1235
+ }
1236
+ if (this.stopped)
1237
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1238
+ let characteristics;
1239
+ if (isAndroid) {
1240
+ if (refreshAndroidGattCache) {
1241
+ characteristics = yield this.resolveCharacteristicsWithTimeout(uuid, device);
1242
+ if (androidRefreshConnectRan)
1243
+ this.androidGattCacheRefreshes.delete(uuid);
1244
+ if (this.stopped)
1245
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1246
+ }
1247
+ device = yield this.negotiateAndroidMtu(uuid, blePlxManager, device);
1248
+ }
1249
+ else if (!skipPostConnectMtu) {
1250
+ const mtuResult = yield resolveNegotiatedMtu(device, transactionId => blePlxManager.cancelTransaction(transactionId));
1251
+ device = mtuResult.device;
1252
+ if (mtuResult.timedOut) {
1253
+ if (this.stopped)
1254
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1255
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] post-connect MTU timed out, reconnecting without requesting MTU');
1256
+ const timedOutDevice = device;
1257
+ let mtuTeardownSettled = false;
1258
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1259
+ yield this.runBestEffortNativeOperation('mtu timeout: cancel device connection', () => timedOutDevice.cancelConnection());
1260
+ mtuTeardownSettled = true;
1261
+ }));
1262
+ if (this.stopped)
1263
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1264
+ if (!mtuTeardownSettled || this.blePlxManager !== blePlxManager) {
1265
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'BLE MTU cleanup timed out');
973
1266
  }
974
- const bondState = yield pairDevice(uuid);
975
- if (bondState.bonding) {
976
- yield onDeviceBondState(uuid);
1267
+ try {
1268
+ device = yield this.connectWithTimeout(uuid, () => timedOutDevice.connect(fallbackConnectOptions));
977
1269
  }
978
- else if (!bondState.bonded) {
979
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1270
+ catch (error) {
1271
+ if (shouldRethrowBleSetupError(error))
1272
+ throw error;
1273
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
1274
+ device = timedOutDevice;
1275
+ }
1276
+ else {
1277
+ remapError(error);
1278
+ }
980
1279
  }
981
1280
  }
982
- catch (error) {
983
- yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
984
- yield Promise.all([
985
- this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
986
- this.runBestEffortNativeOperation('bond failure: cancel device connection', () => connectedDevice.cancelConnection()),
987
- ]);
988
- }));
989
- throw error;
990
- }
991
1281
  }
992
- device = yield resolveNegotiatedMtu(device);
1282
+ if (this.stopped)
1283
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
993
1284
  const acquiredDevice = device;
994
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1285
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice));
1286
+ if (this.stopped)
1287
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
995
1288
  const protocolHint = expectedProtocol
996
1289
  ? undefined
997
1290
  : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
@@ -1034,6 +1327,8 @@ class ReactNativeBleTransport {
1034
1327
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
1035
1328
  yield this.installTransportForAcquire(uuid, acquiredDevice);
1036
1329
  }));
1330
+ if (this.stopped)
1331
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1037
1332
  const currentTransport = transportCache[uuid];
1038
1333
  if (!currentTransport) {
1039
1334
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
@@ -1042,12 +1337,7 @@ class ReactNativeBleTransport {
1042
1337
  return { uuid, protocolType };
1043
1338
  }
1044
1339
  catch (error) {
1045
- if (hdShared.isBleStaleBondHardwareError(error)) {
1046
- yield this.disconnectUnlocked(uuid);
1047
- }
1048
- else {
1049
- yield this.releaseUnlocked(uuid, true);
1050
- }
1340
+ yield this.disconnectUnlocked(uuid);
1051
1341
  throw error;
1052
1342
  }
1053
1343
  finally {
@@ -1059,7 +1349,7 @@ class ReactNativeBleTransport {
1059
1349
  let bufferLength = 0;
1060
1350
  let buffer$1 = [];
1061
1351
  const subscription = characteristic.monitor((error, c) => {
1062
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1352
+ var _a, _b, _c, _d, _e, _f;
1063
1353
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
1064
1354
  if (error) {
1065
1355
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -1076,16 +1366,16 @@ class ReactNativeBleTransport {
1076
1366
  this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
1077
1367
  return;
1078
1368
  }
1369
+ if (reactNative.Platform.OS === 'android' && isStaleGattTableNotifyReason(error.reason)) {
1370
+ this.androidGattCacheRefreshes.add(uuid);
1371
+ }
1079
1372
  if (this.getActiveProtocol(uuid) === 'V2') {
1080
1373
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
1081
1374
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
1082
1375
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
1083
1376
  }
1084
- else if (((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('Cannot write client characteristic config descriptor')) ||
1085
- ((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('Cannot find client characteristic config descriptor')) ||
1086
- ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('The handle is invalid')) ||
1087
- ((_e = error.reason) === null || _e === void 0 ? void 0 : _e.includes('Writing is not permitted')) ||
1088
- ((_f = error.reason) === null || _f === void 0 ? void 0 : _f.includes('notify change failed for device'))) {
1377
+ else if (isStaleGattTableNotifyReason(error.reason) ||
1378
+ ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('notify change failed for device'))) {
1089
1379
  errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
1090
1380
  }
1091
1381
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
@@ -1093,14 +1383,11 @@ class ReactNativeBleTransport {
1093
1383
  }
1094
1384
  if (this.runPromise && this.runPromiseDeviceId === uuid) {
1095
1385
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
1096
- if ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('The connection has timed out unexpectedly')) {
1386
+ if ((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('The connection has timed out unexpectedly')) {
1097
1387
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
1098
1388
  }
1099
- if (((_h = error.reason) === null || _h === void 0 ? void 0 : _h.includes('Cannot write client characteristic config descriptor')) ||
1100
- ((_j = error.reason) === null || _j === void 0 ? void 0 : _j.includes('Cannot find client characteristic config descriptor')) ||
1101
- ((_k = error.reason) === null || _k === void 0 ? void 0 : _k.includes('The handle is invalid')) ||
1102
- ((_l = error.reason) === null || _l === void 0 ? void 0 : _l.includes('Writing is not permitted')) ||
1103
- ((_m = error.reason) === null || _m === void 0 ? void 0 : _m.includes('notify change failed for device'))) {
1389
+ if (isStaleGattTableNotifyReason(error.reason) ||
1390
+ ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('notify change failed for device'))) {
1104
1391
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
1105
1392
  this.runPromise.reject(notifyError);
1106
1393
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
@@ -1142,7 +1429,7 @@ class ReactNativeBleTransport {
1142
1429
  bufferLength = 0;
1143
1430
  buffer$1 = [];
1144
1431
  if (this.runPromiseDeviceId === uuid) {
1145
- (_o = this.runPromise) === null || _o === void 0 ? void 0 : _o.resolve(value.toString('hex'));
1432
+ (_e = this.runPromise) === null || _e === void 0 ? void 0 : _e.resolve(value.toString('hex'));
1146
1433
  }
1147
1434
  }
1148
1435
  }
@@ -1153,7 +1440,7 @@ class ReactNativeBleTransport {
1153
1440
  this.rejectProtocolV2Frames(uuid, notifyError);
1154
1441
  }
1155
1442
  else if (this.runPromiseDeviceId === uuid) {
1156
- (_p = this.runPromise) === null || _p === void 0 ? void 0 : _p.reject(notifyError);
1443
+ (_f = this.runPromise) === null || _f === void 0 ? void 0 : _f.reject(notifyError);
1157
1444
  }
1158
1445
  }
1159
1446
  }, notifyTransactionId);
@@ -1429,13 +1716,14 @@ class ReactNativeBleTransport {
1429
1716
  return check.call(jsonData);
1430
1717
  }
1431
1718
  catch (e) {
1432
- if (name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
1433
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
1719
+ const isProbeTimeout = (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS &&
1720
+ (name === 'GetFeatures' || name === 'Initialize');
1721
+ if (isProbeTimeout) {
1722
+ Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] Protocol V1 ${name} probe call failed:`, e);
1434
1723
  }
1435
1724
  else {
1436
1725
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1437
1726
  }
1438
- const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1439
1727
  const isStaleCall = this.runPromise !== runPromise;
1440
1728
  if (!isProbeTimeout &&
1441
1729
  !isStaleCall &&
@@ -1455,7 +1743,43 @@ class ReactNativeBleTransport {
1455
1743
  });
1456
1744
  }
1457
1745
  stop() {
1746
+ var _a;
1747
+ if (this.stopPromise)
1748
+ return this.stopPromise;
1458
1749
  this.stopped = true;
1750
+ this.bondAbortController.abort();
1751
+ const deviceIds = new Set([
1752
+ ...this.monitorTokens.keys(),
1753
+ ...this.sessionProtocols.keys(),
1754
+ ...this.lifecycleOperations.keys(),
1755
+ ...(this.runPromiseDeviceId ? [this.runPromiseDeviceId] : []),
1756
+ ]);
1757
+ const scans = Array.from(this.scanCleanups, cleanup => cleanup());
1758
+ this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
1759
+ this.androidPriorityResetTimers.clear();
1760
+ this.androidHighPriorityDevices.clear();
1761
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1762
+ (_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.reject(error);
1763
+ this.runPromise = null;
1764
+ this.runPromiseDeviceId = null;
1765
+ deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1766
+ const manager = this.blePlxManager;
1767
+ const pendingConnections = manager
1768
+ ? Array.from(this.lifecycleOperations.keys(), uuid => this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1769
+ yield this.runBestEffortNativeOperation('stop: cancel pending device connection', () => manager.cancelDeviceConnection(uuid));
1770
+ })))
1771
+ : [];
1772
+ this.stopPromise = Promise.all([
1773
+ ...scans,
1774
+ ...pendingConnections,
1775
+ ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1776
+ ]).then(() => __awaiter(this, void 0, void 0, function* () {
1777
+ yield this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
1778
+ yield this.waitForManagerReset();
1779
+ this.blePlxManager = undefined;
1780
+ this.emitter = undefined;
1781
+ }));
1782
+ return this.stopPromise;
1459
1783
  }
1460
1784
  disconnect(session) {
1461
1785
  return __awaiter(this, void 0, void 0, function* () {
@@ -1585,13 +1909,93 @@ class ReactNativeBleTransport {
1585
1909
  });
1586
1910
  }
1587
1911
  cancel() {
1588
- Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1589
- if (this.runPromise) ;
1590
- this.runPromise = null;
1591
- this.runPromiseDeviceId = null;
1912
+ return __awaiter(this, void 0, void 0, function* () {
1913
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1914
+ const pending = this.runPromise;
1915
+ const deviceId = this.runPromiseDeviceId;
1916
+ if (pending) {
1917
+ pending.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled));
1918
+ if (this.runPromise === pending) {
1919
+ this.runPromise = null;
1920
+ this.runPromiseDeviceId = null;
1921
+ }
1922
+ if (deviceId)
1923
+ yield this.disconnect(deviceId);
1924
+ }
1925
+ });
1926
+ }
1927
+ negotiateAndroidMtu(uuid, manager, device) {
1928
+ return __awaiter(this, void 0, void 0, function* () {
1929
+ if (!shouldRefreshNegotiatedMtu(device.mtu))
1930
+ return device;
1931
+ const startedAt = Date.now();
1932
+ let timer;
1933
+ let timedOut = false;
1934
+ let negotiated = device;
1935
+ let failure;
1936
+ try {
1937
+ negotiated = yield Promise.race([
1938
+ device.requestMTU(ANDROID_REQUEST_MTU, `${device.id}:mtu:connected:0:${startedAt}`),
1939
+ new Promise((_, reject) => {
1940
+ timer = setTimeout(() => {
1941
+ timedOut = true;
1942
+ reject(new Error(`BLE MTU exchange timeout after ${ANDROID_MTU_EXCHANGE_TIMEOUT_MS}ms`));
1943
+ }, ANDROID_MTU_EXCHANGE_TIMEOUT_MS);
1944
+ }),
1945
+ ]);
1946
+ }
1947
+ catch (error) {
1948
+ failure = error instanceof Error ? error.message : String(error);
1949
+ }
1950
+ finally {
1951
+ if (timer)
1952
+ clearTimeout(timer);
1953
+ }
1954
+ Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] BLE MTU exchange ${failure ? 'failed' : 'completed'}`, {
1955
+ connectIdSuffix: uuid.slice(-8),
1956
+ elapsedMs: Date.now() - startedAt,
1957
+ timedOut,
1958
+ actual: negotiated.mtu,
1959
+ error: failure,
1960
+ });
1961
+ if (this.stopped)
1962
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1963
+ if (!timedOut && !isKnownDefaultMtu(negotiated.mtu))
1964
+ return negotiated;
1965
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'mtu-backstop' : 'mtu-default');
1966
+ yield this.dropAndroidLink(uuid, manager, negotiated, timedOut ? 'mtu exchange timeout' : 'default mtu');
1967
+ if (resetManager)
1968
+ throw this.createWedgedBleSetupError();
1969
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, timedOut
1970
+ ? 'BLE MTU exchange did not complete, reconnecting on a fresh link'
1971
+ : `BLE link stayed at the default MTU ${negotiated.mtu}, reconnecting on a fresh link`);
1972
+ });
1973
+ }
1974
+ dropAndroidLink(uuid, manager, device, reason) {
1975
+ return __awaiter(this, void 0, void 0, function* () {
1976
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1977
+ yield Promise.all([
1978
+ this.runBestEffortNativeOperation(`${reason}: cancel manager connection`, () => manager.cancelDeviceConnection(uuid)),
1979
+ this.runBestEffortNativeOperation(`${reason}: cancel device connection`, () => device.cancelConnection()),
1980
+ ]);
1981
+ }));
1982
+ const startedAt = Date.now();
1983
+ while (!this.stopped && Date.now() - startedAt < ANDROID_LINK_DROP_QUIET_MS) {
1984
+ yield delay(ANDROID_LINK_DROP_POLL_MS);
1985
+ }
1986
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE link drop', {
1987
+ connectIdSuffix: uuid.slice(-8),
1988
+ reason,
1989
+ stopped: this.stopped,
1990
+ });
1991
+ });
1592
1992
  }
1593
1993
  connectWithTimeout(uuid, connect) {
1594
1994
  return __awaiter(this, void 0, void 0, function* () {
1995
+ if (this.stopped)
1996
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1997
+ const startedAt = Date.now();
1998
+ let succeeded = false;
1595
1999
  let timer;
1596
2000
  let timedOut = false;
1597
2001
  const pending = connect();
@@ -1606,6 +2010,7 @@ class ReactNativeBleTransport {
1606
2010
  }, BLE_CONNECT_TIMEOUT_MS);
1607
2011
  }),
1608
2012
  ]);
2013
+ succeeded = true;
1609
2014
  return result;
1610
2015
  }
1611
2016
  catch (error) {
@@ -1620,11 +2025,19 @@ class ReactNativeBleTransport {
1620
2025
  finally {
1621
2026
  if (timer)
1622
2027
  clearTimeout(timer);
2028
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] connect completed', {
2029
+ connectIdSuffix: uuid.slice(-8),
2030
+ elapsedMs: Date.now() - startedAt,
2031
+ succeeded,
2032
+ backstopExpired: timedOut,
2033
+ });
1623
2034
  }
1624
2035
  });
1625
2036
  }
1626
2037
  resolveCharacteristicsWithTimeout(uuid, device) {
1627
2038
  return __awaiter(this, void 0, void 0, function* () {
2039
+ const startedAt = Date.now();
2040
+ let succeeded = false;
1628
2041
  let timer;
1629
2042
  let timedOut = false;
1630
2043
  const pending = this.resolveCharacteristics(device);
@@ -1640,6 +2053,7 @@ class ReactNativeBleTransport {
1640
2053
  }),
1641
2054
  ]);
1642
2055
  this.connectionSetupTimeoutCounts.delete(uuid);
2056
+ succeeded = true;
1643
2057
  return result;
1644
2058
  }
1645
2059
  catch (error) {
@@ -1649,11 +2063,26 @@ class ReactNativeBleTransport {
1649
2063
  throw this.createWedgedBleSetupError();
1650
2064
  }
1651
2065
  }
2066
+ if (isNativeBleStaleBondError(error) || hdShared.isBleStaleBondHardwareError(error)) {
2067
+ throw toBleStaleBondHardwareError(error);
2068
+ }
2069
+ if (isNativeBleDisconnectError(error)) {
2070
+ throw toBleDisconnectHardwareError(error);
2071
+ }
2072
+ if (reactNative.Platform.OS === 'android' && isMissingGattShapeError(error)) {
2073
+ this.androidGattCacheRefreshes.add(uuid);
2074
+ }
1652
2075
  throw error;
1653
2076
  }
1654
2077
  finally {
1655
2078
  if (timer)
1656
2079
  clearTimeout(timer);
2080
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] GATT setup completed', {
2081
+ connectIdSuffix: uuid.slice(-8),
2082
+ elapsedMs: Date.now() - startedAt,
2083
+ succeeded,
2084
+ backstopExpired: timedOut,
2085
+ });
1657
2086
  }
1658
2087
  });
1659
2088
  }
@@ -1755,6 +2184,8 @@ class ReactNativeBleTransport {
1755
2184
  }
1756
2185
  }
1757
2186
  resetPlxManager() {
2187
+ if (bleManagerResetPromise)
2188
+ return;
1758
2189
  const manager = this.blePlxManager;
1759
2190
  this.blePlxManager = undefined;
1760
2191
  const reason = 'React Native BLE manager reset';
@@ -1792,16 +2223,25 @@ class ReactNativeBleTransport {
1792
2223
  this.acquiringProtocolV2.clear();
1793
2224
  this.sessionProtocols.clear();
1794
2225
  this.protocolReprobeFailures.clear();
2226
+ this.silentDetections.clear();
1795
2227
  this.writeTimeoutCounts.clear();
1796
2228
  this.connectionSetupTimeoutCounts.clear();
1797
2229
  this.monitorTokens.clear();
1798
2230
  this.protocolV2Assemblers.clear();
2231
+ let reset;
1799
2232
  try {
1800
- manager === null || manager === void 0 ? void 0 : manager.destroy();
2233
+ reset = Promise.resolve(manager === null || manager === void 0 ? void 0 : manager.destroy());
1801
2234
  }
1802
2235
  catch (error) {
1803
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
2236
+ reset = Promise.reject(error);
1804
2237
  }
2238
+ bleManagerResetPromise = reset;
2239
+ reset.then(() => {
2240
+ if (bleManagerResetPromise === reset)
2241
+ bleManagerResetPromise = undefined;
2242
+ }, error => {
2243
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE manager destroy failed:', error);
2244
+ });
1805
2245
  }
1806
2246
  createProtocolMismatchError(expected) {
1807
2247
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
@@ -1857,6 +2297,7 @@ class ReactNativeBleTransport {
1857
2297
  !protocolHint &&
1858
2298
  reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1859
2299
  const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
2300
+ yield this.wakeSilentProtocolV1Device(uuid, probeOrder);
1860
2301
  for (let i = 0; i < probeOrder.length; i += 1) {
1861
2302
  const protocol = probeOrder[i];
1862
2303
  if (i > 0) {
@@ -1876,6 +2317,7 @@ class ReactNativeBleTransport {
1876
2317
  this.confirmedProtocolV2.add(uuid);
1877
2318
  }
1878
2319
  this.protocolReprobeFailures.delete(uuid);
2320
+ this.silentDetections.delete(uuid);
1879
2321
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1880
2322
  deviceId: uuid,
1881
2323
  protocol,
@@ -1884,6 +2326,8 @@ class ReactNativeBleTransport {
1884
2326
  return protocol;
1885
2327
  }
1886
2328
  }
2329
+ if (!this.silentDetections.has(uuid))
2330
+ this.silentDetections.set(uuid, 'silent');
1887
2331
  if (trustSessionProtocol) {
1888
2332
  this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1889
2333
  }
@@ -1895,6 +2339,30 @@ class ReactNativeBleTransport {
1895
2339
  throw this.createProtocolDetectionError();
1896
2340
  });
1897
2341
  }
2342
+ wakeSilentProtocolV1Device(uuid, probeOrder) {
2343
+ return __awaiter(this, void 0, void 0, function* () {
2344
+ if (reactNative.Platform.OS !== 'android' ||
2345
+ probeOrder[0] !== 'V1' ||
2346
+ this.silentDetections.get(uuid) !== 'silent') {
2347
+ return;
2348
+ }
2349
+ this.silentDetections.set(uuid, 'woken');
2350
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] sending Protocol V1 Initialize wake', {
2351
+ connectIdSuffix: uuid.slice(-8),
2352
+ });
2353
+ try {
2354
+ this.probingProtocols.set(uuid, 'V1');
2355
+ yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
2356
+ }
2357
+ catch (error) {
2358
+ if (shouldRethrowProtocolProbeError(error))
2359
+ throw error;
2360
+ }
2361
+ finally {
2362
+ this.clearProbeProtocol(uuid, 'V1');
2363
+ }
2364
+ });
2365
+ }
1898
2366
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1899
2367
  var _a, _b, _c;
1900
2368
  return __awaiter(this, void 0, void 0, function* () {
@@ -1951,7 +2419,7 @@ class ReactNativeBleTransport {
1951
2419
  catch (error) {
1952
2420
  this.clearProbeProtocol(uuid, 'V1');
1953
2421
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1954
- if (isWedgedWriteError(error)) {
2422
+ if (shouldRethrowProtocolProbeError(error)) {
1955
2423
  throw error;
1956
2424
  }
1957
2425
  return false;
@@ -1977,7 +2445,7 @@ class ReactNativeBleTransport {
1977
2445
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1978
2446
  this.resetProtocolV2Frames(uuid);
1979
2447
  },
1980
- shouldRethrow: hdShared.isBleStaleBondHardwareError,
2448
+ shouldRethrow: shouldRethrowProtocolProbeError,
1981
2449
  });
1982
2450
  if (!detected) {
1983
2451
  this.clearProbeProtocol(uuid, 'V2');
@@ -2104,6 +2572,9 @@ class ReactNativeBleTransport {
2104
2572
  this.rememberStaleBondError(uuid, bondError);
2105
2573
  throw bondError;
2106
2574
  }
2575
+ if (isNativeBleDisconnectError(error)) {
2576
+ throw toBleDisconnectHardwareError(error);
2577
+ }
2107
2578
  if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2108
2579
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2109
2580
  throw error;
@@ -2146,6 +2617,7 @@ class ReactNativeBleTransport {
2146
2617
  if (!this._messages || !this._messagesV2) {
2147
2618
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
2148
2619
  }
2620
+ const isProtocolProbe = this.probingProtocols.get(uuid) === 'V2';
2149
2621
  const callOptions = options;
2150
2622
  const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
2151
2623
  if (highThroughputWrite) {
@@ -2186,6 +2658,14 @@ class ReactNativeBleTransport {
2186
2658
  }
2187
2659
  catch (e) {
2188
2660
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
2661
+ if (!isProtocolProbe &&
2662
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
2663
+ !this.monitorTokens.has(uuid)) {
2664
+ yield this.runLifecycleOperation(uuid, () => __awaiter(this, void 0, void 0, function* () {
2665
+ if (!this.monitorTokens.has(uuid))
2666
+ yield this.disconnectUnlocked(uuid);
2667
+ }));
2668
+ }
2189
2669
  throw e;
2190
2670
  }
2191
2671
  finally {
@@ -2200,7 +2680,7 @@ class ReactNativeBleTransport {
2200
2680
  const transport = this.getCachedTransport(uuid);
2201
2681
  if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
2202
2682
  return;
2203
- const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
2683
+ const { device: refreshedDevice } = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1, transactionId => { var _a; return (_a = this.blePlxManager) === null || _a === void 0 ? void 0 : _a.cancelTransaction(transactionId); });
2204
2684
  transport.device = refreshedDevice;
2205
2685
  transport.mtuSize =
2206
2686
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
@@ -2317,6 +2797,8 @@ class ReactNativeBleTransport {
2317
2797
  }
2318
2798
  }
2319
2799
 
2800
+ exports.ANDROID_LINK_DROP_QUIET_MS = ANDROID_LINK_DROP_QUIET_MS;
2801
+ exports.ANDROID_MTU_EXCHANGE_TIMEOUT_MS = ANDROID_MTU_EXCHANGE_TIMEOUT_MS;
2320
2802
  exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2321
2803
  exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2322
2804
  exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;