@onekeyfe/hd-transport-react-native 1.2.3-alpha.1 → 1.2.3-alpha.11

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;
@@ -258,6 +316,7 @@ class BleTransport {
258
316
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
259
317
  const Log = bleLogger;
260
318
  const transportCache = {};
319
+ let bleManagerResetPromise;
261
320
  const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 : 5;
262
321
  const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
263
322
  const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
@@ -316,6 +375,43 @@ const WEDGED_WRITE_MESSAGE = 'BLE write timeout after';
316
375
  const isWedgedWriteError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleWriteCharacteristicError &&
317
376
  typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
318
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
+ };
389
+ const logIosBleBondDiagnostic = (phase, error, outcome) => {
390
+ if (reactNative.Platform.OS !== 'ios')
391
+ return;
392
+ const nativeError = (error !== null && error !== void 0 ? error : {});
393
+ const scalar = (value) => typeof value === 'string' || typeof value === 'number' ? value : undefined;
394
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe error', {
395
+ phase,
396
+ outcome,
397
+ errorName: scalar(nativeError.name),
398
+ errorCode: scalar(nativeError.errorCode),
399
+ attErrorCode: scalar(nativeError.attErrorCode),
400
+ iosErrorCode: scalar(nativeError.iosErrorCode),
401
+ androidErrorCode: scalar(nativeError.androidErrorCode),
402
+ nativeStaleBond: isNativeBleStaleBondError(error),
403
+ hardwareStaleBond: hdShared.isBleStaleBondHardwareError(error),
404
+ nativeDisconnect: isNativeBleDisconnectError(error),
405
+ });
406
+ };
407
+ const isIosProbeDisconnectError = (error) => {
408
+ if (reactNative.Platform.OS !== 'ios')
409
+ return false;
410
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
411
+ return (isNativeBleDisconnectError(error) ||
412
+ code === hdShared.HardwareErrorCode.BleDeviceNotBonded ||
413
+ code === hdShared.HardwareErrorCode.BleDeviceDisconnected);
414
+ };
319
415
  const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
320
416
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
321
417
  const IOS_NOTIFY_READY_DELAY_MS = 150;
@@ -350,10 +446,10 @@ function getDeviceDisplayName(device) {
350
446
  }
351
447
  const IOS_REQUEST_MTU = 247;
352
448
  const ANDROID_REQUEST_MTU = 517;
353
- const BLE_MTU_REFRESH_RETRY_DELAY_MS = 200;
354
449
  const ANDROID_HIGH_PRIORITY_IDLE_MS = 1000;
355
450
  const getRequestedBleMtu = () => reactNative.Platform.OS === 'android' ? ANDROID_REQUEST_MTU : IOS_REQUEST_MTU;
356
451
  const BLE_NATIVE_CONNECT_TIMEOUT_MS = 3000;
452
+ const BLE_MTU_REQUEST_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS;
357
453
  const connectOptions = {
358
454
  requestMTU: getRequestedBleMtu(),
359
455
  timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
@@ -362,6 +458,29 @@ const connectOptions = {
362
458
  const fallbackConnectOptions = {
363
459
  timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
364
460
  };
461
+ const androidRefreshGattConnectOptions = {
462
+ timeout: BLE_NATIVE_CONNECT_TIMEOUT_MS,
463
+ refreshGatt: 'OnConnected',
464
+ };
465
+ const ANDROID_MTU_EXCHANGE_TIMEOUT_MS = 12000;
466
+ const ANDROID_LINK_DROP_QUIET_MS = 5000;
467
+ const ANDROID_LINK_DROP_POLL_MS = 250;
468
+ const isKnownDefaultMtu = (mtu) => typeof mtu === 'number' && Number.isFinite(mtu) && mtu <= 23;
469
+ const isMissingGattShapeError = (error) => {
470
+ const code = error === null || error === void 0 ? void 0 : error.errorCode;
471
+ const message = error === null || error === void 0 ? void 0 : error.message;
472
+ return (code === hdShared.HardwareErrorCode.BleServiceNotFound ||
473
+ code === hdShared.HardwareErrorCode.BleCharacteristicNotFound ||
474
+ (typeof message === 'string' &&
475
+ (message.includes('BLECharacteristicNotFound') ||
476
+ message.includes('BLECharacteristicNotWritable') ||
477
+ message.includes('BLECharacteristicNotNotifiable'))));
478
+ };
479
+ const isStaleGattTableNotifyReason = (reason) => !!reason &&
480
+ (reason.includes('Cannot write client characteristic config descriptor') ||
481
+ reason.includes('Cannot find client characteristic config descriptor') ||
482
+ reason.includes('The handle is invalid') ||
483
+ reason.includes('Writing is not permitted'));
365
484
  const BLE_CONNECT_TIMEOUT_MS = BLE_NATIVE_CONNECT_TIMEOUT_MS * 2 + 2000;
366
485
  const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
367
486
  const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
@@ -376,6 +495,11 @@ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? v
376
495
  error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
377
496
  const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
378
497
  const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
498
+ const isMtuOrCancelledConnectError = (error) => {
499
+ const errorCode = error === null || error === void 0 ? void 0 : error.errorCode;
500
+ return (errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
501
+ errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled);
502
+ };
379
503
  const tryToGetConfiguration = (device) => {
380
504
  if (!device || !device.serviceUUIDs)
381
505
  return null;
@@ -387,25 +511,65 @@ const tryToGetConfiguration = (device) => {
387
511
  return null;
388
512
  return infos;
389
513
  };
390
- const requestNegotiatedMtu = (device, stage, attempt) => __awaiter(void 0, void 0, void 0, function* () {
514
+ const requestNegotiatedMtu = (device, stage, attempt, cancelTransaction) => __awaiter(void 0, void 0, void 0, function* () {
391
515
  if (reactNative.Platform.OS !== 'ios' && reactNative.Platform.OS !== 'android')
392
- return device;
516
+ return { device, timedOut: false };
517
+ const transactionId = `${device.id}:mtu:${stage}:${attempt}:${Date.now()}`;
518
+ let timeoutId;
519
+ let timedOut = false;
393
520
  try {
394
- const mtuDevice = yield device.requestMTU(getRequestedBleMtu());
395
- return mtuDevice;
521
+ const request = device.requestMTU(getRequestedBleMtu(), transactionId);
522
+ request.catch(() => undefined);
523
+ const mtuDevice = yield Promise.race([
524
+ request,
525
+ new Promise((_, reject) => {
526
+ timeoutId = setTimeout(() => {
527
+ timedOut = true;
528
+ reject(new Error(`BLE MTU request timeout after ${BLE_MTU_REQUEST_TIMEOUT_MS}ms`));
529
+ }, BLE_MTU_REQUEST_TIMEOUT_MS);
530
+ }),
531
+ ]);
532
+ return { device: mtuDevice, timedOut: false };
396
533
  }
397
534
  catch (error) {
535
+ if (timedOut && cancelTransaction) {
536
+ try {
537
+ Promise.resolve(cancelTransaction(transactionId)).catch(cancelError => {
538
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
539
+ platform: reactNative.Platform.OS,
540
+ stage,
541
+ attempt,
542
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError),
543
+ });
544
+ });
545
+ }
546
+ catch (cancelError) {
547
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU cancellation failed', {
548
+ platform: reactNative.Platform.OS,
549
+ stage,
550
+ attempt,
551
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError),
552
+ });
553
+ }
554
+ }
398
555
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] MTU refresh failed, continuing with current value', {
399
556
  platform: reactNative.Platform.OS,
400
557
  stage,
401
558
  attempt,
402
559
  actual: device.mtu,
560
+ timedOut,
403
561
  error: error instanceof Error ? error.message : String(error),
404
562
  });
405
- return device;
563
+ return { device, timedOut };
564
+ }
565
+ finally {
566
+ if (timeoutId)
567
+ clearTimeout(timeoutId);
406
568
  }
407
569
  });
408
- const resolveNegotiatedMtu = (device) => requestNegotiatedMtu(device, 'connected', 0);
570
+ const resolveNegotiatedMtu = (device, cancelTransaction) => shouldRefreshNegotiatedMtu(device.mtu)
571
+ ? requestNegotiatedMtu(device, 'connected', 0, cancelTransaction)
572
+ : Promise.resolve({ device, timedOut: false });
409
573
  function remapError(error) {
410
574
  var _a;
411
575
  if (error instanceof reactNativeBlePlx.BleError) {
@@ -429,6 +593,7 @@ class ReactNativeBleTransport {
429
593
  this.name = 'ReactNativeBleTransport';
430
594
  this.configured = false;
431
595
  this.stopped = false;
596
+ this.bondAbortController = new AbortController();
432
597
  this.scanTimeout = DEVICE_SCAN_TIMEOUT_MS;
433
598
  this.runPromise = null;
434
599
  this.runPromiseDeviceId = null;
@@ -441,6 +606,9 @@ class ReactNativeBleTransport {
441
606
  this.sessionProtocols = new Map();
442
607
  this.confirmedProtocolV2 = new Set();
443
608
  this.protocolReprobeFailures = new Map();
609
+ this.silentDetections = new Map();
610
+ this.iosProbeDisconnectEvidence = new Map();
611
+ this.androidGattCacheRefreshes = new Set();
444
612
  this.staleBondErrors = new Map();
445
613
  this.acquiringProtocolV2 = new Set();
446
614
  this.protocolV2Assemblers = new Map();
@@ -458,11 +626,20 @@ class ReactNativeBleTransport {
458
626
  },
459
627
  classifyError: () => 'link-fatal',
460
628
  onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
461
- var _b;
629
+ var _b, _c;
462
630
  (_b = this.protocolV2Assemblers.get(uuid)) === null || _b === void 0 ? void 0 : _b.reset();
463
631
  this.rejectProtocolV2Frames(uuid, new Error(reason));
464
632
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V2 link invalidated:', uuid, reason);
465
633
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
634
+ if (this.probingProtocols.get(uuid) !== 'V2') {
635
+ const transport = transportCache[uuid];
636
+ try {
637
+ 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);
638
+ }
639
+ catch (_d) {
640
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 disconnect listener failed');
641
+ }
642
+ }
466
643
  yield this.releaseNative(uuid, true);
467
644
  }
468
645
  }),
@@ -474,6 +651,7 @@ class ReactNativeBleTransport {
474
651
  this.androidPriorityResetTimers = new Map();
475
652
  this.nextMonitorToken = 1;
476
653
  this.lifecycleOperations = new Map();
654
+ this.scanCleanups = new Set();
477
655
  this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : DEVICE_SCAN_TIMEOUT_MS;
478
656
  }
479
657
  init(logger, emitter) {
@@ -502,10 +680,38 @@ class ReactNativeBleTransport {
502
680
  listen() {
503
681
  }
504
682
  getPlxManager() {
505
- if (this.blePlxManager)
506
- return Promise.resolve(this.blePlxManager);
507
- this.blePlxManager = new reactNativeBlePlx.BleManager();
508
- return Promise.resolve(this.blePlxManager);
683
+ return __awaiter(this, void 0, void 0, function* () {
684
+ while (bleManagerResetPromise) {
685
+ yield this.waitForManagerReset();
686
+ }
687
+ if (this.stopped)
688
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
689
+ if (!this.blePlxManager)
690
+ this.blePlxManager = new reactNativeBlePlx.BleManager();
691
+ return this.blePlxManager;
692
+ });
693
+ }
694
+ waitForManagerReset() {
695
+ return __awaiter(this, void 0, void 0, function* () {
696
+ if (!bleManagerResetPromise)
697
+ return;
698
+ let timeout;
699
+ try {
700
+ yield Promise.race([
701
+ bleManagerResetPromise,
702
+ new Promise((_, reject) => {
703
+ timeout = setTimeout(() => reject(this.createWedgedBleSetupError()), BLE_CONNECT_TIMEOUT_MS);
704
+ }),
705
+ ]);
706
+ }
707
+ catch (_a) {
708
+ throw this.createWedgedBleSetupError();
709
+ }
710
+ finally {
711
+ if (timeout)
712
+ clearTimeout(timeout);
713
+ }
714
+ });
509
715
  }
510
716
  resolveCharacteristics(device) {
511
717
  return __awaiter(this, void 0, void 0, function* () {
@@ -670,30 +876,55 @@ class ReactNativeBleTransport {
670
876
  }
671
877
  enumerate() {
672
878
  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
- }
879
+ const scanStartedAt = Date.now();
880
+ let firstDeviceMs;
881
+ const blePlxManager = yield this.getPlxManager();
882
+ yield subscribeBleOn(blePlxManager);
883
+ if (reactNative.Platform.OS === 'android' && reactNative.Platform.Version >= 31) {
884
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, please wait...');
885
+ const resultConnect = yield reactNative.PermissionsAndroid.requestMultiple([
886
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
887
+ reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
888
+ ]);
889
+ Log === null || Log === void 0 ? void 0 : Log.debug('requesting permissions, result: ', resultConnect);
890
+ if (resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
891
+ resultConnect[reactNative.PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted') {
892
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
696
893
  }
894
+ }
895
+ if (this.stopped)
896
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
897
+ return new Promise((resolve, reject) => {
898
+ const deviceList = [];
899
+ let finished = false;
900
+ let scanCleanup;
901
+ const finishScan = (error) => {
902
+ if (scanCleanup)
903
+ return scanCleanup;
904
+ finished = true;
905
+ clearScanTimer();
906
+ scanCleanup = this.runNativeTeardown('scan', blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
907
+ yield blePlxManager.stopDeviceScan();
908
+ })).then(() => {
909
+ this.scanCleanups.delete(cancelScan);
910
+ if (error)
911
+ reject(error);
912
+ else
913
+ resolve(deviceList);
914
+ });
915
+ return scanCleanup;
916
+ };
917
+ const cancelScan = () => finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
918
+ this.scanCleanups.add(cancelScan);
919
+ const clearScanTimer = timer.timeout(() => {
920
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] scan completed', {
921
+ elapsedMs: Date.now() - scanStartedAt,
922
+ firstDeviceMs,
923
+ deviceCount: deviceList.length,
924
+ scanWindowMs: this.scanTimeout,
925
+ });
926
+ finishScan();
927
+ }, this.scanTimeout);
697
928
  blePlxManager.startDeviceScan(getBluetoothServiceUuids(), {
698
929
  allowDuplicates: true,
699
930
  scanMode: reactNativeBlePlx.ScanMode.LowLatency,
@@ -702,19 +933,17 @@ class ReactNativeBleTransport {
702
933
  if (error) {
703
934
  Log === null || Log === void 0 ? void 0 : Log.debug('ble scan error: ', error);
704
935
  if ([reactNativeBlePlx.BleErrorCode.BluetoothPoweredOff, reactNativeBlePlx.BleErrorCode.BluetoothInUnknownState].includes(error.errorCode)) {
705
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
936
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError));
706
937
  }
707
938
  else if (error.errorCode === reactNativeBlePlx.BleErrorCode.BluetoothUnauthorized) {
708
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
939
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError));
709
940
  }
710
941
  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);
942
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationServicesDisabled));
715
943
  }
944
+ else if (error.errorCode === reactNativeBlePlx.BleErrorCode.ScanStartFailed) ;
716
945
  else {
717
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
946
+ finishScan(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleScanError, (_a = error.reason) !== null && _a !== void 0 ? _a : ''));
718
947
  }
719
948
  return;
720
949
  }
@@ -739,6 +968,8 @@ class ReactNativeBleTransport {
739
968
  });
740
969
  }
741
970
  });
971
+ if (finished)
972
+ return;
742
973
  getConnectedDeviceIds(reactNative.Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(devices => {
743
974
  for (const device of devices) {
744
975
  const localName = 'localName' in device && typeof device.localName === 'string'
@@ -754,10 +985,11 @@ class ReactNativeBleTransport {
754
985
  addDevice(device);
755
986
  }
756
987
  }
757
- });
988
+ }, error => Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral failed:', error));
758
989
  const addDevice = (device) => {
759
990
  var _a;
760
- if (deviceList.every(d => d.id !== device.id)) {
991
+ if (!finished && deviceList.every(d => d.id !== device.id)) {
992
+ firstDeviceMs !== null && firstDeviceMs !== void 0 ? firstDeviceMs : (firstDeviceMs = Date.now() - scanStartedAt);
761
993
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
762
994
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
763
995
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
@@ -767,16 +999,14 @@ class ReactNativeBleTransport {
767
999
  });
768
1000
  }
769
1001
  };
770
- timer.timeout(() => {
771
- blePlxManager.stopDeviceScan();
772
- resolve(deviceList);
773
- }, this.scanTimeout);
774
- }));
1002
+ });
775
1003
  });
776
1004
  }
777
1005
  installTransportForAcquire(uuid, device, characteristics) {
778
1006
  return __awaiter(this, void 0, void 0, function* () {
779
1007
  const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, device));
1008
+ if (this.stopped)
1009
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
780
1010
  const transport$1 = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
781
1011
  transport$1.mtuSize = typeof device.mtu === 'number' ? device.mtu : undefined;
782
1012
  const monitorToken = this.nextMonitorToken;
@@ -797,30 +1027,12 @@ class ReactNativeBleTransport {
797
1027
  else if (reactNative.Platform.OS === 'android') {
798
1028
  yield delay(ANDROID_NOTIFY_READY_DELAY_MS);
799
1029
  }
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
- }
1030
+ if (this.stopped)
1031
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
818
1032
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE MTU ready', {
819
1033
  platform: reactNative.Platform.OS,
820
1034
  requested: getRequestedBleMtu(),
821
- initial: initialMtu,
822
1035
  actual: transport$1.mtuSize,
823
- refreshAttempts,
824
1036
  });
825
1037
  return transport$1;
826
1038
  });
@@ -837,6 +1049,8 @@ class ReactNativeBleTransport {
837
1049
  acquireUnlocked(input) {
838
1050
  var _a;
839
1051
  return __awaiter(this, void 0, void 0, function* () {
1052
+ if (this.stopped)
1053
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
840
1054
  const { uuid, forceCleanRunPromise, expectedProtocol, skipProtocolProbe } = input;
841
1055
  const shouldMapProtocolV2StaleBond = expectedProtocol
842
1056
  ? expectedProtocol === 'V2'
@@ -868,9 +1082,13 @@ class ReactNativeBleTransport {
868
1082
  const isCachedDeviceConnected = yield cachedTransport.device
869
1083
  .isConnected()
870
1084
  .catch(() => false);
1085
+ const isCachedAndroidLinkUsable = reactNative.Platform.OS !== 'android' || !this.androidGattCacheRefreshes.has(uuid);
871
1086
  if (isCachedDeviceConnected &&
1087
+ isCachedAndroidLinkUsable &&
872
1088
  cachedProtocol &&
873
1089
  (!expectedProtocol || cachedProtocol === expectedProtocol)) {
1090
+ if (this.stopped)
1091
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
874
1092
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] reuse cached BLE transport:', uuid, cachedProtocol);
875
1093
  return { uuid, protocolType: cachedProtocol };
876
1094
  }
@@ -879,6 +1097,15 @@ class ReactNativeBleTransport {
879
1097
  }
880
1098
  }
881
1099
  let device = null;
1100
+ const isAndroid = reactNative.Platform.OS === 'android';
1101
+ const refreshAndroidGattCache = isAndroid && (!!skipProtocolProbe || this.androidGattCacheRefreshes.has(uuid));
1102
+ let nativeConnectOptions = connectOptions;
1103
+ if (isAndroid) {
1104
+ nativeConnectOptions = refreshAndroidGattCache
1105
+ ? androidRefreshGattConnectOptions
1106
+ : fallbackConnectOptions;
1107
+ }
1108
+ let androidRefreshConnectRan = false;
882
1109
  if (forceCleanRunPromise && this.runPromise) {
883
1110
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
884
1111
  this.runPromise.reject(error);
@@ -887,6 +1114,7 @@ class ReactNativeBleTransport {
887
1114
  Log === null || Log === void 0 ? void 0 : Log.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
888
1115
  }
889
1116
  const blePlxManager = yield this.getPlxManager();
1117
+ let skipPostConnectMtu = false;
890
1118
  try {
891
1119
  yield subscribeBleOn(blePlxManager);
892
1120
  }
@@ -894,6 +1122,27 @@ class ReactNativeBleTransport {
894
1122
  Log === null || Log === void 0 ? void 0 : Log.debug('subscribeBleOn error: ', error);
895
1123
  throw error;
896
1124
  }
1125
+ if (this.stopped)
1126
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1127
+ if (reactNative.Platform.OS === 'android') {
1128
+ try {
1129
+ const bondState = yield pairDevice(uuid);
1130
+ if (bondState.bonding) {
1131
+ yield onDeviceBondState(uuid, this.bondAbortController.signal);
1132
+ }
1133
+ else if (!bondState.bonded) {
1134
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1135
+ }
1136
+ }
1137
+ catch (error) {
1138
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1139
+ yield this.runBestEffortNativeOperation('bond failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid));
1140
+ }));
1141
+ throw error;
1142
+ }
1143
+ }
1144
+ if (this.stopped)
1145
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
897
1146
  if (!device) {
898
1147
  const devices = yield blePlxManager.devices([uuid]);
899
1148
  [device] = devices;
@@ -907,15 +1156,16 @@ class ReactNativeBleTransport {
907
1156
  if (!device) {
908
1157
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device: ', uuid);
909
1158
  try {
910
- device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, connectOptions));
1159
+ device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, nativeConnectOptions));
1160
+ androidRefreshConnectRan = refreshAndroidGattCache;
911
1161
  }
912
1162
  catch (e) {
913
1163
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
914
1164
  if (shouldRethrowBleSetupError(e)) {
915
1165
  throw e;
916
1166
  }
917
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
918
- e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1167
+ if (isMtuOrCancelledConnectError(e)) {
1168
+ skipPostConnectMtu = true;
919
1169
  Log === null || Log === void 0 ? void 0 : Log.debug('first try to reconnect without params');
920
1170
  device = yield this.connectWithTimeout(uuid, () => blePlxManager.connectToDevice(uuid, fallbackConnectOptions));
921
1171
  }
@@ -931,19 +1181,27 @@ class ReactNativeBleTransport {
931
1181
  if (!device) {
932
1182
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'unable to connect to device');
933
1183
  }
1184
+ if (refreshAndroidGattCache &&
1185
+ !androidRefreshConnectRan &&
1186
+ (yield device.isConnected().catch(() => false))) {
1187
+ yield this.dropAndroidLink(uuid, blePlxManager, device, 'gatt cache refresh');
1188
+ if (this.stopped)
1189
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1190
+ }
934
1191
  if (!(yield device.isConnected())) {
935
1192
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device: ', uuid);
936
1193
  const disconnectedDevice = device;
937
1194
  try {
938
- device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(connectOptions));
1195
+ device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(nativeConnectOptions));
1196
+ androidRefreshConnectRan = refreshAndroidGattCache;
939
1197
  }
940
1198
  catch (e) {
941
1199
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
942
1200
  if (shouldRethrowBleSetupError(e)) {
943
1201
  throw e;
944
1202
  }
945
- if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
946
- e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
1203
+ if (isMtuOrCancelledConnectError(e)) {
1204
+ skipPostConnectMtu = true;
947
1205
  Log === null || Log === void 0 ? void 0 : Log.debug('second try to reconnect without params');
948
1206
  try {
949
1207
  device = yield this.connectWithTimeout(uuid, () => disconnectedDevice.connect(fallbackConnectOptions));
@@ -965,33 +1223,70 @@ class ReactNativeBleTransport {
965
1223
  }
966
1224
  }
967
1225
  }
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`);
1226
+ if (this.stopped)
1227
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1228
+ if (reactNative.Platform.OS === 'android' && !(yield device.isConnected().catch(() => false))) {
1229
+ const disconnectedDevice = device;
1230
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1231
+ yield Promise.all([
1232
+ this.runBestEffortNativeOperation('connect failure: cancel manager connection', () => blePlxManager.cancelDeviceConnection(uuid)),
1233
+ this.runBestEffortNativeOperation('connect failure: cancel device connection', () => disconnectedDevice.cancelConnection()),
1234
+ ]);
1235
+ }));
1236
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, 'device is not connected');
1237
+ }
1238
+ if (this.stopped)
1239
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1240
+ let characteristics;
1241
+ if (isAndroid) {
1242
+ if (refreshAndroidGattCache) {
1243
+ characteristics = yield this.resolveCharacteristicsWithTimeout(uuid, device);
1244
+ if (androidRefreshConnectRan)
1245
+ this.androidGattCacheRefreshes.delete(uuid);
1246
+ if (this.stopped)
1247
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1248
+ }
1249
+ device = yield this.negotiateAndroidMtu(uuid, blePlxManager, device);
1250
+ }
1251
+ else if (!skipPostConnectMtu) {
1252
+ const mtuResult = yield resolveNegotiatedMtu(device, transactionId => blePlxManager.cancelTransaction(transactionId));
1253
+ device = mtuResult.device;
1254
+ if (mtuResult.timedOut) {
1255
+ if (this.stopped)
1256
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1257
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] post-connect MTU timed out, reconnecting without requesting MTU');
1258
+ const timedOutDevice = device;
1259
+ let mtuTeardownSettled = false;
1260
+ yield this.runNativeTeardown(uuid, blePlxManager, () => __awaiter(this, void 0, void 0, function* () {
1261
+ yield this.runBestEffortNativeOperation('mtu timeout: cancel device connection', () => timedOutDevice.cancelConnection());
1262
+ mtuTeardownSettled = true;
1263
+ }));
1264
+ if (this.stopped)
1265
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1266
+ if (!mtuTeardownSettled || this.blePlxManager !== blePlxManager) {
1267
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'BLE MTU cleanup timed out');
973
1268
  }
974
- const bondState = yield pairDevice(uuid);
975
- if (bondState.bonding) {
976
- yield onDeviceBondState(uuid);
1269
+ try {
1270
+ device = yield this.connectWithTimeout(uuid, () => timedOutDevice.connect(fallbackConnectOptions));
977
1271
  }
978
- else if (!bondState.bonded) {
979
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1272
+ catch (error) {
1273
+ if (shouldRethrowBleSetupError(error))
1274
+ throw error;
1275
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.DeviceAlreadyConnected) {
1276
+ device = timedOutDevice;
1277
+ }
1278
+ else {
1279
+ remapError(error);
1280
+ }
980
1281
  }
981
1282
  }
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
1283
  }
992
- device = yield resolveNegotiatedMtu(device);
1284
+ if (this.stopped)
1285
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
993
1286
  const acquiredDevice = device;
994
- const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
1287
+ const { writeCharacteristic, notifyCharacteristic } = characteristics !== null && characteristics !== void 0 ? characteristics : (yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice));
1288
+ if (this.stopped)
1289
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
995
1290
  const protocolHint = expectedProtocol
996
1291
  ? undefined
997
1292
  : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
@@ -1034,6 +1329,8 @@ class ReactNativeBleTransport {
1034
1329
  const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint, () => __awaiter(this, void 0, void 0, function* () {
1035
1330
  yield this.installTransportForAcquire(uuid, acquiredDevice);
1036
1331
  }));
1332
+ if (this.stopped)
1333
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1037
1334
  const currentTransport = transportCache[uuid];
1038
1335
  if (!currentTransport) {
1039
1336
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
@@ -1042,12 +1339,9 @@ class ReactNativeBleTransport {
1042
1339
  return { uuid, protocolType };
1043
1340
  }
1044
1341
  catch (error) {
1045
- if (hdShared.isBleStaleBondHardwareError(error)) {
1046
- yield this.disconnectUnlocked(uuid);
1047
- }
1048
- else {
1049
- yield this.releaseUnlocked(uuid, true);
1050
- }
1342
+ yield this.disconnectUnlocked(uuid, {
1343
+ preserveIosProbeDisconnect: this.iosProbeDisconnectEvidence.has(uuid),
1344
+ });
1051
1345
  throw error;
1052
1346
  }
1053
1347
  finally {
@@ -1059,7 +1353,7 @@ class ReactNativeBleTransport {
1059
1353
  let bufferLength = 0;
1060
1354
  let buffer$1 = [];
1061
1355
  const subscription = characteristic.monitor((error, c) => {
1062
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1356
+ var _a, _b, _c, _d, _e, _f;
1063
1357
  const isCurrentMonitor = this.monitorTokens.get(uuid) === monitorToken;
1064
1358
  if (error) {
1065
1359
  Log === null || Log === void 0 ? void 0 : Log.debug(`error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${error}`);
@@ -1076,16 +1370,16 @@ class ReactNativeBleTransport {
1076
1370
  this.rememberStaleBondError(uuid, toBleStaleBondHardwareError(error));
1077
1371
  return;
1078
1372
  }
1373
+ if (reactNative.Platform.OS === 'android' && isStaleGattTableNotifyReason(error.reason)) {
1374
+ this.androidGattCacheRefreshes.add(uuid);
1375
+ }
1079
1376
  if (this.getActiveProtocol(uuid) === 'V2') {
1080
1377
  let errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
1081
1378
  if ((_a = error.reason) === null || _a === void 0 ? void 0 : _a.includes('The connection has timed out unexpectedly')) {
1082
1379
  errorCode = hdShared.HardwareErrorCode.BleTimeoutError;
1083
1380
  }
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'))) {
1381
+ else if (isStaleGattTableNotifyReason(error.reason) ||
1382
+ ((_b = error.reason) === null || _b === void 0 ? void 0 : _b.includes('notify change failed for device'))) {
1089
1383
  errorCode = hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure;
1090
1384
  }
1091
1385
  this.rejectProtocolV2Frames(uuid, hdShared.ERRORS.TypedError(errorCode));
@@ -1093,14 +1387,11 @@ class ReactNativeBleTransport {
1093
1387
  }
1094
1388
  if (this.runPromise && this.runPromiseDeviceId === uuid) {
1095
1389
  let ERROR = hdShared.HardwareErrorCode.BleCharacteristicNotifyError;
1096
- if ((_g = error.reason) === null || _g === void 0 ? void 0 : _g.includes('The connection has timed out unexpectedly')) {
1390
+ if ((_c = error.reason) === null || _c === void 0 ? void 0 : _c.includes('The connection has timed out unexpectedly')) {
1097
1391
  ERROR = hdShared.HardwareErrorCode.BleTimeoutError;
1098
1392
  }
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'))) {
1393
+ if (isStaleGattTableNotifyReason(error.reason) ||
1394
+ ((_d = error.reason) === null || _d === void 0 ? void 0 : _d.includes('notify change failed for device'))) {
1104
1395
  const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
1105
1396
  this.runPromise.reject(notifyError);
1106
1397
  Log === null || Log === void 0 ? void 0 : Log.debug(`${hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`);
@@ -1142,7 +1433,7 @@ class ReactNativeBleTransport {
1142
1433
  bufferLength = 0;
1143
1434
  buffer$1 = [];
1144
1435
  if (this.runPromiseDeviceId === uuid) {
1145
- (_o = this.runPromise) === null || _o === void 0 ? void 0 : _o.resolve(value.toString('hex'));
1436
+ (_e = this.runPromise) === null || _e === void 0 ? void 0 : _e.resolve(value.toString('hex'));
1146
1437
  }
1147
1438
  }
1148
1439
  }
@@ -1153,7 +1444,7 @@ class ReactNativeBleTransport {
1153
1444
  this.rejectProtocolV2Frames(uuid, notifyError);
1154
1445
  }
1155
1446
  else if (this.runPromiseDeviceId === uuid) {
1156
- (_p = this.runPromise) === null || _p === void 0 ? void 0 : _p.reject(notifyError);
1447
+ (_f = this.runPromise) === null || _f === void 0 ? void 0 : _f.reject(notifyError);
1157
1448
  }
1158
1449
  }
1159
1450
  }, notifyTransactionId);
@@ -1429,13 +1720,14 @@ class ReactNativeBleTransport {
1429
1720
  return check.call(jsonData);
1430
1721
  }
1431
1722
  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);
1723
+ const isProbeTimeout = (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS &&
1724
+ (name === 'GetFeatures' || name === 'Initialize');
1725
+ if (isProbeTimeout) {
1726
+ Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] Protocol V1 ${name} probe call failed:`, e);
1434
1727
  }
1435
1728
  else {
1436
1729
  Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
1437
1730
  }
1438
- const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1439
1731
  const isStaleCall = this.runPromise !== runPromise;
1440
1732
  if (!isProbeTimeout &&
1441
1733
  !isStaleCall &&
@@ -1455,14 +1747,51 @@ class ReactNativeBleTransport {
1455
1747
  });
1456
1748
  }
1457
1749
  stop() {
1750
+ var _a;
1751
+ if (this.stopPromise)
1752
+ return this.stopPromise;
1458
1753
  this.stopped = true;
1754
+ this.bondAbortController.abort();
1755
+ const deviceIds = new Set([
1756
+ ...this.monitorTokens.keys(),
1757
+ ...this.sessionProtocols.keys(),
1758
+ ...this.lifecycleOperations.keys(),
1759
+ ...(this.runPromiseDeviceId ? [this.runPromiseDeviceId] : []),
1760
+ ]);
1761
+ const scans = Array.from(this.scanCleanups, cleanup => cleanup());
1762
+ this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
1763
+ this.androidPriorityResetTimers.clear();
1764
+ this.androidHighPriorityDevices.clear();
1765
+ this.iosProbeDisconnectEvidence.clear();
1766
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1767
+ (_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.reject(error);
1768
+ this.runPromise = null;
1769
+ this.runPromiseDeviceId = null;
1770
+ deviceIds.forEach(uuid => this.rejectProtocolV2Frames(uuid, error));
1771
+ const manager = this.blePlxManager;
1772
+ const pendingConnections = manager
1773
+ ? Array.from(this.lifecycleOperations.keys(), uuid => this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1774
+ yield this.runBestEffortNativeOperation('stop: cancel pending device connection', () => manager.cancelDeviceConnection(uuid));
1775
+ })))
1776
+ : [];
1777
+ this.stopPromise = Promise.all([
1778
+ ...scans,
1779
+ ...pendingConnections,
1780
+ ...Array.from(deviceIds, uuid => this.disconnect(uuid)),
1781
+ ]).then(() => __awaiter(this, void 0, void 0, function* () {
1782
+ yield this.protocolV2Links.invalidateAllLinks('React Native BLE transport stopped');
1783
+ yield this.waitForManagerReset();
1784
+ this.blePlxManager = undefined;
1785
+ this.emitter = undefined;
1786
+ }));
1787
+ return this.stopPromise;
1459
1788
  }
1460
1789
  disconnect(session) {
1461
1790
  return __awaiter(this, void 0, void 0, function* () {
1462
1791
  return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
1463
1792
  });
1464
1793
  }
1465
- disconnectUnlocked(session) {
1794
+ disconnectUnlocked(session, options) {
1466
1795
  var _a, _b, _c;
1467
1796
  return __awaiter(this, void 0, void 0, function* () {
1468
1797
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
@@ -1498,6 +1827,9 @@ class ReactNativeBleTransport {
1498
1827
  this.deviceProtocolHints.delete(session);
1499
1828
  this.sessionProtocols.delete(session);
1500
1829
  this.protocolReprobeFailures.delete(session);
1830
+ if (!(options === null || options === void 0 ? void 0 : options.preserveIosProbeDisconnect)) {
1831
+ this.iosProbeDisconnectEvidence.delete(session);
1832
+ }
1501
1833
  this.protocolV2Assemblers.delete(session);
1502
1834
  this.resetProtocolV2Frames(session);
1503
1835
  try {
@@ -1585,13 +1917,93 @@ class ReactNativeBleTransport {
1585
1917
  });
1586
1918
  }
1587
1919
  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;
1920
+ return __awaiter(this, void 0, void 0, function* () {
1921
+ Log === null || Log === void 0 ? void 0 : Log.debug('transport-react-native transport cancel');
1922
+ const pending = this.runPromise;
1923
+ const deviceId = this.runPromiseDeviceId;
1924
+ if (pending) {
1925
+ pending.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled));
1926
+ if (this.runPromise === pending) {
1927
+ this.runPromise = null;
1928
+ this.runPromiseDeviceId = null;
1929
+ }
1930
+ if (deviceId)
1931
+ yield this.disconnect(deviceId);
1932
+ }
1933
+ });
1934
+ }
1935
+ negotiateAndroidMtu(uuid, manager, device) {
1936
+ return __awaiter(this, void 0, void 0, function* () {
1937
+ if (!shouldRefreshNegotiatedMtu(device.mtu))
1938
+ return device;
1939
+ const startedAt = Date.now();
1940
+ let timer;
1941
+ let timedOut = false;
1942
+ let negotiated = device;
1943
+ let failure;
1944
+ try {
1945
+ negotiated = yield Promise.race([
1946
+ device.requestMTU(ANDROID_REQUEST_MTU, `${device.id}:mtu:connected:0:${startedAt}`),
1947
+ new Promise((_, reject) => {
1948
+ timer = setTimeout(() => {
1949
+ timedOut = true;
1950
+ reject(new Error(`BLE MTU exchange timeout after ${ANDROID_MTU_EXCHANGE_TIMEOUT_MS}ms`));
1951
+ }, ANDROID_MTU_EXCHANGE_TIMEOUT_MS);
1952
+ }),
1953
+ ]);
1954
+ }
1955
+ catch (error) {
1956
+ failure = error instanceof Error ? error.message : String(error);
1957
+ }
1958
+ finally {
1959
+ if (timer)
1960
+ clearTimeout(timer);
1961
+ }
1962
+ Log === null || Log === void 0 ? void 0 : Log.debug(`[ReactNativeBleTransport] BLE MTU exchange ${failure ? 'failed' : 'completed'}`, {
1963
+ connectIdSuffix: uuid.slice(-8),
1964
+ elapsedMs: Date.now() - startedAt,
1965
+ timedOut,
1966
+ actual: negotiated.mtu,
1967
+ error: failure,
1968
+ });
1969
+ if (this.stopped)
1970
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1971
+ if (!timedOut && !isKnownDefaultMtu(negotiated.mtu))
1972
+ return negotiated;
1973
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'mtu-backstop' : 'mtu-default');
1974
+ yield this.dropAndroidLink(uuid, manager, negotiated, timedOut ? 'mtu exchange timeout' : 'default mtu');
1975
+ if (resetManager)
1976
+ throw this.createWedgedBleSetupError();
1977
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleConnectedError, timedOut
1978
+ ? 'BLE MTU exchange did not complete, reconnecting on a fresh link'
1979
+ : `BLE link stayed at the default MTU ${negotiated.mtu}, reconnecting on a fresh link`);
1980
+ });
1981
+ }
1982
+ dropAndroidLink(uuid, manager, device, reason) {
1983
+ return __awaiter(this, void 0, void 0, function* () {
1984
+ yield this.runNativeTeardown(uuid, manager, () => __awaiter(this, void 0, void 0, function* () {
1985
+ yield Promise.all([
1986
+ this.runBestEffortNativeOperation(`${reason}: cancel manager connection`, () => manager.cancelDeviceConnection(uuid)),
1987
+ this.runBestEffortNativeOperation(`${reason}: cancel device connection`, () => device.cancelConnection()),
1988
+ ]);
1989
+ }));
1990
+ const startedAt = Date.now();
1991
+ while (!this.stopped && Date.now() - startedAt < ANDROID_LINK_DROP_QUIET_MS) {
1992
+ yield delay(ANDROID_LINK_DROP_POLL_MS);
1993
+ }
1994
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android BLE link drop', {
1995
+ connectIdSuffix: uuid.slice(-8),
1996
+ reason,
1997
+ stopped: this.stopped,
1998
+ });
1999
+ });
1592
2000
  }
1593
2001
  connectWithTimeout(uuid, connect) {
1594
2002
  return __awaiter(this, void 0, void 0, function* () {
2003
+ if (this.stopped)
2004
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
2005
+ const startedAt = Date.now();
2006
+ let succeeded = false;
1595
2007
  let timer;
1596
2008
  let timedOut = false;
1597
2009
  const pending = connect();
@@ -1606,6 +2018,7 @@ class ReactNativeBleTransport {
1606
2018
  }, BLE_CONNECT_TIMEOUT_MS);
1607
2019
  }),
1608
2020
  ]);
2021
+ succeeded = true;
1609
2022
  return result;
1610
2023
  }
1611
2024
  catch (error) {
@@ -1620,11 +2033,19 @@ class ReactNativeBleTransport {
1620
2033
  finally {
1621
2034
  if (timer)
1622
2035
  clearTimeout(timer);
2036
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] connect completed', {
2037
+ connectIdSuffix: uuid.slice(-8),
2038
+ elapsedMs: Date.now() - startedAt,
2039
+ succeeded,
2040
+ backstopExpired: timedOut,
2041
+ });
1623
2042
  }
1624
2043
  });
1625
2044
  }
1626
2045
  resolveCharacteristicsWithTimeout(uuid, device) {
1627
2046
  return __awaiter(this, void 0, void 0, function* () {
2047
+ const startedAt = Date.now();
2048
+ let succeeded = false;
1628
2049
  let timer;
1629
2050
  let timedOut = false;
1630
2051
  const pending = this.resolveCharacteristics(device);
@@ -1640,6 +2061,7 @@ class ReactNativeBleTransport {
1640
2061
  }),
1641
2062
  ]);
1642
2063
  this.connectionSetupTimeoutCounts.delete(uuid);
2064
+ succeeded = true;
1643
2065
  return result;
1644
2066
  }
1645
2067
  catch (error) {
@@ -1649,11 +2071,20 @@ class ReactNativeBleTransport {
1649
2071
  throw this.createWedgedBleSetupError();
1650
2072
  }
1651
2073
  }
2074
+ if (reactNative.Platform.OS === 'android' && isMissingGattShapeError(error)) {
2075
+ this.androidGattCacheRefreshes.add(uuid);
2076
+ }
1652
2077
  throw error;
1653
2078
  }
1654
2079
  finally {
1655
2080
  if (timer)
1656
2081
  clearTimeout(timer);
2082
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] GATT setup completed', {
2083
+ connectIdSuffix: uuid.slice(-8),
2084
+ elapsedMs: Date.now() - startedAt,
2085
+ succeeded,
2086
+ backstopExpired: timedOut,
2087
+ });
1657
2088
  }
1658
2089
  });
1659
2090
  }
@@ -1674,6 +2105,7 @@ class ReactNativeBleTransport {
1674
2105
  this.deviceProtocol.delete(uuid);
1675
2106
  this.probingProtocols.delete(uuid);
1676
2107
  this.staleBondErrors.delete(uuid);
2108
+ this.iosProbeDisconnectEvidence.delete(uuid);
1677
2109
  this.acquiringProtocolV2.delete(uuid);
1678
2110
  this.protocolV2Assemblers.delete(uuid);
1679
2111
  this.resetProtocolV2Frames(uuid);
@@ -1745,6 +2177,7 @@ class ReactNativeBleTransport {
1745
2177
  this.deviceProtocol.delete(uuid);
1746
2178
  this.probingProtocols.delete(uuid);
1747
2179
  this.staleBondErrors.delete(uuid);
2180
+ this.iosProbeDisconnectEvidence.delete(uuid);
1748
2181
  this.acquiringProtocolV2.delete(uuid);
1749
2182
  this.protocolV2Assemblers.delete(uuid);
1750
2183
  this.resetProtocolV2Frames(uuid);
@@ -1755,6 +2188,8 @@ class ReactNativeBleTransport {
1755
2188
  }
1756
2189
  }
1757
2190
  resetPlxManager() {
2191
+ if (bleManagerResetPromise)
2192
+ return;
1758
2193
  const manager = this.blePlxManager;
1759
2194
  this.blePlxManager = undefined;
1760
2195
  const reason = 'React Native BLE manager reset';
@@ -1789,19 +2224,29 @@ class ReactNativeBleTransport {
1789
2224
  this.deviceProtocol.clear();
1790
2225
  this.probingProtocols.clear();
1791
2226
  this.staleBondErrors.clear();
2227
+ this.iosProbeDisconnectEvidence.clear();
1792
2228
  this.acquiringProtocolV2.clear();
1793
2229
  this.sessionProtocols.clear();
1794
2230
  this.protocolReprobeFailures.clear();
2231
+ this.silentDetections.clear();
1795
2232
  this.writeTimeoutCounts.clear();
1796
2233
  this.connectionSetupTimeoutCounts.clear();
1797
2234
  this.monitorTokens.clear();
1798
2235
  this.protocolV2Assemblers.clear();
2236
+ let reset;
1799
2237
  try {
1800
- manager === null || manager === void 0 ? void 0 : manager.destroy();
2238
+ reset = Promise.resolve(manager === null || manager === void 0 ? void 0 : manager.destroy());
1801
2239
  }
1802
2240
  catch (error) {
1803
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
2241
+ reset = Promise.reject(error);
1804
2242
  }
2243
+ bleManagerResetPromise = reset;
2244
+ reset.then(() => {
2245
+ if (bleManagerResetPromise === reset)
2246
+ bleManagerResetPromise = undefined;
2247
+ }, error => {
2248
+ Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE manager destroy failed:', error);
2249
+ });
1805
2250
  }
1806
2251
  createProtocolMismatchError(expected) {
1807
2252
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
@@ -1809,6 +2254,35 @@ class ReactNativeBleTransport {
1809
2254
  createProtocolDetectionError() {
1810
2255
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
1811
2256
  }
2257
+ rememberIosProbeDisconnect(uuid, phase, error) {
2258
+ if (!isIosProbeDisconnectError(error))
2259
+ return undefined;
2260
+ const previous = this.iosProbeDisconnectEvidence.get(uuid);
2261
+ const errorCode = error === null || error === void 0 ? void 0 : error.errorCode;
2262
+ const evidence = Object.assign({ phase }, (typeof errorCode === 'string' || typeof errorCode === 'number' ? { errorCode } : {}));
2263
+ this.iosProbeDisconnectEvidence.set(uuid, evidence);
2264
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe disconnect recorded', {
2265
+ phase,
2266
+ errorCode: evidence.errorCode,
2267
+ repeated: Boolean(previous),
2268
+ });
2269
+ if (!previous)
2270
+ return undefined;
2271
+ return this.consumeIosProbeDisconnect(uuid, 'repeated-probe-disconnect');
2272
+ }
2273
+ consumeIosProbeDisconnect(uuid, confirmation) {
2274
+ const evidence = this.iosProbeDisconnectEvidence.get(uuid);
2275
+ if (!evidence)
2276
+ return undefined;
2277
+ this.iosProbeDisconnectEvidence.delete(uuid);
2278
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport][BleBondDiagnostic] stale bond confirmed', {
2279
+ confirmation,
2280
+ triggerPhase: evidence.phase,
2281
+ triggerErrorCode: evidence.errorCode,
2282
+ finalErrorCode: hdShared.HardwareErrorCode.BleBondInvalid,
2283
+ });
2284
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid);
2285
+ }
1812
2286
  clearProbeProtocol(uuid, protocol) {
1813
2287
  if (this.probingProtocols.get(uuid) === protocol) {
1814
2288
  this.probingProtocols.delete(uuid);
@@ -1822,9 +2296,10 @@ class ReactNativeBleTransport {
1822
2296
  return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
1823
2297
  }
1824
2298
  detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
1825
- var _a;
2299
+ var _a, _b;
1826
2300
  return __awaiter(this, void 0, void 0, function* () {
1827
2301
  if (expectedProtocol === 'V1') {
2302
+ this.iosProbeDisconnectEvidence.delete(uuid);
1828
2303
  this.deviceProtocol.set(uuid, 'V1');
1829
2304
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
1830
2305
  deviceId: uuid,
@@ -1838,6 +2313,7 @@ class ReactNativeBleTransport {
1838
2313
  }
1839
2314
  if (expectedProtocol === 'V2') {
1840
2315
  if (yield this.probeProtocolV2(uuid)) {
2316
+ this.iosProbeDisconnectEvidence.delete(uuid);
1841
2317
  this.deviceProtocol.set(uuid, 'V2');
1842
2318
  this.sessionProtocols.set(uuid, 'V2');
1843
2319
  this.confirmedProtocolV2.add(uuid);
@@ -1848,6 +2324,19 @@ class ReactNativeBleTransport {
1848
2324
  });
1849
2325
  return 'V2';
1850
2326
  }
2327
+ const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
2328
+ if (reactNative.Platform.OS === 'ios') {
2329
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
2330
+ outcome: 'expected-v2-no-response',
2331
+ expectedProtocol,
2332
+ protocolHint: protocolHint !== null && protocolHint !== void 0 ? protocolHint : 'none',
2333
+ finalErrorCode: staleBondError
2334
+ ? hdShared.HardwareErrorCode.BleBondInvalid
2335
+ : hdShared.HardwareErrorCode.RuntimeError,
2336
+ });
2337
+ }
2338
+ if (staleBondError)
2339
+ throw staleBondError;
1851
2340
  throw this.createProtocolMismatchError(expectedProtocol);
1852
2341
  }
1853
2342
  const sessionProtocol = this.sessionProtocols.get(uuid);
@@ -1857,6 +2346,7 @@ class ReactNativeBleTransport {
1857
2346
  !protocolHint &&
1858
2347
  reprobeFailures < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
1859
2348
  const probeOrder = trustSessionProtocol ? [sessionProtocol] : fullProbeOrder;
2349
+ yield this.wakeSilentProtocolV1Device(uuid, probeOrder);
1860
2350
  for (let i = 0; i < probeOrder.length; i += 1) {
1861
2351
  const protocol = probeOrder[i];
1862
2352
  if (i > 0) {
@@ -1870,12 +2360,14 @@ class ReactNativeBleTransport {
1870
2360
  }
1871
2361
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1872
2362
  if (detected) {
2363
+ this.iosProbeDisconnectEvidence.delete(uuid);
1873
2364
  this.deviceProtocol.set(uuid, protocol);
1874
2365
  this.sessionProtocols.set(uuid, protocol);
1875
2366
  if (protocol === 'V2') {
1876
2367
  this.confirmedProtocolV2.add(uuid);
1877
2368
  }
1878
2369
  this.protocolReprobeFailures.delete(uuid);
2370
+ this.silentDetections.delete(uuid);
1879
2371
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1880
2372
  deviceId: uuid,
1881
2373
  protocol,
@@ -1884,6 +2376,8 @@ class ReactNativeBleTransport {
1884
2376
  return protocol;
1885
2377
  }
1886
2378
  }
2379
+ if (!this.silentDetections.has(uuid))
2380
+ this.silentDetections.set(uuid, 'silent');
1887
2381
  if (trustSessionProtocol) {
1888
2382
  this.protocolReprobeFailures.set(uuid, reprobeFailures + 1);
1889
2383
  }
@@ -1892,9 +2386,53 @@ class ReactNativeBleTransport {
1892
2386
  }
1893
2387
  this.deviceProtocol.delete(uuid);
1894
2388
  this.probingProtocols.delete(uuid);
2389
+ const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
2390
+ if (reactNative.Platform.OS === 'ios') {
2391
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
2392
+ outcome: 'all-probes-no-response',
2393
+ expectedProtocol: expectedProtocol !== null && expectedProtocol !== void 0 ? expectedProtocol : 'none',
2394
+ protocolHint: protocolHint !== null && protocolHint !== void 0 ? protocolHint : 'none',
2395
+ sessionProtocol: sessionProtocol !== null && sessionProtocol !== void 0 ? sessionProtocol : 'none',
2396
+ probeOrder: probeOrder.join(','),
2397
+ trustSessionProtocol,
2398
+ reprobeFailures,
2399
+ transportCached: Boolean(transportCache[uuid]),
2400
+ notifyActive: Boolean((_b = transportCache[uuid]) === null || _b === void 0 ? void 0 : _b.notifySubscription),
2401
+ staleBondRecorded: this.staleBondErrors.has(uuid),
2402
+ finalErrorCode: staleBondError
2403
+ ? hdShared.HardwareErrorCode.BleBondInvalid
2404
+ : hdShared.HardwareErrorCode.BleTimeoutError,
2405
+ });
2406
+ }
2407
+ if (staleBondError)
2408
+ throw staleBondError;
1895
2409
  throw this.createProtocolDetectionError();
1896
2410
  });
1897
2411
  }
2412
+ wakeSilentProtocolV1Device(uuid, probeOrder) {
2413
+ return __awaiter(this, void 0, void 0, function* () {
2414
+ if (reactNative.Platform.OS !== 'android' ||
2415
+ probeOrder[0] !== 'V1' ||
2416
+ this.silentDetections.get(uuid) !== 'silent') {
2417
+ return;
2418
+ }
2419
+ this.silentDetections.set(uuid, 'woken');
2420
+ Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] sending Protocol V1 Initialize wake', {
2421
+ connectIdSuffix: uuid.slice(-8),
2422
+ });
2423
+ try {
2424
+ this.probingProtocols.set(uuid, 'V1');
2425
+ yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
2426
+ }
2427
+ catch (error) {
2428
+ if (shouldRethrowProtocolProbeError(error))
2429
+ throw error;
2430
+ }
2431
+ finally {
2432
+ this.clearProbeProtocol(uuid, 'V1');
2433
+ }
2434
+ });
2435
+ }
1898
2436
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1899
2437
  var _a, _b, _c;
1900
2438
  return __awaiter(this, void 0, void 0, function* () {
@@ -1951,7 +2489,12 @@ class ReactNativeBleTransport {
1951
2489
  catch (error) {
1952
2490
  this.clearProbeProtocol(uuid, 'V1');
1953
2491
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
1954
- if (isWedgedWriteError(error)) {
2492
+ const shouldRethrow = shouldRethrowProtocolProbeError(error);
2493
+ const staleBondError = this.rememberIosProbeDisconnect(uuid, 'protocol-v1-get-features', error);
2494
+ logIosBleBondDiagnostic('protocol-v1-get-features', error, shouldRethrow ? 'rethrow' : 'fallback');
2495
+ if (staleBondError)
2496
+ throw staleBondError;
2497
+ if (shouldRethrow) {
1955
2498
  throw error;
1956
2499
  }
1957
2500
  return false;
@@ -1967,18 +2510,28 @@ class ReactNativeBleTransport {
1967
2510
  this.probingProtocols.set(uuid, 'V2');
1968
2511
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1969
2512
  this.throwIfStaleBondError(uuid);
1970
- const detected = yield transport.probeProtocolV2({
1971
- call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
1972
- timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
1973
- logger: Log,
1974
- logPrefix: 'ProtocolV2 RN-BLE',
1975
- onProbeFailed: () => {
1976
- var _a;
1977
- (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1978
- this.resetProtocolV2Frames(uuid);
1979
- },
1980
- shouldRethrow: hdShared.isBleStaleBondHardwareError,
1981
- });
2513
+ let detected;
2514
+ try {
2515
+ detected = yield transport.probeProtocolV2({
2516
+ call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
2517
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
2518
+ logger: Log,
2519
+ logPrefix: 'ProtocolV2 RN-BLE',
2520
+ onProbeFailed: () => {
2521
+ var _a;
2522
+ (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2523
+ this.resetProtocolV2Frames(uuid);
2524
+ },
2525
+ shouldRethrow: shouldRethrowProtocolProbeError,
2526
+ });
2527
+ }
2528
+ catch (error) {
2529
+ const staleBondError = this.rememberIosProbeDisconnect(uuid, 'protocol-v2-ping', error);
2530
+ logIosBleBondDiagnostic('protocol-v2-ping', error, 'rethrow');
2531
+ if (staleBondError)
2532
+ throw staleBondError;
2533
+ throw error;
2534
+ }
1982
2535
  if (!detected) {
1983
2536
  this.clearProbeProtocol(uuid, 'V2');
1984
2537
  }
@@ -2104,6 +2657,9 @@ class ReactNativeBleTransport {
2104
2657
  this.rememberStaleBondError(uuid, bondError);
2105
2658
  throw bondError;
2106
2659
  }
2660
+ if (isNativeBleDisconnectError(error)) {
2661
+ throw toBleDisconnectHardwareError(error);
2662
+ }
2107
2663
  if (getFirmwareUploadWriteRetryType(error) !== 'congested' ||
2108
2664
  attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
2109
2665
  throw error;
@@ -2146,6 +2702,7 @@ class ReactNativeBleTransport {
2146
2702
  if (!this._messages || !this._messagesV2) {
2147
2703
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
2148
2704
  }
2705
+ const isProtocolProbe = this.probingProtocols.get(uuid) === 'V2';
2149
2706
  const callOptions = options;
2150
2707
  const highThroughputWrite = transport.isProtocolV2HighThroughputCall(name);
2151
2708
  if (highThroughputWrite) {
@@ -2186,6 +2743,14 @@ class ReactNativeBleTransport {
2186
2743
  }
2187
2744
  catch (e) {
2188
2745
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] Protocol V2 call error:', e);
2746
+ if (!isProtocolProbe &&
2747
+ (e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
2748
+ !this.monitorTokens.has(uuid)) {
2749
+ yield this.runLifecycleOperation(uuid, () => __awaiter(this, void 0, void 0, function* () {
2750
+ if (!this.monitorTokens.has(uuid))
2751
+ yield this.disconnectUnlocked(uuid);
2752
+ }));
2753
+ }
2189
2754
  throw e;
2190
2755
  }
2191
2756
  finally {
@@ -2200,7 +2765,7 @@ class ReactNativeBleTransport {
2200
2765
  const transport = this.getCachedTransport(uuid);
2201
2766
  if (!shouldRefreshNegotiatedMtu(transport.mtuSize))
2202
2767
  return;
2203
- const refreshedDevice = yield requestNegotiatedMtu(transport.device, 'highThroughput', 1);
2768
+ 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
2769
  transport.device = refreshedDevice;
2205
2770
  transport.mtuSize =
2206
2771
  typeof refreshedDevice.mtu === 'number' ? refreshedDevice.mtu : transport.mtuSize;
@@ -2317,6 +2882,8 @@ class ReactNativeBleTransport {
2317
2882
  }
2318
2883
  }
2319
2884
 
2885
+ exports.ANDROID_LINK_DROP_QUIET_MS = ANDROID_LINK_DROP_QUIET_MS;
2886
+ exports.ANDROID_MTU_EXCHANGE_TIMEOUT_MS = ANDROID_MTU_EXCHANGE_TIMEOUT_MS;
2320
2887
  exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
2321
2888
  exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2322
2889
  exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;