@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/dist/index.js CHANGED
@@ -46,76 +46,6 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
46
46
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
47
47
  };
48
48
 
49
- const lastKeyMissingAt = new Map();
50
- const listeners$1 = new Set();
51
- let unsubscribeNative$1;
52
- const normalizeDeviceId$1 = (deviceId) => deviceId.toLowerCase();
53
- const getCapableBleUtils$1 = () => {
54
- if (reactNative.Platform.OS !== 'android')
55
- return undefined;
56
- const bleUtils = BleUtils__default["default"];
57
- if (typeof bleUtils.supportsDeviceKeyMissing !== 'function' ||
58
- typeof bleUtils.onDeviceKeyMissing !== 'function') {
59
- return undefined;
60
- }
61
- return bleUtils.supportsDeviceKeyMissing() ? bleUtils : undefined;
62
- };
63
- const isBleKeyMissingSupported = () => getCapableBleUtils$1() !== undefined;
64
- const startBleKeyMissingTracking = () => {
65
- if (unsubscribeNative$1)
66
- return true;
67
- const bleUtils = getCapableBleUtils$1();
68
- if (!(bleUtils === null || bleUtils === void 0 ? void 0 : bleUtils.onDeviceKeyMissing))
69
- return false;
70
- unsubscribeNative$1 = bleUtils.onDeviceKeyMissing(event => {
71
- if (typeof (event === null || event === void 0 ? void 0 : event.id) !== 'string')
72
- return;
73
- const deviceId = normalizeDeviceId$1(event.id);
74
- lastKeyMissingAt.set(deviceId, Date.now());
75
- listeners$1.forEach(listener => listener(deviceId));
76
- });
77
- return true;
78
- };
79
- const stopBleKeyMissingTracking = () => {
80
- unsubscribeNative$1 === null || unsubscribeNative$1 === void 0 ? void 0 : unsubscribeNative$1();
81
- unsubscribeNative$1 = undefined;
82
- lastKeyMissingAt.clear();
83
- listeners$1.clear();
84
- };
85
- const hasBleKeyMissingSince = (deviceId, sinceMs) => {
86
- const at = lastKeyMissingAt.get(normalizeDeviceId$1(deviceId));
87
- return at !== undefined && at >= sinceMs;
88
- };
89
- const onBleKeyMissing = (deviceId, callback) => {
90
- const target = normalizeDeviceId$1(deviceId);
91
- const listener = id => {
92
- if (id === target)
93
- callback();
94
- };
95
- listeners$1.add(listener);
96
- return () => {
97
- listeners$1.delete(listener);
98
- };
99
- };
100
- const waitForBleKeyMissing = (deviceId, sinceMs, timeoutMs) => {
101
- if (!startBleKeyMissingTracking())
102
- return Promise.resolve(false);
103
- if (hasBleKeyMissingSince(deviceId, sinceMs))
104
- return Promise.resolve(true);
105
- return new Promise(resolve => {
106
- const settle = (found) => {
107
- clearTimeout(timer);
108
- cleanup();
109
- resolve(found);
110
- };
111
- const cleanup = onBleKeyMissing(deviceId, () => {
112
- if (hasBleKeyMissingSince(deviceId, sinceMs))
113
- settle(true);
114
- });
115
- const timer = setTimeout(() => settle(false), timeoutMs);
116
- });
117
- };
118
-
119
49
  let activeLogger;
120
50
  const setBleLogger = (logger) => {
121
51
  activeLogger = logger;
@@ -140,35 +70,17 @@ const bondFailureReasons = {
140
70
  };
141
71
  const getConnectedDeviceIds = (serviceUuids) => BleUtils__default["default"].getConnectedPeripherals(serviceUuids);
142
72
  const pairDevice = (macAddress) => BleUtils__default["default"].pairDevice(macAddress);
143
- const SYSTEM_BONDING_RESTART_WINDOW_MS = 1500;
144
- const createBondFailureError = (bondState) => {
145
- var _a;
146
- const nativeReason = 'reason' in bondState && typeof bondState.reason === 'number' ? bondState.reason : undefined;
147
- const reason = nativeReason === undefined ? 'unknown' : (_a = bondFailureReasons[nativeReason]) !== null && _a !== void 0 ? _a : 'unknown';
148
- const params = Object.assign({ phase: 'bond', reason }, (nativeReason === undefined ? {} : { nativeReason }));
149
- if (reason === 'timeout') {
150
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', params);
151
- }
152
- if (reason === 'canceled') {
153
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled, 'Bluetooth pairing canceled', params);
154
- }
155
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing failed', params);
156
- };
157
- const onDeviceBondState = (bleMacAddress, signal, options) => new Promise((resolve, reject) => {
73
+ const onDeviceBondState = (bleMacAddress, signal) => new Promise((resolve, reject) => {
158
74
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
159
75
  reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
160
76
  return;
161
77
  }
162
- let pendingFailure;
163
78
  const cleanup = () => {
164
79
  if (timeout) {
165
80
  clearTimeout(timeout);
166
81
  }
167
- if (pendingFailure)
168
- clearTimeout(pendingFailure);
169
82
  if (cleanupListener)
170
83
  cleanupListener();
171
- cleanupKeyMissing === null || cleanupKeyMissing === void 0 ? void 0 : cleanupKeyMissing();
172
84
  signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
173
85
  };
174
86
  const onAbort = () => {
@@ -183,44 +95,35 @@ const onDeviceBondState = (bleMacAddress, signal, options) => new Promise((resol
183
95
  reason: 'timeout',
184
96
  }));
185
97
  }, 60 * 1000);
186
- const cleanupKeyMissing = startBleKeyMissingTracking()
187
- ? onBleKeyMissing(bleMacAddress, () => {
188
- cleanup();
189
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, undefined, {
190
- phase: 'bond',
191
- reason: 'key_missing',
192
- }));
193
- })
194
- : undefined;
195
98
  const cleanupListener = BleUtils__default["default"].onDeviceBondState(peripheral => {
196
- var _a;
99
+ var _a, _b;
197
100
  if (((_a = peripheral.id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== bleMacAddress.toLowerCase()) {
198
101
  return;
199
102
  }
200
103
  const { bondState } = peripheral;
201
104
  const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
202
105
  const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
203
- const hasRestarted = bondState.preState === 'BOND_NONE' && bondState.state === 'BOND_BONDING';
204
106
  Logger.debug('onDeviceBondState bondState:', bondState);
205
107
  if (hasBonded) {
206
108
  cleanup();
207
109
  resolve(peripheral);
208
110
  }
209
- else if (hasRestarted && pendingFailure) {
210
- clearTimeout(pendingFailure);
211
- pendingFailure = undefined;
212
- }
213
- else if (hasFailed && (options === null || options === void 0 ? void 0 : options.systemInitiated)) {
214
- if (pendingFailure)
215
- clearTimeout(pendingFailure);
216
- pendingFailure = setTimeout(() => {
217
- cleanup();
218
- reject(createBondFailureError(bondState));
219
- }, SYSTEM_BONDING_RESTART_WINDOW_MS);
220
- }
221
111
  else if (hasFailed) {
222
112
  cleanup();
223
- reject(createBondFailureError(bondState));
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
+ }
224
127
  }
225
128
  });
226
129
  });
@@ -328,172 +231,6 @@ const subscribeBleOn = (bleManager, ms = 1000) => new Promise((resolve, reject)
328
231
  }, ms);
329
232
  });
330
233
 
331
- const HCI_PIN_OR_KEY_MISSING = 0x06;
332
- const lastEncryption = new Map();
333
- const lastAclDisconnectAt = new Map();
334
- const listeners = new Set();
335
- let unsubscribeNative;
336
- const normalizeDeviceId = (deviceId) => deviceId.toLowerCase();
337
- const isEncrypted = (record) => record.status === 0 && record.enabled;
338
- const getCapableBleUtils = () => {
339
- if (reactNative.Platform.OS !== 'android')
340
- return undefined;
341
- const bleUtils = BleUtils__default["default"];
342
- if (typeof bleUtils.supportsDeviceEncryptionChange !== 'function' ||
343
- typeof bleUtils.onDeviceEncryptionChange !== 'function' ||
344
- typeof bleUtils.onDeviceAclDisconnected !== 'function') {
345
- return undefined;
346
- }
347
- return bleUtils.supportsDeviceEncryptionChange() ? bleUtils : undefined;
348
- };
349
- const notify = (deviceId) => {
350
- listeners.forEach(listener => listener(deviceId));
351
- };
352
- const startBleEncryptionTracking = () => {
353
- if (unsubscribeNative)
354
- return true;
355
- const bleUtils = getCapableBleUtils();
356
- if (!(bleUtils === null || bleUtils === void 0 ? void 0 : bleUtils.onDeviceEncryptionChange) || !bleUtils.onDeviceAclDisconnected)
357
- return false;
358
- unsubscribeNative = [
359
- bleUtils.onDeviceEncryptionChange(event => {
360
- if (typeof (event === null || event === void 0 ? void 0 : event.id) !== 'string')
361
- return;
362
- const deviceId = normalizeDeviceId(event.id);
363
- lastEncryption.set(deviceId, {
364
- at: Date.now(),
365
- status: Number(event.status),
366
- enabled: event.enabled === true,
367
- });
368
- notify(deviceId);
369
- }),
370
- bleUtils.onDeviceAclDisconnected(event => {
371
- if (typeof (event === null || event === void 0 ? void 0 : event.id) !== 'string')
372
- return;
373
- const deviceId = normalizeDeviceId(event.id);
374
- lastAclDisconnectAt.set(deviceId, Date.now());
375
- notify(deviceId);
376
- }),
377
- ];
378
- return true;
379
- };
380
- const stopBleEncryptionTracking = () => {
381
- unsubscribeNative === null || unsubscribeNative === void 0 ? void 0 : unsubscribeNative.forEach(unsubscribe => unsubscribe());
382
- unsubscribeNative = undefined;
383
- lastEncryption.clear();
384
- lastAclDisconnectAt.clear();
385
- listeners.clear();
386
- };
387
- const markBleLinkEncrypted = (deviceId) => {
388
- if (!unsubscribeNative)
389
- return;
390
- lastEncryption.set(normalizeDeviceId(deviceId), { at: Date.now(), status: 0, enabled: true });
391
- };
392
- const waitForAndroidLinkEncryption = ({ deviceId, linkStartedAt, resultTimeoutMs, repairTimeoutMs, keyMissingGraceMs, signal, onRepairStarted, }) => {
393
- if (!startBleEncryptionTracking())
394
- return Promise.resolve('unresolved');
395
- startBleKeyMissingTracking();
396
- const target = normalizeDeviceId(deviceId);
397
- const previous = lastEncryption.get(target);
398
- const previousDropAt = lastAclDisconnectAt.get(target);
399
- if (previous &&
400
- previous.at < linkStartedAt &&
401
- isEncrypted(previous) &&
402
- (previousDropAt === undefined || previousDropAt < previous.at)) {
403
- return Promise.resolve('already-encrypted');
404
- }
405
- return new Promise((resolve, reject) => {
406
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
407
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
408
- return;
409
- }
410
- let repairStartedAt;
411
- let droppedDuringRepair = false;
412
- let timer;
413
- let settled = false;
414
- const cleanup = () => {
415
- settled = true;
416
- if (timer)
417
- clearTimeout(timer);
418
- listeners.delete(onLinkEvent);
419
- cleanupKeyMissing();
420
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
421
- };
422
- const finish = (result) => {
423
- cleanup();
424
- resolve(result);
425
- };
426
- const fail = (error) => {
427
- cleanup();
428
- reject(error);
429
- };
430
- const arm = (ms, onExpire) => {
431
- if (timer)
432
- clearTimeout(timer);
433
- timer = setTimeout(onExpire, ms);
434
- };
435
- const bondInvalid = () => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, undefined, {
436
- phase: 'connect',
437
- reason: 'key_missing',
438
- });
439
- const evaluate = () => {
440
- if (settled)
441
- return;
442
- const record = lastEncryption.get(target);
443
- const dropAt = lastAclDisconnectAt.get(target);
444
- if (repairStartedAt === undefined) {
445
- if (hasBleKeyMissingSince(target, linkStartedAt)) {
446
- fail(bondInvalid());
447
- return;
448
- }
449
- if (dropAt !== undefined && dropAt >= linkStartedAt) {
450
- finish('unresolved');
451
- return;
452
- }
453
- if (!record || record.at < linkStartedAt)
454
- return;
455
- if (isEncrypted(record)) {
456
- finish('encrypted');
457
- return;
458
- }
459
- if (record.status !== HCI_PIN_OR_KEY_MISSING) {
460
- finish('unresolved');
461
- return;
462
- }
463
- repairStartedAt = record.at;
464
- onRepairStarted === null || onRepairStarted === void 0 ? void 0 : onRepairStarted();
465
- arm(repairTimeoutMs, () => fail(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'Bluetooth pairing timed out', {
466
- phase: 'bond',
467
- reason: 'timeout',
468
- })));
469
- return;
470
- }
471
- if (hasBleKeyMissingSince(target, repairStartedAt)) {
472
- fail(bondInvalid());
473
- return;
474
- }
475
- if (record && record.at > repairStartedAt && isEncrypted(record)) {
476
- finish('re-paired');
477
- return;
478
- }
479
- if (!droppedDuringRepair && dropAt !== undefined && dropAt >= repairStartedAt) {
480
- droppedDuringRepair = true;
481
- arm(keyMissingGraceMs, () => fail(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected)));
482
- }
483
- };
484
- const onLinkEvent = id => {
485
- if (id === target)
486
- evaluate();
487
- };
488
- const onAbort = () => fail(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
489
- listeners.add(onLinkEvent);
490
- const cleanupKeyMissing = onBleKeyMissing(target, evaluate);
491
- signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
492
- arm(resultTimeoutMs, () => finish('unresolved'));
493
- evaluate();
494
- });
495
- };
496
-
497
234
  const BLE_PLX_DEVICE_DISCONNECTED = 201;
498
235
  const IOS_PERIPHERAL_DISCONNECTED = 7;
499
236
  const nativeErrorText$1 = (error) => [error.reason, error.message]
@@ -649,17 +386,31 @@ const shouldRethrowProtocolProbeError = (error) => {
649
386
  code === hdShared.HardwareErrorCode.BleCharacteristicNotifyChangeFailure ||
650
387
  code === hdShared.HardwareErrorCode.BleWriteCharacteristicError);
651
388
  };
652
- const ANDROID_KEY_MISSING_LINK_WINDOW_MS = 10000;
653
- const ANDROID_KEY_MISSING_GRACE_MS = 500;
654
- const ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS = 1500;
655
- const ANDROID_SYSTEM_REPAIR_TIMEOUT_MS = 35000;
656
- const isAndroidLinkLossError = (error) => {
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;
657
410
  const code = error === null || error === void 0 ? void 0 : error.errorCode;
658
411
  return (isNativeBleDisconnectError(error) ||
659
412
  code === hdShared.HardwareErrorCode.BleDeviceNotBonded ||
660
- code === hdShared.HardwareErrorCode.BleDeviceDisconnected ||
661
- code === hdShared.HardwareErrorCode.BleCharacteristicNotifyError ||
662
- code === hdShared.HardwareErrorCode.BleConnectedError);
413
+ code === hdShared.HardwareErrorCode.BleDeviceDisconnected);
663
414
  };
664
415
  const BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
665
416
  const DEVICE_SCAN_TIMEOUT_MS = 3000;
@@ -856,9 +607,9 @@ class ReactNativeBleTransport {
856
607
  this.confirmedProtocolV2 = new Set();
857
608
  this.protocolReprobeFailures = new Map();
858
609
  this.silentDetections = new Map();
610
+ this.iosProbeDisconnectEvidence = new Map();
859
611
  this.androidGattCacheRefreshes = new Set();
860
612
  this.staleBondErrors = new Map();
861
- this.androidLinkStartedAt = new Map();
862
613
  this.acquiringProtocolV2 = new Set();
863
614
  this.protocolV2Assemblers = new Map();
864
615
  this.protocolV2FrameQueues = new Map();
@@ -906,10 +657,6 @@ class ReactNativeBleTransport {
906
657
  init(logger, emitter) {
907
658
  setBleLogger(logger);
908
659
  this.emitter = emitter;
909
- if (reactNative.Platform.OS === 'android') {
910
- startBleKeyMissingTracking();
911
- startBleEncryptionTracking();
912
- }
913
660
  }
914
661
  configure(signedData) {
915
662
  const messages = parseConfigure(signedData);
@@ -1296,14 +1043,7 @@ class ReactNativeBleTransport {
1296
1043
  if (!uuid) {
1297
1044
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
1298
1045
  }
1299
- return this.runLifecycleOperation(uuid, () => __awaiter(this, void 0, void 0, function* () {
1300
- try {
1301
- return yield this.acquireUnlocked(input);
1302
- }
1303
- catch (error) {
1304
- throw yield this.resolveAndroidBondInvalid(uuid, error);
1305
- }
1306
- }));
1046
+ return this.runLifecycleOperation(uuid, () => this.acquireUnlocked(input));
1307
1047
  });
1308
1048
  }
1309
1049
  acquireUnlocked(input) {
@@ -1358,7 +1098,6 @@ class ReactNativeBleTransport {
1358
1098
  }
1359
1099
  let device = null;
1360
1100
  const isAndroid = reactNative.Platform.OS === 'android';
1361
- let androidBondedBeforeConnect = false;
1362
1101
  const refreshAndroidGattCache = isAndroid && (!!skipProtocolProbe || this.androidGattCacheRefreshes.has(uuid));
1363
1102
  let nativeConnectOptions = connectOptions;
1364
1103
  if (isAndroid) {
@@ -1386,16 +1125,10 @@ class ReactNativeBleTransport {
1386
1125
  if (this.stopped)
1387
1126
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1388
1127
  if (reactNative.Platform.OS === 'android') {
1389
- startBleKeyMissingTracking();
1390
- startBleEncryptionTracking();
1391
- this.androidLinkStartedAt.delete(uuid);
1392
1128
  try {
1393
1129
  const bondState = yield pairDevice(uuid);
1394
- androidBondedBeforeConnect = !bondState.bonding && bondState.bonded;
1395
1130
  if (bondState.bonding) {
1396
- yield onDeviceBondState(uuid, this.bondAbortController.signal, {
1397
- systemInitiated: bondState.initiated === false,
1398
- });
1131
+ yield onDeviceBondState(uuid, this.bondAbortController.signal);
1399
1132
  }
1400
1133
  else if (!bondState.bonded) {
1401
1134
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded');
@@ -1407,7 +1140,6 @@ class ReactNativeBleTransport {
1407
1140
  }));
1408
1141
  throw error;
1409
1142
  }
1410
- this.androidLinkStartedAt.set(uuid, Date.now());
1411
1143
  }
1412
1144
  if (this.stopped)
1413
1145
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
@@ -1565,13 +1297,7 @@ class ReactNativeBleTransport {
1565
1297
  if (shouldMapProtocolV2StaleBond) {
1566
1298
  this.acquiringProtocolV2.add(uuid);
1567
1299
  }
1568
- let linkEncryption;
1569
1300
  try {
1570
- if (androidBondedBeforeConnect && !skipProtocolProbe) {
1571
- linkEncryption = yield this.waitForAndroidLinkSecurity(uuid);
1572
- if (this.stopped)
1573
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
1574
- }
1575
1301
  yield this.installTransportForAcquire(uuid, acquiredDevice, {
1576
1302
  writeCharacteristic,
1577
1303
  notifyCharacteristic,
@@ -1610,13 +1336,12 @@ class ReactNativeBleTransport {
1610
1336
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotFound);
1611
1337
  }
1612
1338
  this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1613
- if (linkEncryption === 'unresolved') {
1614
- markBleLinkEncrypted(uuid);
1615
- }
1616
1339
  return { uuid, protocolType };
1617
1340
  }
1618
1341
  catch (error) {
1619
- yield this.disconnectUnlocked(uuid);
1342
+ yield this.disconnectUnlocked(uuid, {
1343
+ preserveIosProbeDisconnect: this.iosProbeDisconnectEvidence.has(uuid),
1344
+ });
1620
1345
  throw error;
1621
1346
  }
1622
1347
  finally {
@@ -1814,64 +1539,14 @@ class ReactNativeBleTransport {
1814
1539
  if (!protocol) {
1815
1540
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
1816
1541
  }
1542
+ if (protocol === 'V2') {
1543
+ return this.callProtocolV2(uuid, name, data, options);
1544
+ }
1817
1545
  const forceRun = name === 'Initialize' || name === 'Cancel';
1818
- if (protocol !== 'V2' && this.runPromise && !forceRun) {
1546
+ if (this.runPromise && !forceRun) {
1819
1547
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1820
1548
  }
1821
- try {
1822
- return protocol === 'V2'
1823
- ? yield this.callProtocolV2(uuid, name, data, options)
1824
- : yield this.callProtocolV1(uuid, name, data, options);
1825
- }
1826
- catch (error) {
1827
- throw yield this.resolveAndroidBondInvalid(uuid, error);
1828
- }
1829
- });
1830
- }
1831
- resolveAndroidBondInvalid(uuid, error) {
1832
- return __awaiter(this, void 0, void 0, function* () {
1833
- if (reactNative.Platform.OS !== 'android' || !isBleKeyMissingSupported())
1834
- return error;
1835
- if (hdShared.isBleStaleBondHardwareError(error) || !isAndroidLinkLossError(error))
1836
- return error;
1837
- const linkStartedAt = this.androidLinkStartedAt.get(uuid);
1838
- if (linkStartedAt === undefined ||
1839
- Date.now() - linkStartedAt > ANDROID_KEY_MISSING_LINK_WINDOW_MS) {
1840
- return error;
1841
- }
1842
- if (!(yield waitForBleKeyMissing(uuid, linkStartedAt, ANDROID_KEY_MISSING_GRACE_MS))) {
1843
- return error;
1844
- }
1845
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android key missing, bond is invalid:', uuid);
1846
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleBondInvalid, undefined, {
1847
- phase: 'connect',
1848
- reason: 'key_missing',
1849
- });
1850
- });
1851
- }
1852
- waitForAndroidLinkSecurity(uuid) {
1853
- return __awaiter(this, void 0, void 0, function* () {
1854
- const linkStartedAt = this.androidLinkStartedAt.get(uuid);
1855
- if (linkStartedAt === undefined)
1856
- return 'unresolved';
1857
- const waitStartedAt = Date.now();
1858
- const result = yield waitForAndroidLinkEncryption({
1859
- deviceId: uuid,
1860
- linkStartedAt,
1861
- resultTimeoutMs: ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS,
1862
- repairTimeoutMs: ANDROID_SYSTEM_REPAIR_TIMEOUT_MS,
1863
- keyMissingGraceMs: ANDROID_KEY_MISSING_GRACE_MS,
1864
- signal: this.bondAbortController.signal,
1865
- onRepairStarted: () => {
1866
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android bond lost, waiting for system re-pairing:', uuid);
1867
- },
1868
- });
1869
- Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Android link encryption', {
1870
- connectIdSuffix: uuid.slice(-8),
1871
- result,
1872
- waitedMs: Date.now() - waitStartedAt,
1873
- });
1874
- return result;
1549
+ return this.callProtocolV1(uuid, name, data, options);
1875
1550
  });
1876
1551
  }
1877
1552
  callProtocolV1(uuid, name, data, options) {
@@ -2087,9 +1762,7 @@ class ReactNativeBleTransport {
2087
1762
  this.androidPriorityResetTimers.forEach(timeout => clearTimeout(timeout));
2088
1763
  this.androidPriorityResetTimers.clear();
2089
1764
  this.androidHighPriorityDevices.clear();
2090
- this.androidLinkStartedAt.clear();
2091
- stopBleKeyMissingTracking();
2092
- stopBleEncryptionTracking();
1765
+ this.iosProbeDisconnectEvidence.clear();
2093
1766
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected);
2094
1767
  (_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.reject(error);
2095
1768
  this.runPromise = null;
@@ -2118,7 +1791,7 @@ class ReactNativeBleTransport {
2118
1791
  return this.runLifecycleOperation(session, () => this.disconnectUnlocked(session));
2119
1792
  });
2120
1793
  }
2121
- disconnectUnlocked(session) {
1794
+ disconnectUnlocked(session, options) {
2122
1795
  var _a, _b, _c;
2123
1796
  return __awaiter(this, void 0, void 0, function* () {
2124
1797
  yield this.protocolV2Links.invalidateLink(session, 'React Native BLE transport disconnected');
@@ -2154,6 +1827,9 @@ class ReactNativeBleTransport {
2154
1827
  this.deviceProtocolHints.delete(session);
2155
1828
  this.sessionProtocols.delete(session);
2156
1829
  this.protocolReprobeFailures.delete(session);
1830
+ if (!(options === null || options === void 0 ? void 0 : options.preserveIosProbeDisconnect)) {
1831
+ this.iosProbeDisconnectEvidence.delete(session);
1832
+ }
2157
1833
  this.protocolV2Assemblers.delete(session);
2158
1834
  this.resetProtocolV2Frames(session);
2159
1835
  try {
@@ -2429,6 +2105,7 @@ class ReactNativeBleTransport {
2429
2105
  this.deviceProtocol.delete(uuid);
2430
2106
  this.probingProtocols.delete(uuid);
2431
2107
  this.staleBondErrors.delete(uuid);
2108
+ this.iosProbeDisconnectEvidence.delete(uuid);
2432
2109
  this.acquiringProtocolV2.delete(uuid);
2433
2110
  this.protocolV2Assemblers.delete(uuid);
2434
2111
  this.resetProtocolV2Frames(uuid);
@@ -2500,6 +2177,7 @@ class ReactNativeBleTransport {
2500
2177
  this.deviceProtocol.delete(uuid);
2501
2178
  this.probingProtocols.delete(uuid);
2502
2179
  this.staleBondErrors.delete(uuid);
2180
+ this.iosProbeDisconnectEvidence.delete(uuid);
2503
2181
  this.acquiringProtocolV2.delete(uuid);
2504
2182
  this.protocolV2Assemblers.delete(uuid);
2505
2183
  this.resetProtocolV2Frames(uuid);
@@ -2546,6 +2224,7 @@ class ReactNativeBleTransport {
2546
2224
  this.deviceProtocol.clear();
2547
2225
  this.probingProtocols.clear();
2548
2226
  this.staleBondErrors.clear();
2227
+ this.iosProbeDisconnectEvidence.clear();
2549
2228
  this.acquiringProtocolV2.clear();
2550
2229
  this.sessionProtocols.clear();
2551
2230
  this.protocolReprobeFailures.clear();
@@ -2575,6 +2254,35 @@ class ReactNativeBleTransport {
2575
2254
  createProtocolDetectionError() {
2576
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');
2577
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
+ }
2578
2286
  clearProbeProtocol(uuid, protocol) {
2579
2287
  if (this.probingProtocols.get(uuid) === protocol) {
2580
2288
  this.probingProtocols.delete(uuid);
@@ -2588,9 +2296,10 @@ class ReactNativeBleTransport {
2588
2296
  return (_a = this.deviceProtocol.get(uuid)) !== null && _a !== void 0 ? _a : this.probingProtocols.get(uuid);
2589
2297
  }
2590
2298
  detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
2591
- var _a;
2299
+ var _a, _b;
2592
2300
  return __awaiter(this, void 0, void 0, function* () {
2593
2301
  if (expectedProtocol === 'V1') {
2302
+ this.iosProbeDisconnectEvidence.delete(uuid);
2594
2303
  this.deviceProtocol.set(uuid, 'V1');
2595
2304
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
2596
2305
  deviceId: uuid,
@@ -2604,6 +2313,7 @@ class ReactNativeBleTransport {
2604
2313
  }
2605
2314
  if (expectedProtocol === 'V2') {
2606
2315
  if (yield this.probeProtocolV2(uuid)) {
2316
+ this.iosProbeDisconnectEvidence.delete(uuid);
2607
2317
  this.deviceProtocol.set(uuid, 'V2');
2608
2318
  this.sessionProtocols.set(uuid, 'V2');
2609
2319
  this.confirmedProtocolV2.add(uuid);
@@ -2614,6 +2324,19 @@ class ReactNativeBleTransport {
2614
2324
  });
2615
2325
  return 'V2';
2616
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;
2617
2340
  throw this.createProtocolMismatchError(expectedProtocol);
2618
2341
  }
2619
2342
  const sessionProtocol = this.sessionProtocols.get(uuid);
@@ -2637,6 +2360,7 @@ class ReactNativeBleTransport {
2637
2360
  }
2638
2361
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
2639
2362
  if (detected) {
2363
+ this.iosProbeDisconnectEvidence.delete(uuid);
2640
2364
  this.deviceProtocol.set(uuid, protocol);
2641
2365
  this.sessionProtocols.set(uuid, protocol);
2642
2366
  if (protocol === 'V2') {
@@ -2662,6 +2386,26 @@ class ReactNativeBleTransport {
2662
2386
  }
2663
2387
  this.deviceProtocol.delete(uuid);
2664
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;
2665
2409
  throw this.createProtocolDetectionError();
2666
2410
  });
2667
2411
  }
@@ -2745,7 +2489,12 @@ class ReactNativeBleTransport {
2745
2489
  catch (error) {
2746
2490
  this.clearProbeProtocol(uuid, 'V1');
2747
2491
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
2748
- if (shouldRethrowProtocolProbeError(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) {
2749
2498
  throw error;
2750
2499
  }
2751
2500
  return false;
@@ -2761,18 +2510,28 @@ class ReactNativeBleTransport {
2761
2510
  this.probingProtocols.set(uuid, 'V2');
2762
2511
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2763
2512
  this.throwIfStaleBondError(uuid);
2764
- const detected = yield transport.probeProtocolV2({
2765
- call: (name, data, options) => this.callProtocolV2(uuid, name, data, options),
2766
- timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT_MS,
2767
- logger: Log,
2768
- logPrefix: 'ProtocolV2 RN-BLE',
2769
- onProbeFailed: () => {
2770
- var _a;
2771
- (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
2772
- this.resetProtocolV2Frames(uuid);
2773
- },
2774
- shouldRethrow: shouldRethrowProtocolProbeError,
2775
- });
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
+ }
2776
2535
  if (!detected) {
2777
2536
  this.clearProbeProtocol(uuid, 'V2');
2778
2537
  }
@@ -3123,12 +2882,8 @@ class ReactNativeBleTransport {
3123
2882
  }
3124
2883
  }
3125
2884
 
3126
- exports.ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS = ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS;
3127
- exports.ANDROID_KEY_MISSING_GRACE_MS = ANDROID_KEY_MISSING_GRACE_MS;
3128
- exports.ANDROID_KEY_MISSING_LINK_WINDOW_MS = ANDROID_KEY_MISSING_LINK_WINDOW_MS;
3129
2885
  exports.ANDROID_LINK_DROP_QUIET_MS = ANDROID_LINK_DROP_QUIET_MS;
3130
2886
  exports.ANDROID_MTU_EXCHANGE_TIMEOUT_MS = ANDROID_MTU_EXCHANGE_TIMEOUT_MS;
3131
- exports.ANDROID_SYSTEM_REPAIR_TIMEOUT_MS = ANDROID_SYSTEM_REPAIR_TIMEOUT_MS;
3132
2887
  exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD;
3133
2888
  exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
3134
2889
  exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;