@onekeyfe/hd-transport-react-native 1.2.0-alpha.147 → 1.2.0-alpha.149

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.d.ts CHANGED
@@ -91,6 +91,7 @@ declare const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
91
91
  declare const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
92
92
  /** BLE setup timeouts since the last successful setup before the manager is recreated. */
93
93
  declare const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
94
+ declare const BLE_SETUP_WEDGED_MESSAGE = "BLE setup wedged repeatedly";
94
95
  type IOneKeyDevice = OneKeyDeviceInfoBase & Device;
95
96
  declare class ReactNativeBleTransport {
96
97
  blePlxManager: BleManager | undefined;
@@ -121,6 +122,8 @@ declare class ReactNativeBleTransport {
121
122
  private deviceProtocolHints;
122
123
  /** Protocol this device actually answered on, kept across reconnects of one session. */
123
124
  private sessionProtocols;
125
+ /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
126
+ private confirmedProtocolV2;
124
127
  /** Consecutive detections that failed while trusting sessionProtocols. */
125
128
  private protocolReprobeFailures;
126
129
  private protocolV2Assemblers;
@@ -179,8 +182,10 @@ declare class ReactNativeBleTransport {
179
182
  * Give up on a BLE setup operation the native layer did not settle. The abandoned
180
183
  * operation still owns native connection/GATT state that can poison the next attempt,
181
184
  * so it is cleared here without awaiting the same queue that stopped responding.
185
+ * Returns true when the manager itself was reset so the caller can stop Core retries.
182
186
  */
183
187
  private abandonStalledConnection;
188
+ private createWedgedBleSetupError;
184
189
  private getCachedTransport;
185
190
  /**
186
191
  * Write one packet under a bounded budget. A write that never settles means the
@@ -224,4 +229,4 @@ declare class ReactNativeBleTransport {
224
229
  getProtocolType(path: string): ProtocolType | undefined;
225
230
  }
226
231
 
227
- export { BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD, BLE_CONNECT_TIMEOUT_MS, BLE_GATT_SETUP_TIMEOUT_MS, BLE_NATIVE_TEARDOWN_TIMEOUT_MS, BLE_WRITE_PACKET_TIMEOUT_MS, BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD, IOneKeyDevice, PROTOCOL_REPROBE_FALLBACK_ATTEMPTS, ProtocolV2BleTuning, configureProtocolV2BleTuning, ReactNativeBleTransport as default, getFirmwareUploadWriteRetryType, getProtocolV2BleTuning, resetProtocolV2BleTuning };
232
+ export { BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD, BLE_CONNECT_TIMEOUT_MS, BLE_GATT_SETUP_TIMEOUT_MS, BLE_NATIVE_TEARDOWN_TIMEOUT_MS, BLE_SETUP_WEDGED_MESSAGE, BLE_WRITE_PACKET_TIMEOUT_MS, BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD, IOneKeyDevice, PROTOCOL_REPROBE_FALLBACK_ATTEMPTS, ProtocolV2BleTuning, configureProtocolV2BleTuning, ReactNativeBleTransport as default, getFirmwareUploadWriteRetryType, getProtocolV2BleTuning, resetProtocolV2BleTuning };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -311,9 +311,6 @@ function resetProtocolV2BleTuning() {
311
311
  function getProtocolV2BleTuning() {
312
312
  return Object.assign({}, protocolV2BleTuning);
313
313
  }
314
- function inferProtocolHintFromDeviceName(name) {
315
- return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
316
- }
317
314
  function getDeviceDisplayName(device) {
318
315
  return (device === null || device === void 0 ? void 0 : device.name) || (device === null || device === void 0 ? void 0 : device.localName) || null;
319
316
  }
@@ -336,9 +333,14 @@ const BLE_GATT_SETUP_TIMEOUT_MS = 10000;
336
333
  const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
337
334
  const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
338
335
  const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
336
+ const BLE_SETUP_WEDGED_MESSAGE = 'BLE setup wedged repeatedly';
339
337
  const isConnectTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleConnectedError &&
340
338
  typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
341
339
  error.message.startsWith(CONNECT_TIMEOUT_MESSAGE);
340
+ const isWedgedBleSetupError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.PollingTimeout &&
341
+ typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' &&
342
+ error.message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
343
+ const shouldRethrowBleSetupError = (error) => isConnectTimeoutError(error) || isWedgedBleSetupError(error);
342
344
  const isNativeOperationTimeoutError = (error) => (error === null || error === void 0 ? void 0 : error.errorCode) === reactNativeBlePlx.BleErrorCode.OperationTimedOut;
343
345
  const tryToGetConfiguration = (device) => {
344
346
  if (!device || !device.serviceUUIDs)
@@ -404,6 +406,7 @@ class ReactNativeBleTransport {
404
406
  this.connectionSetupTimeoutCounts = new Map();
405
407
  this.deviceProtocolHints = new Map();
406
408
  this.sessionProtocols = new Map();
409
+ this.confirmedProtocolV2 = new Set();
407
410
  this.protocolReprobeFailures = new Map();
408
411
  this.protocolV2Assemblers = new Map();
409
412
  this.protocolV2FrameQueues = new Map();
@@ -721,16 +724,11 @@ class ReactNativeBleTransport {
721
724
  var _a;
722
725
  if (deviceList.every(d => d.id !== device.id)) {
723
726
  const displayName = (_a = getDeviceDisplayName(device)) !== null && _a !== void 0 ? _a : 'Unknown BLE Device';
724
- const protocolHint = inferProtocolHintFromDeviceName(displayName);
725
- if (protocolHint) {
726
- this.deviceProtocolHints.set(device.id, protocolHint);
727
- }
728
727
  deviceList.push(Object.assign(Object.assign({}, device), { name: displayName, commType: 'ble' }));
729
728
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] OneKey BLE device discovered', {
730
729
  deviceId: device.id,
731
730
  name: displayName,
732
731
  serviceUUIDs: device.serviceUUIDs,
733
- protocolHint,
734
732
  });
735
733
  }
736
734
  };
@@ -802,7 +800,7 @@ class ReactNativeBleTransport {
802
800
  });
803
801
  }
804
802
  acquireUnlocked(input) {
805
- var _a, _b;
803
+ var _a;
806
804
  return __awaiter(this, void 0, void 0, function* () {
807
805
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
808
806
  const cachedTransport = transportCache[uuid];
@@ -860,7 +858,7 @@ class ReactNativeBleTransport {
860
858
  }
861
859
  catch (e) {
862
860
  Log === null || Log === void 0 ? void 0 : Log.debug('try to connect to device has error: ', e);
863
- if (isConnectTimeoutError(e)) {
861
+ if (shouldRethrowBleSetupError(e)) {
864
862
  throw e;
865
863
  }
866
864
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
@@ -888,7 +886,7 @@ class ReactNativeBleTransport {
888
886
  }
889
887
  catch (e) {
890
888
  Log === null || Log === void 0 ? void 0 : Log.debug('not connected, try to connect to device has error: ', e);
891
- if (isConnectTimeoutError(e)) {
889
+ if (shouldRethrowBleSetupError(e)) {
892
890
  throw e;
893
891
  }
894
892
  if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
@@ -916,7 +914,7 @@ class ReactNativeBleTransport {
916
914
  const { writeCharacteristic, notifyCharacteristic } = yield this.resolveCharacteristicsWithTimeout(uuid, acquiredDevice);
917
915
  const protocolHint = expectedProtocol
918
916
  ? undefined
919
- : (_b = (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
917
+ : (_a = input.protocolHint) !== null && _a !== void 0 ? _a : this.deviceProtocolHints.get(uuid);
920
918
  yield this.releaseUnlocked(uuid, true);
921
919
  if (protocolHint) {
922
920
  this.deviceProtocolHints.set(uuid, protocolHint);
@@ -937,7 +935,12 @@ class ReactNativeBleTransport {
937
935
  return { uuid, protocolType };
938
936
  }
939
937
  catch (error) {
940
- yield this.releaseUnlocked(uuid, true);
938
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === hdShared.HardwareErrorCode.BleDeviceBondError) {
939
+ yield this.disconnectUnlocked(uuid);
940
+ }
941
+ else {
942
+ yield this.releaseUnlocked(uuid, true);
943
+ }
941
944
  throw error;
942
945
  }
943
946
  });
@@ -1495,7 +1498,10 @@ class ReactNativeBleTransport {
1495
1498
  }
1496
1499
  catch (error) {
1497
1500
  if (timedOut || isNativeOperationTimeoutError(error)) {
1498
- this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1501
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1502
+ if (resetManager) {
1503
+ throw this.createWedgedBleSetupError();
1504
+ }
1499
1505
  }
1500
1506
  throw error;
1501
1507
  }
@@ -1526,7 +1532,10 @@ class ReactNativeBleTransport {
1526
1532
  }
1527
1533
  catch (error) {
1528
1534
  if (timedOut || isNativeOperationTimeoutError(error)) {
1529
- this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1535
+ const resetManager = this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1536
+ if (resetManager) {
1537
+ throw this.createWedgedBleSetupError();
1538
+ }
1530
1539
  }
1531
1540
  throw error;
1532
1541
  }
@@ -1558,7 +1567,12 @@ class ReactNativeBleTransport {
1558
1567
  Log === null || Log === void 0 ? void 0 : Log.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1559
1568
  this.resetPlxManager();
1560
1569
  this.connectionSetupTimeoutCounts.delete(uuid);
1570
+ return true;
1561
1571
  }
1572
+ return false;
1573
+ }
1574
+ createWedgedBleSetupError() {
1575
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.PollingTimeout, BLE_SETUP_WEDGED_MESSAGE);
1562
1576
  }
1563
1577
  getCachedTransport(uuid) {
1564
1578
  const transport = transportCache[uuid];
@@ -1659,6 +1673,7 @@ class ReactNativeBleTransport {
1659
1673
  this.deviceProtocol.clear();
1660
1674
  this.probingProtocols.clear();
1661
1675
  this.sessionProtocols.clear();
1676
+ this.confirmedProtocolV2.clear();
1662
1677
  this.protocolReprobeFailures.clear();
1663
1678
  this.writeTimeoutCounts.clear();
1664
1679
  this.connectionSetupTimeoutCounts.clear();
@@ -1671,8 +1686,9 @@ class ReactNativeBleTransport {
1671
1686
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] BLE manager destroy failed (ignored):', error);
1672
1687
  }
1673
1688
  }
1674
- createProtocolMismatchError(expected) {
1675
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1689
+ createProtocolMismatchError(expected, uuid) {
1690
+ const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1691
+ return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1676
1692
  }
1677
1693
  createProtocolDetectionError() {
1678
1694
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
@@ -1712,12 +1728,13 @@ class ReactNativeBleTransport {
1712
1728
  });
1713
1729
  return 'V1';
1714
1730
  }
1715
- throw this.createProtocolMismatchError(expectedProtocol);
1731
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
1716
1732
  }
1717
1733
  if (expectedProtocol === 'V2') {
1718
1734
  if (yield this.probeProtocolV2(uuid)) {
1719
1735
  this.deviceProtocol.set(uuid, 'V2');
1720
1736
  this.sessionProtocols.set(uuid, 'V2');
1737
+ this.confirmedProtocolV2.add(uuid);
1721
1738
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1722
1739
  deviceId: uuid,
1723
1740
  protocol: 'V2',
@@ -1725,7 +1742,7 @@ class ReactNativeBleTransport {
1725
1742
  });
1726
1743
  return 'V2';
1727
1744
  }
1728
- throw this.createProtocolMismatchError(expectedProtocol);
1745
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
1729
1746
  }
1730
1747
  const sessionProtocol = this.sessionProtocols.get(uuid);
1731
1748
  const reprobeFailures = (_a = this.protocolReprobeFailures.get(uuid)) !== null && _a !== void 0 ? _a : 0;
@@ -1749,6 +1766,9 @@ class ReactNativeBleTransport {
1749
1766
  if (detected) {
1750
1767
  this.deviceProtocol.set(uuid, protocol);
1751
1768
  this.sessionProtocols.set(uuid, protocol);
1769
+ if (protocol === 'V2') {
1770
+ this.confirmedProtocolV2.add(uuid);
1771
+ }
1752
1772
  this.protocolReprobeFailures.delete(uuid);
1753
1773
  Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol detected', {
1754
1774
  deviceId: uuid,
@@ -2175,6 +2195,7 @@ exports.BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_CONNECT_TIMEOUT_MANAGE
2175
2195
  exports.BLE_CONNECT_TIMEOUT_MS = BLE_CONNECT_TIMEOUT_MS;
2176
2196
  exports.BLE_GATT_SETUP_TIMEOUT_MS = BLE_GATT_SETUP_TIMEOUT_MS;
2177
2197
  exports.BLE_NATIVE_TEARDOWN_TIMEOUT_MS = BLE_NATIVE_TEARDOWN_TIMEOUT_MS;
2198
+ exports.BLE_SETUP_WEDGED_MESSAGE = BLE_SETUP_WEDGED_MESSAGE;
2178
2199
  exports.BLE_WRITE_PACKET_TIMEOUT_MS = BLE_WRITE_PACKET_TIMEOUT_MS;
2179
2200
  exports.BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD = BLE_WRITE_TIMEOUT_MANAGER_RESET_THRESHOLD;
2180
2201
  exports.PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = PROTOCOL_REPROBE_FALLBACK_ATTEMPTS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.0-alpha.147",
3
+ "version": "1.2.0-alpha.149",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,11 +20,11 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-core": "1.2.0-alpha.147",
24
- "@onekeyfe/hd-shared": "1.2.0-alpha.147",
25
- "@onekeyfe/hd-transport": "1.2.0-alpha.147",
23
+ "@onekeyfe/hd-core": "1.2.0-alpha.149",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.149",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.149",
26
26
  "@onekeyfe/react-native-ble-utils": "^0.1.6",
27
27
  "react-native-ble-plx": "3.5.1"
28
28
  },
29
- "gitHead": "a34e98865e6be227cf3e00d486146e6592761db0"
29
+ "gitHead": "0c2b72c1edbafb2b738e3eb0b9ecdad8d8b92df7"
30
30
  }
@@ -6,6 +6,7 @@ import ReactNativeBleTransport, {
6
6
  BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD,
7
7
  BLE_CONNECT_TIMEOUT_MS,
8
8
  BLE_GATT_SETUP_TIMEOUT_MS,
9
+ BLE_SETUP_WEDGED_MESSAGE,
9
10
  } from '../index';
10
11
  import protocolV1Schema from './protocolV1SchemaFixture';
11
12
 
@@ -179,7 +180,7 @@ describe('BLE connect timeout', () => {
179
180
  expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(UUID);
180
181
  });
181
182
 
182
- test('repeated stalled connects recreate the BLE manager', async () => {
183
+ test('repeated stalled connects recreate the BLE manager and stop with PollingTimeout', async () => {
183
184
  const { transport, bleManager } = createHarness(
184
185
  () =>
185
186
  new Promise(() => {
@@ -188,10 +189,12 @@ describe('BLE connect timeout', () => {
188
189
  );
189
190
  const destroy = jest.fn();
190
191
  (bleManager as unknown as { destroy: jest.Mock }).destroy = destroy;
192
+ const errors: Array<{ errorCode?: unknown; message?: unknown }> = [];
191
193
 
192
194
  for (let attempt = 0; attempt < BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD; attempt += 1) {
193
195
  let settled = false;
194
- transport.acquire({ uuid: UUID }).catch(() => {
196
+ transport.acquire({ uuid: UUID }).catch(error => {
197
+ errors.push(error);
195
198
  settled = true;
196
199
  });
197
200
  // eslint-disable-next-line no-await-in-loop
@@ -202,6 +205,11 @@ describe('BLE connect timeout', () => {
202
205
 
203
206
  expect(destroy).toHaveBeenCalledTimes(1);
204
207
  expect((transport as unknown as { blePlxManager?: unknown }).blePlxManager).toBeUndefined();
208
+ expect(errors[0]?.errorCode).toBe(HardwareErrorCode.BleConnectedError);
209
+ expect(errors[1]).toMatchObject({
210
+ errorCode: HardwareErrorCode.PollingTimeout,
211
+ message: BLE_SETUP_WEDGED_MESSAGE,
212
+ });
205
213
  });
206
214
 
207
215
  test('a recreated BLE manager starts with fresh timeout budgets for every device', () => {
@@ -224,12 +232,15 @@ describe('BLE connect timeout', () => {
224
232
  errorCode: BleErrorCode.OperationTimedOut,
225
233
  });
226
234
 
227
- for (let attempt = 0; attempt < BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD; attempt += 1) {
228
- // eslint-disable-next-line no-await-in-loop
229
- await expect(
230
- (transport as any).connectWithTimeout(UUID, () => Promise.reject(nativeTimeout))
231
- ).rejects.toBe(nativeTimeout);
232
- }
235
+ await expect(
236
+ (transport as any).connectWithTimeout(UUID, () => Promise.reject(nativeTimeout))
237
+ ).rejects.toBe(nativeTimeout);
238
+ await expect(
239
+ (transport as any).connectWithTimeout(UUID, () => Promise.reject(nativeTimeout))
240
+ ).rejects.toMatchObject({
241
+ errorCode: HardwareErrorCode.PollingTimeout,
242
+ message: BLE_SETUP_WEDGED_MESSAGE,
243
+ });
233
244
 
234
245
  expect(destroy).toHaveBeenCalledTimes(1);
235
246
  });
@@ -405,7 +405,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
405
405
  await transport.release(uuid, true);
406
406
  });
407
407
 
408
- test('actively probes Protocol V2 on iOS when only a name-derived hint is available', async () => {
408
+ test('detects Protocol V2 on iOS without using the BLE name as a protocol hint', async () => {
409
409
  const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
410
410
  deviceName: 'Pro2 6E9E',
411
411
  });
@@ -415,15 +415,68 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
415
415
  protocolType: 'V2',
416
416
  });
417
417
  expect(device.requestMTU).toHaveBeenCalledWith(247);
418
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
418
+ expect(writeCharacteristic.writeWithResponse.mock.calls.length).toBeGreaterThan(1);
419
419
 
420
420
  await expect(
421
421
  transport.call(uuid, 'Ping', { message: 'first-core-command' })
422
422
  ).resolves.toBeDefined();
423
- expect(sentSeqs).toEqual([1, 2]);
423
+ expect(sentSeqs.filter(seq => seq > 0)).toEqual([1, 2]);
424
424
  await transport.release(uuid, true);
425
425
  });
426
426
 
427
+ test.each(['ios', 'android'] as const)(
428
+ 'keeps a first expected Protocol V2 probe miss retryable on %s',
429
+ async platform => {
430
+ setPlatformOS(platform);
431
+ const { transport, uuid, device } = createHarness();
432
+ jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
433
+
434
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
435
+ errorCode: HardwareErrorCode.RuntimeError,
436
+ });
437
+
438
+ expect(device.cancelConnection).not.toHaveBeenCalled();
439
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
440
+ }
441
+ );
442
+
443
+ test.each(['ios', 'android'] as const)(
444
+ 'keeps a second expected Protocol V2 probe miss retryable on %s',
445
+ async platform => {
446
+ setPlatformOS(platform);
447
+ const { transport, uuid, device } = createHarness();
448
+ jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
449
+
450
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
451
+ errorCode: HardwareErrorCode.RuntimeError,
452
+ });
453
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
454
+ errorCode: HardwareErrorCode.RuntimeError,
455
+ });
456
+
457
+ expect(device.cancelConnection).not.toHaveBeenCalled();
458
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
459
+ }
460
+ );
461
+
462
+ test.each(['ios', 'android'] as const)(
463
+ 'reports a stale bond on %s when a previously confirmed Protocol V2 device stops responding',
464
+ async platform => {
465
+ setPlatformOS(platform);
466
+ const { transport, uuid, device } = createHarness();
467
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
468
+ await transport.release(uuid, true);
469
+ jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
470
+
471
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
472
+ errorCode: HardwareErrorCode.BleDeviceBondError,
473
+ });
474
+
475
+ expect(device.cancelConnection).toHaveBeenCalled();
476
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
477
+ }
478
+ );
479
+
427
480
  test('waits for an in-flight release before reacquiring the same device', async () => {
428
481
  const { transport, uuid } = createHarness();
429
482
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
package/src/index.ts CHANGED
@@ -203,10 +203,6 @@ export function getProtocolV2BleTuning() {
203
203
  return { ...protocolV2BleTuning };
204
204
  }
205
205
 
206
- function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
207
- return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
208
- }
209
-
210
206
  function getDeviceDisplayName(device?: Device | null) {
211
207
  return device?.name || device?.localName || null;
212
208
  }
@@ -258,10 +254,17 @@ export const PROTOCOL_REPROBE_FALLBACK_ATTEMPTS = 3;
258
254
  /** BLE setup timeouts since the last successful setup before the manager is recreated. */
259
255
  export const BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD = 2;
260
256
  const CONNECT_TIMEOUT_MESSAGE = 'BLE connect timeout after';
257
+ export const BLE_SETUP_WEDGED_MESSAGE = 'BLE setup wedged repeatedly';
261
258
  const isConnectTimeoutError = (error: unknown): boolean =>
262
259
  (error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleConnectedError &&
263
260
  typeof (error as { message?: unknown })?.message === 'string' &&
264
261
  (error as { message: string }).message.startsWith(CONNECT_TIMEOUT_MESSAGE);
262
+ const isWedgedBleSetupError = (error: unknown): boolean =>
263
+ (error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.PollingTimeout &&
264
+ typeof (error as { message?: unknown })?.message === 'string' &&
265
+ (error as { message: string }).message.startsWith(BLE_SETUP_WEDGED_MESSAGE);
266
+ const shouldRethrowBleSetupError = (error: unknown): boolean =>
267
+ isConnectTimeoutError(error) || isWedgedBleSetupError(error);
265
268
  const isNativeOperationTimeoutError = (error: unknown): boolean =>
266
269
  (error as { errorCode?: unknown })?.errorCode === BleErrorCode.OperationTimedOut;
267
270
 
@@ -381,6 +384,9 @@ export default class ReactNativeBleTransport {
381
384
  /** Protocol this device actually answered on, kept across reconnects of one session. */
382
385
  private sessionProtocols: Map<string, ProtocolType> = new Map();
383
386
 
387
+ /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
388
+ private confirmedProtocolV2 = new Set<string>();
389
+
384
390
  /** Consecutive detections that failed while trusting sessionProtocols. */
385
391
  private protocolReprobeFailures: Map<string, number> = new Map();
386
392
 
@@ -765,10 +771,7 @@ export default class ReactNativeBleTransport {
765
771
  const addDevice = (device: Device) => {
766
772
  if (deviceList.every(d => d.id !== device.id)) {
767
773
  const displayName = getDeviceDisplayName(device) ?? 'Unknown BLE Device';
768
- const protocolHint = inferProtocolHintFromDeviceName(displayName);
769
- if (protocolHint) {
770
- this.deviceProtocolHints.set(device.id, protocolHint);
771
- }
774
+
772
775
  deviceList.push({
773
776
  ...device,
774
777
  name: displayName,
@@ -778,7 +781,6 @@ export default class ReactNativeBleTransport {
778
781
  deviceId: device.id,
779
782
  name: displayName,
780
783
  serviceUUIDs: device.serviceUUIDs,
781
- protocolHint,
782
784
  });
783
785
  }
784
786
  };
@@ -944,7 +946,7 @@ export default class ReactNativeBleTransport {
944
946
  );
945
947
  } catch (e) {
946
948
  Log?.debug('try to connect to device has error: ', e);
947
- if (isConnectTimeoutError(e)) {
949
+ if (shouldRethrowBleSetupError(e)) {
948
950
  throw e;
949
951
  }
950
952
  if (
@@ -978,7 +980,7 @@ export default class ReactNativeBleTransport {
978
980
  );
979
981
  } catch (e) {
980
982
  Log?.debug('not connected, try to connect to device has error: ', e);
981
- if (isConnectTimeoutError(e)) {
983
+ if (shouldRethrowBleSetupError(e)) {
982
984
  throw e;
983
985
  }
984
986
  if (
@@ -1015,9 +1017,7 @@ export default class ReactNativeBleTransport {
1015
1017
 
1016
1018
  const protocolHint = expectedProtocol
1017
1019
  ? undefined
1018
- : input.protocolHint ??
1019
- this.deviceProtocolHints.get(uuid) ??
1020
- inferProtocolHintFromDeviceName(getDeviceDisplayName(acquiredDevice));
1020
+ : input.protocolHint ?? this.deviceProtocolHints.get(uuid);
1021
1021
 
1022
1022
  // release transport before new transport instance
1023
1023
  await this.releaseUnlocked(uuid, true);
@@ -1046,7 +1046,11 @@ export default class ReactNativeBleTransport {
1046
1046
  this.attachDisconnectSubscription(currentTransport, currentTransport.device, uuid);
1047
1047
  return { uuid, protocolType };
1048
1048
  } catch (error) {
1049
- await this.releaseUnlocked(uuid, true);
1049
+ if ((error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleDeviceBondError) {
1050
+ await this.disconnectUnlocked(uuid);
1051
+ } else {
1052
+ await this.releaseUnlocked(uuid, true);
1053
+ }
1050
1054
  throw error;
1051
1055
  }
1052
1056
  }
@@ -1244,7 +1248,7 @@ export default class ReactNativeBleTransport {
1244
1248
 
1245
1249
  this.deviceProtocol.delete(uuid);
1246
1250
  this.probingProtocols.delete(uuid);
1247
- // Preserve a name-derived hint across disconnects so reconnect can probe V2 first.
1251
+ // Confirmed protocol and caller hints stay in deviceProtocol / protocolHint.
1248
1252
  this.protocolV2Assemblers.get(uuid)?.reset();
1249
1253
  this.protocolV2Assemblers.delete(uuid);
1250
1254
  this.resetProtocolV2Frames(uuid);
@@ -1757,7 +1761,13 @@ export default class ReactNativeBleTransport {
1757
1761
  return result;
1758
1762
  } catch (error) {
1759
1763
  if (timedOut || isNativeOperationTimeoutError(error)) {
1760
- this.abandonStalledConnection(uuid, timedOut ? 'connect-backstop' : 'connect-native');
1764
+ const resetManager = this.abandonStalledConnection(
1765
+ uuid,
1766
+ timedOut ? 'connect-backstop' : 'connect-native'
1767
+ );
1768
+ if (resetManager) {
1769
+ throw this.createWedgedBleSetupError();
1770
+ }
1761
1771
  }
1762
1772
  throw error;
1763
1773
  } finally {
@@ -1793,7 +1803,13 @@ export default class ReactNativeBleTransport {
1793
1803
  return result;
1794
1804
  } catch (error) {
1795
1805
  if (timedOut || isNativeOperationTimeoutError(error)) {
1796
- this.abandonStalledConnection(uuid, timedOut ? 'gatt-backstop' : 'gatt-native');
1806
+ const resetManager = this.abandonStalledConnection(
1807
+ uuid,
1808
+ timedOut ? 'gatt-backstop' : 'gatt-native'
1809
+ );
1810
+ if (resetManager) {
1811
+ throw this.createWedgedBleSetupError();
1812
+ }
1797
1813
  }
1798
1814
  throw error;
1799
1815
  } finally {
@@ -1805,11 +1821,12 @@ export default class ReactNativeBleTransport {
1805
1821
  * Give up on a BLE setup operation the native layer did not settle. The abandoned
1806
1822
  * operation still owns native connection/GATT state that can poison the next attempt,
1807
1823
  * so it is cleared here without awaiting the same queue that stopped responding.
1824
+ * Returns true when the manager itself was reset so the caller can stop Core retries.
1808
1825
  */
1809
1826
  private abandonStalledConnection(
1810
1827
  uuid: string,
1811
1828
  stage: 'connect-backstop' | 'connect-native' | 'gatt-backstop' | 'gatt-native'
1812
- ) {
1829
+ ): boolean {
1813
1830
  const timeouts = (this.connectionSetupTimeoutCounts.get(uuid) ?? 0) + 1;
1814
1831
  this.connectionSetupTimeoutCounts.set(uuid, timeouts);
1815
1832
  Log?.error('[ReactNativeBleTransport] BLE setup timed out:', uuid, {
@@ -1835,7 +1852,15 @@ export default class ReactNativeBleTransport {
1835
1852
  Log?.error('[ReactNativeBleTransport] BLE setup wedged repeatedly, resetting BLE manager');
1836
1853
  this.resetPlxManager();
1837
1854
  this.connectionSetupTimeoutCounts.delete(uuid);
1855
+ return true;
1838
1856
  }
1857
+ return false;
1858
+ }
1859
+
1860
+ private createWedgedBleSetupError() {
1861
+ // PollingTimeout is not retried by connectDeviceForBle and already maps
1862
+ // to the App "connection failed" help text.
1863
+ return ERRORS.TypedError(HardwareErrorCode.PollingTimeout, BLE_SETUP_WEDGED_MESSAGE);
1839
1864
  }
1840
1865
 
1841
1866
  private getCachedTransport(uuid: string) {
@@ -1965,6 +1990,7 @@ export default class ReactNativeBleTransport {
1965
1990
  this.deviceProtocol.clear();
1966
1991
  this.probingProtocols.clear();
1967
1992
  this.sessionProtocols.clear();
1993
+ this.confirmedProtocolV2.clear();
1968
1994
  this.protocolReprobeFailures.clear();
1969
1995
  this.writeTimeoutCounts.clear();
1970
1996
  this.connectionSetupTimeoutCounts.clear();
@@ -1977,9 +2003,12 @@ export default class ReactNativeBleTransport {
1977
2003
  }
1978
2004
  }
1979
2005
 
1980
- private createProtocolMismatchError(expected: ProtocolType) {
2006
+ private createProtocolMismatchError(expected: ProtocolType, uuid: string) {
2007
+ // A generic Ping miss is not a bond failure. Only a later miss after this
2008
+ // endpoint already answered V2, or a native encryption/pairing error, is.
2009
+ const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1981
2010
  return ERRORS.TypedError(
1982
- HardwareErrorCode.RuntimeError,
2011
+ isStaleV2Bond ? HardwareErrorCode.BleDeviceBondError : HardwareErrorCode.RuntimeError,
1983
2012
  `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
1984
2013
  );
1985
2014
  }
@@ -2035,13 +2064,14 @@ export default class ReactNativeBleTransport {
2035
2064
  });
2036
2065
  return 'V1';
2037
2066
  }
2038
- throw this.createProtocolMismatchError(expectedProtocol);
2067
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
2039
2068
  }
2040
2069
 
2041
2070
  if (expectedProtocol === 'V2') {
2042
2071
  if (await this.probeProtocolV2(uuid)) {
2043
2072
  this.deviceProtocol.set(uuid, 'V2');
2044
2073
  this.sessionProtocols.set(uuid, 'V2');
2074
+ this.confirmedProtocolV2.add(uuid);
2045
2075
  Log?.debug('[ReactNativeBleTransport] protocol detected', {
2046
2076
  deviceId: uuid,
2047
2077
  protocol: 'V2',
@@ -2049,7 +2079,7 @@ export default class ReactNativeBleTransport {
2049
2079
  });
2050
2080
  return 'V2';
2051
2081
  }
2052
- throw this.createProtocolMismatchError(expectedProtocol);
2082
+ throw this.createProtocolMismatchError(expectedProtocol, uuid);
2053
2083
  }
2054
2084
 
2055
2085
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -2084,6 +2114,9 @@ export default class ReactNativeBleTransport {
2084
2114
  if (detected) {
2085
2115
  this.deviceProtocol.set(uuid, protocol);
2086
2116
  this.sessionProtocols.set(uuid, protocol);
2117
+ if (protocol === 'V2') {
2118
+ this.confirmedProtocolV2.add(uuid);
2119
+ }
2087
2120
  this.protocolReprobeFailures.delete(uuid);
2088
2121
  Log?.debug('[ReactNativeBleTransport] protocol detected', {
2089
2122
  deviceId: uuid,