@onekeyfe/hd-transport-react-native 1.2.3-alpha.10 → 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/src/index.ts CHANGED
@@ -47,18 +47,6 @@ import {
47
47
  getInfosForServiceUuid,
48
48
  isSameBleUuid,
49
49
  } from './constants';
50
- import {
51
- isBleKeyMissingSupported,
52
- startBleKeyMissingTracking,
53
- stopBleKeyMissingTracking,
54
- waitForBleKeyMissing,
55
- } from './bleKeyMissing';
56
- import {
57
- markBleLinkEncrypted,
58
- startBleEncryptionTracking,
59
- stopBleEncryptionTracking,
60
- waitForAndroidLinkEncryption,
61
- } from './bleEncryption';
62
50
  import { isNativeBleDisconnectError, toBleDisconnectHardwareError } from './bleNativeDisconnect';
63
51
  import {
64
52
  isBleStaleBondHardwareError,
@@ -74,7 +62,6 @@ import type { Deferred } from '@onekeyfe/hd-shared';
74
62
  import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
75
63
  import type EventEmitter from 'events';
76
64
  import type { BleAcquireInput, TransportOptions } from './types';
77
- import type { AndroidLinkEncryption } from './bleEncryption';
78
65
 
79
66
  type FirmwareInstallBleAcquireInput = BleAcquireInput & {
80
67
  /**
@@ -201,36 +188,50 @@ const shouldRethrowProtocolProbeError = (error: unknown): boolean => {
201
188
  code === HardwareErrorCode.BleWriteCharacteristicError
202
189
  );
203
190
  };
204
- /**
205
- * Android reports a lost bond (ACTION_KEY_MISSING) while it encrypts a new link, so only
206
- * a failure this soon after the link started can be explained by it. Later disconnects,
207
- * such as a firmware-update reboot, must keep their own meaning.
208
- */
209
- export const ANDROID_KEY_MISSING_LINK_WINDOW_MS = 10_000;
210
- /** The broadcast and the GATT disconnect it explains travel separately; either can land first. */
211
- export const ANDROID_KEY_MISSING_GRACE_MS = 500;
212
- /**
213
- * Android reports a bonded link's first encryption result within about half a second of
214
- * connecting. Past this, the result is treated as unknown and the link is used as before.
215
- */
216
- export const ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS = 1500;
217
- /**
218
- * A system re-pairing needs the user to accept a pairing request and confirm the code on the
219
- * device. The Bluetooth stack gives up after 30s and then reports key missing.
220
- */
221
- export const ANDROID_SYSTEM_REPAIR_TIMEOUT_MS = 35_000;
222
- /**
223
- * How a link dropped by a device that refuses a stale bond reaches JS. A wedged write is
224
- * excluded: the system is still re-pairing then and has not reported key missing yet.
225
- */
226
- const isAndroidLinkLossError = (error: unknown): boolean => {
191
+
192
+ const logIosBleBondDiagnostic = (
193
+ phase: string,
194
+ error: unknown,
195
+ outcome: 'fallback' | 'rethrow'
196
+ ) => {
197
+ if (Platform.OS !== 'ios') return;
198
+
199
+ const nativeError = (error ?? {}) as {
200
+ name?: unknown;
201
+ errorCode?: unknown;
202
+ attErrorCode?: unknown;
203
+ iosErrorCode?: unknown;
204
+ androidErrorCode?: unknown;
205
+ };
206
+ const scalar = (value: unknown) =>
207
+ typeof value === 'string' || typeof value === 'number' ? value : undefined;
208
+
209
+ Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe error', {
210
+ phase,
211
+ outcome,
212
+ errorName: scalar(nativeError.name),
213
+ errorCode: scalar(nativeError.errorCode),
214
+ attErrorCode: scalar(nativeError.attErrorCode),
215
+ iosErrorCode: scalar(nativeError.iosErrorCode),
216
+ androidErrorCode: scalar(nativeError.androidErrorCode),
217
+ nativeStaleBond: isNativeBleStaleBondError(error),
218
+ hardwareStaleBond: isBleStaleBondHardwareError(error),
219
+ nativeDisconnect: isNativeBleDisconnectError(error),
220
+ });
221
+ };
222
+
223
+ type IosProbeDisconnectEvidence = {
224
+ phase: 'protocol-v1-get-features' | 'protocol-v2-ping';
225
+ errorCode?: string | number;
226
+ };
227
+
228
+ const isIosProbeDisconnectError = (error: unknown): boolean => {
229
+ if (Platform.OS !== 'ios') return false;
227
230
  const code = (error as { errorCode?: unknown })?.errorCode;
228
231
  return (
229
232
  isNativeBleDisconnectError(error) ||
230
233
  code === HardwareErrorCode.BleDeviceNotBonded ||
231
- code === HardwareErrorCode.BleDeviceDisconnected ||
232
- code === HardwareErrorCode.BleCharacteristicNotifyError ||
233
- code === HardwareErrorCode.BleConnectedError
234
+ code === HardwareErrorCode.BleDeviceDisconnected
234
235
  );
235
236
  };
236
237
  /** Consecutive wedged writes on one device before the BLE manager itself is recreated. */
@@ -575,6 +576,14 @@ export default class ReactNativeBleTransport {
575
576
  /** Endpoints whose last detection got no answer; 'woken' once their Initialize wake is spent. */
576
577
  private silentDetections = new Map<string, 'silent' | 'woken'>();
577
578
 
579
+ /**
580
+ * iOS can report a reset Pro2 as a generic peripheral disconnect instead of
581
+ * CBErrorPeerRemovedPairingInformation. Keep that first probe failure across
582
+ * the automatic cold reconnect and require a second failed probe before
583
+ * classifying the OS bond as invalid.
584
+ */
585
+ private iosProbeDisconnectEvidence = new Map<string, IosProbeDisconnectEvidence>();
586
+
578
587
  /** Android endpoints whose cached GATT table is suspect; the next connect refreshes it. */
579
588
  private androidGattCacheRefreshes = new Set<string>();
580
589
 
@@ -585,9 +594,6 @@ export default class ReactNativeBleTransport {
585
594
  */
586
595
  private staleBondErrors: Map<string, Error> = new Map();
587
596
 
588
- /** When the current Android link attempt began; bounds which key-missing signals apply. */
589
- private androidLinkStartedAt: Map<string, number> = new Map();
590
-
591
597
  /** Strict or previously confirmed V2 target while acquire installs notifications. */
592
598
  private acquiringProtocolV2 = new Set<string>();
593
599
 
@@ -652,11 +658,6 @@ export default class ReactNativeBleTransport {
652
658
  init(logger: any, emitter: EventEmitter) {
653
659
  setBleLogger(logger);
654
660
  this.emitter = emitter;
655
- if (Platform.OS === 'android') {
656
- // Link security events are only meaningful if they were observed since the link came up.
657
- startBleKeyMissingTracking();
658
- startBleEncryptionTracking();
659
- }
660
661
  }
661
662
 
662
663
  configure(signedData: any) {
@@ -1108,13 +1109,7 @@ export default class ReactNativeBleTransport {
1108
1109
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
1109
1110
  }
1110
1111
 
1111
- return this.runLifecycleOperation(uuid, async () => {
1112
- try {
1113
- return await this.acquireUnlocked(input);
1114
- } catch (error) {
1115
- throw await this.resolveAndroidBondInvalid(uuid, error);
1116
- }
1117
- });
1112
+ return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
1118
1113
  }
1119
1114
 
1120
1115
  private async acquireUnlocked(input: FirmwareInstallBleAcquireInput) {
@@ -1194,8 +1189,6 @@ export default class ReactNativeBleTransport {
1194
1189
 
1195
1190
  let device: Device | null = null;
1196
1191
  const isAndroid = Platform.OS === 'android';
1197
- // Only a bond that existed before this acquire can have been lost by the device.
1198
- let androidBondedBeforeConnect = false;
1199
1192
  // A firmware-install reconnect always refreshes: the new firmware may expose a different table.
1200
1193
  const refreshAndroidGattCache =
1201
1194
  isAndroid && (!!skipProtocolProbe || this.androidGattCacheRefreshes.has(uuid));
@@ -1227,19 +1220,11 @@ export default class ReactNativeBleTransport {
1227
1220
 
1228
1221
  if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1229
1222
  if (Platform.OS === 'android') {
1230
- // Subscribe before the link exists: key missing is broadcast while Android encrypts it.
1231
- startBleKeyMissingTracking();
1232
- startBleEncryptionTracking();
1233
- // Failures before the new link starts must not be read against the previous link.
1234
- this.androidLinkStartedAt.delete(uuid);
1235
1223
  // Initiate bonding locally before GATT can trigger peripheral-initiated pairing.
1236
1224
  try {
1237
1225
  const bondState = await pairDevice(uuid);
1238
- androidBondedBeforeConnect = !bondState.bonding && bondState.bonded;
1239
1226
  if (bondState.bonding) {
1240
- await onDeviceBondState(uuid, this.bondAbortController.signal, {
1241
- systemInitiated: bondState.initiated === false,
1242
- });
1227
+ await onDeviceBondState(uuid, this.bondAbortController.signal);
1243
1228
  } else if (!bondState.bonded) {
1244
1229
  throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
1245
1230
  }
@@ -1251,7 +1236,6 @@ export default class ReactNativeBleTransport {
1251
1236
  });
1252
1237
  throw error;
1253
1238
  }
1254
- this.androidLinkStartedAt.set(uuid, Date.now());
1255
1239
  }
1256
1240
  if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1257
1241
 
@@ -1439,13 +1423,7 @@ export default class ReactNativeBleTransport {
1439
1423
  this.acquiringProtocolV2.add(uuid);
1440
1424
  }
1441
1425
 
1442
- let linkEncryption: AndroidLinkEncryption | undefined;
1443
1426
  try {
1444
- // A firmware-install reconnect keeps its existing sequence.
1445
- if (androidBondedBeforeConnect && !skipProtocolProbe) {
1446
- linkEncryption = await this.waitForAndroidLinkSecurity(uuid);
1447
- if (this.stopped) throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
1448
- }
1449
1427
  await this.installTransportForAcquire(uuid, acquiredDevice, {
1450
1428
  writeCharacteristic,
1451
1429
  notifyCharacteristic,
@@ -1497,16 +1475,13 @@ export default class ReactNativeBleTransport {
1497
1475
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
1498
1476
  }
1499
1477
  this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1500
- if (linkEncryption === 'unresolved') {
1501
- // Notifications and the probe only work on an encrypted link, so the next reuse of this
1502
- // link needs no result that was reported before tracking started.
1503
- markBleLinkEncrypted(uuid);
1504
- }
1505
1478
  return { uuid, protocolType };
1506
1479
  } catch (error) {
1507
1480
  // A failed acquire must retire the physical link before Core retries. Logical
1508
1481
  // release leaves GATT connected even when neither protocol receives a response.
1509
- await this.disconnectUnlocked(uuid);
1482
+ await this.disconnectUnlocked(uuid, {
1483
+ preserveIosProbeDisconnect: this.iosProbeDisconnectEvidence.has(uuid),
1484
+ });
1510
1485
  throw error;
1511
1486
  } finally {
1512
1487
  this.acquiringProtocolV2.delete(uuid);
@@ -1770,77 +1745,16 @@ export default class ReactNativeBleTransport {
1770
1745
  `Device protocol has not been detected for ${uuid}`
1771
1746
  );
1772
1747
  }
1773
- const forceRun = name === 'Initialize' || name === 'Cancel';
1774
- if (protocol !== 'V2' && this.runPromise && !forceRun) {
1775
- throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
1776
- }
1777
-
1778
- try {
1779
- return protocol === 'V2'
1780
- ? await this.callProtocolV2(uuid, name, data, options)
1781
- : await this.callProtocolV1(uuid, name, data, options);
1782
- } catch (error) {
1783
- // An expected-V1 acquire skips the probe, so the first call is what meets the dropped link.
1784
- throw await this.resolveAndroidBondInvalid(uuid, error);
1748
+ if (protocol === 'V2') {
1749
+ return this.callProtocolV2(uuid, name, data, options);
1785
1750
  }
1786
- }
1787
1751
 
1788
- /**
1789
- * Android 16+ keeps a bond the device no longer holds keys for. The device drops the link
1790
- * and the only structured evidence is ACTION_KEY_MISSING, so a link loss right after
1791
- * connect is re-read as an invalid bond when that signal belongs to the same attempt.
1792
- */
1793
- private async resolveAndroidBondInvalid(uuid: string, error: unknown): Promise<unknown> {
1794
- if (Platform.OS !== 'android' || !isBleKeyMissingSupported()) return error;
1795
- if (isBleStaleBondHardwareError(error) || !isAndroidLinkLossError(error)) return error;
1796
- const linkStartedAt = this.androidLinkStartedAt.get(uuid);
1797
- if (
1798
- linkStartedAt === undefined ||
1799
- Date.now() - linkStartedAt > ANDROID_KEY_MISSING_LINK_WINDOW_MS
1800
- ) {
1801
- return error;
1802
- }
1803
- if (!(await waitForBleKeyMissing(uuid, linkStartedAt, ANDROID_KEY_MISSING_GRACE_MS))) {
1804
- return error;
1752
+ const forceRun = name === 'Initialize' || name === 'Cancel';
1753
+ if (this.runPromise && !forceRun) {
1754
+ throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
1805
1755
  }
1806
- Log?.debug('[ReactNativeBleTransport] Android key missing, bond is invalid:', uuid);
1807
- return ERRORS.TypedError(HardwareErrorCode.BleBondInvalid, undefined, {
1808
- phase: 'connect',
1809
- reason: 'key_missing',
1810
- });
1811
- }
1812
1756
 
1813
- /**
1814
- * Android 16+ encrypts a bonded link on its own right after connecting. Subscribing to
1815
- * notifications before that finishes gets the CCCD write rejected, and the framework's retry
1816
- * encrypts again with the same stale key; firmware that allows one failure per link then drops
1817
- * it before the system can re-pair. Holding GATT until the result lets a lost bond be re-paired
1818
- * in place. Without the platform signal this resolves immediately.
1819
- */
1820
- private async waitForAndroidLinkSecurity(uuid: string): Promise<AndroidLinkEncryption> {
1821
- const linkStartedAt = this.androidLinkStartedAt.get(uuid);
1822
- if (linkStartedAt === undefined) return 'unresolved';
1823
- const waitStartedAt = Date.now();
1824
- const result = await waitForAndroidLinkEncryption({
1825
- deviceId: uuid,
1826
- linkStartedAt,
1827
- resultTimeoutMs: ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS,
1828
- repairTimeoutMs: ANDROID_SYSTEM_REPAIR_TIMEOUT_MS,
1829
- keyMissingGraceMs: ANDROID_KEY_MISSING_GRACE_MS,
1830
- signal: this.bondAbortController.signal,
1831
- onRepairStarted: () => {
1832
- Log?.debug(
1833
- '[ReactNativeBleTransport] Android bond lost, waiting for system re-pairing:',
1834
- uuid
1835
- );
1836
- },
1837
- });
1838
- Log?.debug('[ReactNativeBleTransport] Android link encryption', {
1839
- connectIdSuffix: uuid.slice(-8),
1840
- result,
1841
- waitedMs: Date.now() - waitStartedAt,
1842
- });
1843
- return result;
1757
+ return this.callProtocolV1(uuid, name, data, options);
1844
1758
  }
1845
1759
 
1846
1760
  private async callProtocolV1(
@@ -2108,9 +2022,7 @@ export default class ReactNativeBleTransport {
2108
2022
  this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
2109
2023
  this.androidPriorityResetTimers.clear();
2110
2024
  this.androidHighPriorityDevices.clear();
2111
- this.androidLinkStartedAt.clear();
2112
- stopBleKeyMissingTracking();
2113
- stopBleEncryptionTracking();
2025
+ this.iosProbeDisconnectEvidence.clear();
2114
2026
  const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
2115
2027
  this.runPromise?.reject(error);
2116
2028
  this.runPromise = null;
@@ -2146,7 +2058,10 @@ export default class ReactNativeBleTransport {
2146
2058
  return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
2147
2059
  }
2148
2060
 
2149
- private async disconnectUnlocked(session: string) {
2061
+ private async disconnectUnlocked(
2062
+ session: string,
2063
+ options?: { preserveIosProbeDisconnect?: boolean }
2064
+ ) {
2150
2065
  await this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
2151
2066
  const transport = transportCache[session];
2152
2067
  const manager = this.blePlxManager;
@@ -2188,6 +2103,9 @@ export default class ReactNativeBleTransport {
2188
2103
  this.deviceProtocolHints.delete(session);
2189
2104
  this.sessionProtocols.delete(session);
2190
2105
  this.protocolReprobeFailures.delete(session);
2106
+ if (!options?.preserveIosProbeDisconnect) {
2107
+ this.iosProbeDisconnectEvidence.delete(session);
2108
+ }
2191
2109
  this.protocolV2Assemblers.delete(session);
2192
2110
  this.resetProtocolV2Frames(session);
2193
2111
 
@@ -2536,6 +2454,7 @@ export default class ReactNativeBleTransport {
2536
2454
  this.deviceProtocol.delete(uuid);
2537
2455
  this.probingProtocols.delete(uuid);
2538
2456
  this.staleBondErrors.delete(uuid);
2457
+ this.iosProbeDisconnectEvidence.delete(uuid);
2539
2458
  this.acquiringProtocolV2.delete(uuid);
2540
2459
  this.protocolV2Assemblers.delete(uuid);
2541
2460
  this.resetProtocolV2Frames(uuid);
@@ -2634,6 +2553,7 @@ export default class ReactNativeBleTransport {
2634
2553
  this.deviceProtocol.delete(uuid);
2635
2554
  this.probingProtocols.delete(uuid);
2636
2555
  this.staleBondErrors.delete(uuid);
2556
+ this.iosProbeDisconnectEvidence.delete(uuid);
2637
2557
  this.acquiringProtocolV2.delete(uuid);
2638
2558
  this.protocolV2Assemblers.delete(uuid);
2639
2559
  this.resetProtocolV2Frames(uuid);
@@ -2687,6 +2607,7 @@ export default class ReactNativeBleTransport {
2687
2607
  this.deviceProtocol.clear();
2688
2608
  this.probingProtocols.clear();
2689
2609
  this.staleBondErrors.clear();
2610
+ this.iosProbeDisconnectEvidence.clear();
2690
2611
  this.acquiringProtocolV2.clear();
2691
2612
  this.sessionProtocols.clear();
2692
2613
  // Keep transport-lifetime V2 proof so the same endpoint can finish a no-probe
@@ -2730,6 +2651,47 @@ export default class ReactNativeBleTransport {
2730
2651
  );
2731
2652
  }
2732
2653
 
2654
+ private rememberIosProbeDisconnect(
2655
+ uuid: string,
2656
+ phase: IosProbeDisconnectEvidence['phase'],
2657
+ error: unknown
2658
+ ): Error | undefined {
2659
+ if (!isIosProbeDisconnectError(error)) return undefined;
2660
+
2661
+ const previous = this.iosProbeDisconnectEvidence.get(uuid);
2662
+ const errorCode = (error as { errorCode?: unknown })?.errorCode;
2663
+ const evidence: IosProbeDisconnectEvidence = {
2664
+ phase,
2665
+ ...(typeof errorCode === 'string' || typeof errorCode === 'number' ? { errorCode } : {}),
2666
+ };
2667
+ this.iosProbeDisconnectEvidence.set(uuid, evidence);
2668
+ Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] probe disconnect recorded', {
2669
+ phase,
2670
+ errorCode: evidence.errorCode,
2671
+ repeated: Boolean(previous),
2672
+ });
2673
+
2674
+ if (!previous) return undefined;
2675
+ return this.consumeIosProbeDisconnect(uuid, 'repeated-probe-disconnect');
2676
+ }
2677
+
2678
+ private consumeIosProbeDisconnect(
2679
+ uuid: string,
2680
+ confirmation: 'repeated-probe-disconnect' | 'follow-up-no-response'
2681
+ ): Error | undefined {
2682
+ const evidence = this.iosProbeDisconnectEvidence.get(uuid);
2683
+ if (!evidence) return undefined;
2684
+
2685
+ this.iosProbeDisconnectEvidence.delete(uuid);
2686
+ Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] stale bond confirmed', {
2687
+ confirmation,
2688
+ triggerPhase: evidence.phase,
2689
+ triggerErrorCode: evidence.errorCode,
2690
+ finalErrorCode: HardwareErrorCode.BleBondInvalid,
2691
+ });
2692
+ return ERRORS.TypedError(HardwareErrorCode.BleBondInvalid);
2693
+ }
2694
+
2733
2695
  private clearProbeProtocol(uuid: string, protocol: ProtocolType) {
2734
2696
  if (this.probingProtocols.get(uuid) === protocol) {
2735
2697
  this.probingProtocols.delete(uuid);
@@ -2761,6 +2723,7 @@ export default class ReactNativeBleTransport {
2761
2723
  // received no response. skipProtocolProbe does not need it either — its only
2762
2724
  // caller is the V2 firmware-install reconnect.
2763
2725
  if (expectedProtocol === 'V1') {
2726
+ this.iosProbeDisconnectEvidence.delete(uuid);
2764
2727
  this.deviceProtocol.set(uuid, 'V1');
2765
2728
  Log?.debug('[ReactNativeBleTransport] protocol selected', {
2766
2729
  deviceId: uuid,
@@ -2776,6 +2739,7 @@ export default class ReactNativeBleTransport {
2776
2739
 
2777
2740
  if (expectedProtocol === 'V2') {
2778
2741
  if (await this.probeProtocolV2(uuid)) {
2742
+ this.iosProbeDisconnectEvidence.delete(uuid);
2779
2743
  this.deviceProtocol.set(uuid, 'V2');
2780
2744
  this.sessionProtocols.set(uuid, 'V2');
2781
2745
  this.confirmedProtocolV2.add(uuid);
@@ -2786,6 +2750,18 @@ export default class ReactNativeBleTransport {
2786
2750
  });
2787
2751
  return 'V2';
2788
2752
  }
2753
+ const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
2754
+ if (Platform.OS === 'ios') {
2755
+ Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
2756
+ outcome: 'expected-v2-no-response',
2757
+ expectedProtocol,
2758
+ protocolHint: protocolHint ?? 'none',
2759
+ finalErrorCode: staleBondError
2760
+ ? HardwareErrorCode.BleBondInvalid
2761
+ : HardwareErrorCode.RuntimeError,
2762
+ });
2763
+ }
2764
+ if (staleBondError) throw staleBondError;
2789
2765
  throw this.createProtocolMismatchError(expectedProtocol);
2790
2766
  }
2791
2767
 
@@ -2821,6 +2797,7 @@ export default class ReactNativeBleTransport {
2821
2797
  const detected =
2822
2798
  protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
2823
2799
  if (detected) {
2800
+ this.iosProbeDisconnectEvidence.delete(uuid);
2824
2801
  this.deviceProtocol.set(uuid, protocol);
2825
2802
  this.sessionProtocols.set(uuid, protocol);
2826
2803
  if (protocol === 'V2') {
@@ -2850,6 +2827,25 @@ export default class ReactNativeBleTransport {
2850
2827
 
2851
2828
  this.deviceProtocol.delete(uuid);
2852
2829
  this.probingProtocols.delete(uuid);
2830
+ const staleBondError = this.consumeIosProbeDisconnect(uuid, 'follow-up-no-response');
2831
+ if (Platform.OS === 'ios') {
2832
+ Log?.debug('[ReactNativeBleTransport][BleBondDiagnostic] protocol detection ended', {
2833
+ outcome: 'all-probes-no-response',
2834
+ expectedProtocol: expectedProtocol ?? 'none',
2835
+ protocolHint: protocolHint ?? 'none',
2836
+ sessionProtocol: sessionProtocol ?? 'none',
2837
+ probeOrder: probeOrder.join(','),
2838
+ trustSessionProtocol,
2839
+ reprobeFailures,
2840
+ transportCached: Boolean(transportCache[uuid]),
2841
+ notifyActive: Boolean(transportCache[uuid]?.notifySubscription),
2842
+ staleBondRecorded: this.staleBondErrors.has(uuid),
2843
+ finalErrorCode: staleBondError
2844
+ ? HardwareErrorCode.BleBondInvalid
2845
+ : HardwareErrorCode.BleTimeoutError,
2846
+ });
2847
+ }
2848
+ if (staleBondError) throw staleBondError;
2853
2849
  throw this.createProtocolDetectionError();
2854
2850
  }
2855
2851
 
@@ -2948,7 +2944,19 @@ export default class ReactNativeBleTransport {
2948
2944
  } catch (error) {
2949
2945
  this.clearProbeProtocol(uuid, 'V1');
2950
2946
  Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
2951
- if (shouldRethrowProtocolProbeError(error)) {
2947
+ const shouldRethrow = shouldRethrowProtocolProbeError(error);
2948
+ const staleBondError = this.rememberIosProbeDisconnect(
2949
+ uuid,
2950
+ 'protocol-v1-get-features',
2951
+ error
2952
+ );
2953
+ logIosBleBondDiagnostic(
2954
+ 'protocol-v1-get-features',
2955
+ error,
2956
+ shouldRethrow ? 'rethrow' : 'fallback'
2957
+ );
2958
+ if (staleBondError) throw staleBondError;
2959
+ if (shouldRethrow) {
2952
2960
  throw error;
2953
2961
  }
2954
2962
  return false;
@@ -2963,18 +2971,26 @@ export default class ReactNativeBleTransport {
2963
2971
  this.probingProtocols.set(uuid, 'V2');
2964
2972
  this.protocolV2Assemblers.get(uuid)?.reset();
2965
2973
  this.throwIfStaleBondError(uuid);
2966
- const detected = await probeProtocolV2Helper({
2967
- call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
2968
- this.callProtocolV2(uuid, name, data, options),
2969
- timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
2970
- logger: Log,
2971
- logPrefix: 'ProtocolV2 RN-BLE',
2972
- onProbeFailed: () => {
2973
- this.protocolV2Assemblers.get(uuid)?.reset();
2974
- this.resetProtocolV2Frames(uuid);
2975
- },
2976
- shouldRethrow: shouldRethrowProtocolProbeError,
2977
- });
2974
+ let detected: boolean;
2975
+ try {
2976
+ detected = await probeProtocolV2Helper({
2977
+ call: (name: string, data: Record<string, unknown>, options?: TransportCallOptions) =>
2978
+ this.callProtocolV2(uuid, name, data, options),
2979
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
2980
+ logger: Log,
2981
+ logPrefix: 'ProtocolV2 RN-BLE',
2982
+ onProbeFailed: () => {
2983
+ this.protocolV2Assemblers.get(uuid)?.reset();
2984
+ this.resetProtocolV2Frames(uuid);
2985
+ },
2986
+ shouldRethrow: shouldRethrowProtocolProbeError,
2987
+ });
2988
+ } catch (error) {
2989
+ const staleBondError = this.rememberIosProbeDisconnect(uuid, 'protocol-v2-ping', error);
2990
+ logIosBleBondDiagnostic('protocol-v2-ping', error, 'rethrow');
2991
+ if (staleBondError) throw staleBondError;
2992
+ throw error;
2993
+ }
2978
2994
  if (!detected) {
2979
2995
  this.clearProbeProtocol(uuid, 'V2');
2980
2996
  } else {
@@ -1,17 +0,0 @@
1
- export declare const HCI_PIN_OR_KEY_MISSING = 6;
2
- export type AndroidLinkEncryption = 'encrypted' | 'already-encrypted' | 're-paired' | 'unresolved';
3
- export declare const isBleEncryptionTrackingSupported: () => boolean;
4
- export declare const startBleEncryptionTracking: () => boolean;
5
- export declare const stopBleEncryptionTracking: () => void;
6
- export declare const markBleLinkEncrypted: (deviceId: string) => void;
7
- export type WaitForAndroidLinkEncryptionOptions = {
8
- deviceId: string;
9
- linkStartedAt: number;
10
- resultTimeoutMs: number;
11
- repairTimeoutMs: number;
12
- keyMissingGraceMs: number;
13
- signal?: AbortSignal;
14
- onRepairStarted?: () => void;
15
- };
16
- export declare const waitForAndroidLinkEncryption: ({ deviceId, linkStartedAt, resultTimeoutMs, repairTimeoutMs, keyMissingGraceMs, signal, onRepairStarted, }: WaitForAndroidLinkEncryptionOptions) => Promise<AndroidLinkEncryption>;
17
- //# sourceMappingURL=bleEncryption.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bleEncryption.d.ts","sourceRoot":"","sources":["../src/bleEncryption.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,sBAAsB,IAAO,CAAC;AAE3C,MAAM,MAAM,qBAAqB,GAE7B,WAAW,GAEX,mBAAmB,GAEnB,WAAW,GAEX,YAAY,CAAC;AA0CjB,eAAO,MAAM,gCAAgC,eAA2C,CAAC;AAGzF,eAAO,MAAM,0BAA0B,QAAO,OAwB7C,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAMrC,CAAC;AAMF,eAAO,MAAM,oBAAoB,aAAc,MAAM,SAGpD,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,EAAE,MAAM,CAAC;IAEtB,eAAe,EAAE,MAAM,CAAC;IAExB,eAAe,EAAE,MAAM,CAAC;IAExB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAOF,eAAO,MAAM,4BAA4B,+GAQtC,mCAAmC,KAAG,QAAQ,qBAAqB,CAuHrE,CAAC"}
@@ -1,7 +0,0 @@
1
- export declare const isBleKeyMissingSupported: () => boolean;
2
- export declare const startBleKeyMissingTracking: () => boolean;
3
- export declare const stopBleKeyMissingTracking: () => void;
4
- export declare const hasBleKeyMissingSince: (deviceId: string, sinceMs: number) => boolean;
5
- export declare const onBleKeyMissing: (deviceId: string, callback: () => void) => (() => void);
6
- export declare const waitForBleKeyMissing: (deviceId: string, sinceMs: number, timeoutMs: number) => Promise<boolean>;
7
- //# sourceMappingURL=bleKeyMissing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bleKeyMissing.d.ts","sourceRoot":"","sources":["../src/bleKeyMissing.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,wBAAwB,eAA2C,CAAC;AAGjF,eAAO,MAAM,0BAA0B,QAAO,OAY7C,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAKrC,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,KAAG,OAGzE,CAAC;AAGF,eAAO,MAAM,eAAe,aAAc,MAAM,YAAY,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CASnF,CAAC;AAOF,eAAO,MAAM,oBAAoB,aACrB,MAAM,WACP,MAAM,aACJ,MAAM,KAChB,QAAQ,OAAO,CAejB,CAAC"}