@onekeyfe/hd-transport-react-native 1.2.2-alpha.100 → 1.2.2-alpha.102

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.
@@ -0,0 +1,47 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import { isNativeBleStaleBondError, toBleStaleBondHardwareError } from '../bleStaleBond';
4
+
5
+ describe('native BLE stale bond mapping', () => {
6
+ test.each([
7
+ [
8
+ { attErrorCode: 15, reason: 'Encryption is insufficient' },
9
+ HardwareErrorCode.BleDeviceBondError,
10
+ ],
11
+ [
12
+ { attErrorCode: 5, reason: 'GATT_INSUF_AUTHENTICATION' },
13
+ HardwareErrorCode.BleDeviceBondError,
14
+ ],
15
+ [
16
+ { androidErrorCode: 5, reason: 'Connection state changed with status 5' },
17
+ HardwareErrorCode.BleDeviceBondError,
18
+ ],
19
+ [
20
+ { androidErrorCode: 15, reason: 'Connection state changed with status 15' },
21
+ HardwareErrorCode.BleDeviceBondError,
22
+ ],
23
+ [
24
+ { iosErrorCode: 14, reason: 'Peer removed pairing information' },
25
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
26
+ ],
27
+ [
28
+ { reason: 'peer removed pairing information' },
29
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
30
+ ],
31
+ [
32
+ { message: 'PEER REMOVED PAIRING INFORMATION' },
33
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
34
+ ],
35
+ ])('maps %j to %s', (nativeError, errorCode) => {
36
+ expect(isNativeBleStaleBondError(nativeError)).toBe(true);
37
+ expect(toBleStaleBondHardwareError(nativeError)).toMatchObject({ errorCode });
38
+ });
39
+
40
+ test('does not treat the generic ATT unlikely error as a stale bond', () => {
41
+ expect(isNativeBleStaleBondError({ attErrorCode: 14, reason: 'Unlikely error' })).toBe(false);
42
+ });
43
+
44
+ test('does not treat a generic disconnect as a stale bond', () => {
45
+ expect(isNativeBleStaleBondError({ reason: 'Device disconnected' })).toBe(false);
46
+ });
47
+ });
@@ -90,7 +90,7 @@ describe('React Native BLE strategy', () => {
90
90
  ).toBe(182);
91
91
  });
92
92
 
93
- test('uses withoutResponse for a high-volume write unless explicitly overridden', () => {
93
+ test('uses withoutResponse by default unless explicitly overridden', () => {
94
94
  const characteristic = {
95
95
  isWritableWithResponse: true,
96
96
  isWritableWithoutResponse: true,
@@ -99,8 +99,7 @@ describe('React Native BLE strategy', () => {
99
99
  expect(
100
100
  shouldWriteProtocolV2WithResponse({
101
101
  platform: 'ios',
102
- highThroughput: true,
103
- requestedWithResponse: false,
102
+ highThroughput: false,
104
103
  characteristic,
105
104
  })
106
105
  ).toBe(false);
@@ -109,9 +109,11 @@ const schemas = {
109
109
  const createHarness = ({
110
110
  deviceName = 'OneKey Pro 2',
111
111
  isWritableWithResponse = true,
112
+ monitorError,
112
113
  }: {
113
114
  deviceName?: string;
114
115
  isWritableWithResponse?: boolean;
116
+ monitorError?: (Error & { reason?: string; attErrorCode?: number; iosErrorCode?: number }) | null;
115
117
  } = {}) => {
116
118
  const uuid = 'rn-pro2-id';
117
119
  const sentSeqs: number[] = [];
@@ -130,6 +132,9 @@ const createHarness = ({
130
132
  isNotifiable: true,
131
133
  monitor: jest.fn(callback => {
132
134
  notifyCallback = callback;
135
+ if (monitorError) {
136
+ queueMicrotask(() => callback(monitorError, null));
137
+ }
133
138
  return { remove: jest.fn() };
134
139
  }),
135
140
  };
@@ -355,6 +360,134 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
355
360
  expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
356
361
  });
357
362
 
363
+ test('connects the Android GATT link before starting system bonding', async () => {
364
+ setPlatformOS('android');
365
+ const { transport, uuid, device } = createHarness();
366
+ const operationOrder: string[] = [];
367
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
368
+
369
+ device.isConnected.mockResolvedValueOnce(false);
370
+ device.connect = jest.fn(() => {
371
+ operationOrder.push('connect');
372
+ return Promise.resolve(device);
373
+ });
374
+ pairDeviceMock.mockImplementationOnce(() => {
375
+ operationOrder.push('bond');
376
+ return Promise.resolve({ bonded: true, bonding: false });
377
+ });
378
+
379
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).resolves.toEqual({
380
+ uuid,
381
+ protocolType: 'V2',
382
+ });
383
+
384
+ expect(operationOrder).toEqual(['connect', 'bond']);
385
+ await transport.release(uuid, true);
386
+ });
387
+
388
+ test('does not start Android bonding when both reconnect attempts fail', async () => {
389
+ setPlatformOS('android');
390
+ const { transport, uuid, device } = createHarness();
391
+ const BleErrorMock = jest.requireMock('react-native-ble-plx').BleError as new (
392
+ message: string
393
+ ) => Error;
394
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
395
+ const firstError = Object.assign(new BleErrorMock('MTU change failed'), {
396
+ errorCode: 206,
397
+ reason: 'MTU change failed',
398
+ });
399
+ const fallbackError = Object.assign(new BleErrorMock('GATT connect failed'), {
400
+ errorCode: 205,
401
+ reason: 'GATT connect failed',
402
+ });
403
+
404
+ device.isConnected.mockResolvedValueOnce(false);
405
+ device.connect = jest
406
+ .fn()
407
+ .mockRejectedValueOnce(firstError)
408
+ .mockRejectedValueOnce(fallbackError);
409
+ pairDeviceMock.mockClear();
410
+
411
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
412
+ errorCode: HardwareErrorCode.BleConnectedError,
413
+ });
414
+ expect(device.connect).toHaveBeenCalledTimes(2);
415
+ expect(pairDeviceMock).not.toHaveBeenCalled();
416
+ });
417
+
418
+ test('checks that the Android GATT link is connected before bonding', async () => {
419
+ setPlatformOS('android');
420
+ const { transport, uuid, device, bleManager } = createHarness();
421
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
422
+
423
+ device.isConnected.mockResolvedValueOnce(false).mockResolvedValueOnce(false);
424
+ device.connect = jest.fn().mockResolvedValue(device);
425
+ pairDeviceMock.mockClear();
426
+
427
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
428
+ errorCode: HardwareErrorCode.BleConnectedError,
429
+ });
430
+ expect(pairDeviceMock).not.toHaveBeenCalled();
431
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
432
+ expect(device.cancelConnection).toHaveBeenCalledTimes(1);
433
+ });
434
+
435
+ test.each([
436
+ [
437
+ 'ios',
438
+ 'OneKey Neo',
439
+ {
440
+ iosErrorCode: 14,
441
+ reason: 'Peer removed pairing information',
442
+ },
443
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
444
+ ],
445
+ [
446
+ 'android',
447
+ 'OneKey Pro 2',
448
+ {
449
+ androidErrorCode: 5,
450
+ reason: 'Connection state changed with status 5',
451
+ },
452
+ HardwareErrorCode.BleDeviceBondError,
453
+ ],
454
+ ] as const)(
455
+ 'maps a %s %s stale bond during connect before protocol detection',
456
+ async (platform, deviceName, nativeError, errorCode) => {
457
+ setPlatformOS(platform);
458
+ const { transport, uuid, device } = createHarness({ deviceName });
459
+ const BleErrorMock = jest.requireMock('react-native-ble-plx').BleError as new (
460
+ message: string
461
+ ) => Error;
462
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
463
+ pairDeviceMock.mockClear();
464
+
465
+ device.isConnected.mockResolvedValueOnce(false);
466
+ device.connect = jest
467
+ .fn()
468
+ .mockRejectedValue(Object.assign(new BleErrorMock(nativeError.reason), nativeError));
469
+
470
+ await expect(transport.acquire({ uuid })).rejects.toMatchObject({ errorCode });
471
+ expect(pairDeviceMock).not.toHaveBeenCalled();
472
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
473
+ }
474
+ );
475
+
476
+ test('closes the Android GATT link when system bonding fails', async () => {
477
+ setPlatformOS('android');
478
+ const { transport, uuid, device, bleManager } = createHarness();
479
+ const pairDeviceMock = jest.requireMock('../BleManager').pairDevice as jest.Mock;
480
+
481
+ pairDeviceMock.mockRejectedValueOnce(new Error('bonding canceled'));
482
+
483
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toThrow(
484
+ 'bonding canceled'
485
+ );
486
+
487
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
488
+ expect(device.cancelConnection).toHaveBeenCalledTimes(1);
489
+ });
490
+
358
491
  test('uses withResponse for consecutive iOS Protocol V1 control commands without releasing', async () => {
359
492
  const { transport, uuid, writeCharacteristic } = createV1Harness({
360
493
  respondOnWriteCount: [1, 2],
@@ -405,6 +538,22 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
405
538
  await transport.release(uuid, true);
406
539
  });
407
540
 
541
+ test('keeps native stale-bond write mapping out of Protocol V1 calls', async () => {
542
+ const { transport, uuid, writeCharacteristic } = createV1Harness();
543
+ const nativeError = Object.assign(new Error('Encryption is insufficient'), {
544
+ attErrorCode: 15,
545
+ reason: 'Encryption is insufficient',
546
+ });
547
+
548
+ await transport.acquire({ uuid, expectedProtocol: 'V1' });
549
+ writeCharacteristic.writeWithResponse.mockRejectedValueOnce(nativeError);
550
+
551
+ await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
552
+ errorCode: HardwareErrorCode.BleWriteCharacteristicError,
553
+ });
554
+ await transport.release(uuid, true);
555
+ });
556
+
408
557
  test('detects Protocol V2 on iOS without using the BLE name as a protocol hint', async () => {
409
558
  const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
410
559
  deviceName: 'Pro2 6E9E',
@@ -415,7 +564,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
415
564
  protocolType: 'V2',
416
565
  });
417
566
  expect(device.requestMTU).toHaveBeenCalledWith(247);
418
- expect(writeCharacteristic.writeWithResponse.mock.calls.length).toBeGreaterThan(1);
567
+ expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
568
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
419
569
 
420
570
  await expect(
421
571
  transport.call(uuid, 'Ping', { message: 'first-core-command' })
@@ -424,6 +574,93 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
424
574
  await transport.release(uuid, true);
425
575
  });
426
576
 
577
+ test('physically refreshes an uncached Protocol V2 firmware install link without Ping', async () => {
578
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
579
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
580
+ (transport as any).sessionProtocols.set(uuid, 'V2');
581
+ device.connect = jest.fn().mockResolvedValue(device);
582
+ device.isConnected.mockResolvedValueOnce(false);
583
+
584
+ await expect(
585
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
586
+ ).resolves.toEqual({
587
+ uuid,
588
+ protocolType: 'V2',
589
+ });
590
+
591
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
592
+ expect(device.cancelConnection).not.toHaveBeenCalled();
593
+ expect(device.connect).toHaveBeenCalled();
594
+ expect(probeProtocolV2).not.toHaveBeenCalled();
595
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
596
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
597
+ expect(transport.getProtocolType(uuid)).toBe('V2');
598
+ await transport.release(uuid, true);
599
+ });
600
+
601
+ test('refreshes a cached firmware install connection instead of trusting GATT state', async () => {
602
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
603
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
604
+
605
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
606
+ device.connect = jest.fn().mockResolvedValue(device);
607
+ device.isConnected.mockResolvedValueOnce(false);
608
+ writeCharacteristic.writeWithResponse.mockClear();
609
+ writeCharacteristic.writeWithoutResponse.mockClear();
610
+
611
+ await expect(
612
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
613
+ ).resolves.toEqual({
614
+ uuid,
615
+ protocolType: 'V2',
616
+ });
617
+
618
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
619
+ expect(device.cancelConnection).toHaveBeenCalled();
620
+ expect(device.connect).toHaveBeenCalled();
621
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
622
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
623
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
624
+ await transport.release(uuid, true);
625
+ });
626
+
627
+ test('rejects no-probe acquire before the BLE endpoint has confirmed a protocol', async () => {
628
+ const { transport, uuid } = createHarness();
629
+
630
+ await expect(
631
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
632
+ ).rejects.toThrow('previously confirmed protocol');
633
+
634
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
635
+ });
636
+
637
+ test('keeps confirmed V2 authorization across a BLE manager reset', async () => {
638
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
639
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
640
+
641
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
642
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
643
+
644
+ (transport as any).resetPlxManager();
645
+ transport.blePlxManager = bleManager as any;
646
+ device.connect = jest.fn().mockResolvedValue(device);
647
+ device.isConnected.mockResolvedValueOnce(false);
648
+ writeCharacteristic.writeWithResponse.mockClear();
649
+ writeCharacteristic.writeWithoutResponse.mockClear();
650
+
651
+ await expect(
652
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
653
+ ).resolves.toEqual({
654
+ uuid,
655
+ protocolType: 'V2',
656
+ });
657
+
658
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
659
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
660
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
661
+ await transport.release(uuid, true);
662
+ });
663
+
427
664
  test.each(['ios', 'android'] as const)(
428
665
  'keeps a first expected Protocol V2 probe miss retryable on %s',
429
666
  async platform => {
@@ -459,8 +696,37 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
459
696
  }
460
697
  );
461
698
 
699
+ test.each([
700
+ [
701
+ 'Encryption is insufficient',
702
+ { reason: 'Encryption is insufficient', attErrorCode: 15 },
703
+ HardwareErrorCode.BleDeviceBondError,
704
+ ],
705
+ [
706
+ 'Peer removed pairing information',
707
+ { reason: 'Peer removed pairing information', iosErrorCode: 14 },
708
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
709
+ ],
710
+ ] as const)(
711
+ 'fails Protocol V2 acquire immediately on %s instead of waiting for Ping',
712
+ async (_label, nativeError, errorCode) => {
713
+ const { transport, uuid, device } = createHarness({
714
+ monitorError: Object.assign(new Error(nativeError.reason), nativeError),
715
+ });
716
+ const probe = jest.spyOn(transport as any, 'probeProtocolV2');
717
+
718
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
719
+ errorCode,
720
+ });
721
+
722
+ expect(probe).not.toHaveBeenCalled();
723
+ expect(device.cancelConnection).toHaveBeenCalled();
724
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
725
+ }
726
+ );
727
+
462
728
  test.each(['ios', 'android'] as const)(
463
- 'reports a stale bond on %s when a previously confirmed Protocol V2 device stops responding',
729
+ 'keeps a confirmed Protocol V2 probe miss retryable on %s without native bond evidence',
464
730
  async platform => {
465
731
  setPlatformOS(platform);
466
732
  const { transport, uuid, device } = createHarness();
@@ -469,10 +735,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
469
735
  jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
470
736
 
471
737
  await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
472
- errorCode: HardwareErrorCode.BleDeviceBondError,
738
+ errorCode: HardwareErrorCode.RuntimeError,
473
739
  });
474
740
 
475
- expect(device.cancelConnection).toHaveBeenCalled();
741
+ expect(device.cancelConnection).not.toHaveBeenCalled();
476
742
  expect(transport.getProtocolType(uuid)).toBeUndefined();
477
743
  }
478
744
  );
@@ -658,7 +924,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
658
924
  });
659
925
  expect(device.requestMTU).toHaveBeenCalledTimes(3);
660
926
  expect((transport as any).getCachedTransport(uuid).mtuSize).toBeUndefined();
661
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalled();
927
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
662
928
  await transport.release(uuid, true);
663
929
  });
664
930
 
@@ -667,6 +933,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
667
933
  device.mtu = undefined;
668
934
 
669
935
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
936
+ const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
670
937
  device.requestMTU.mockImplementationOnce(() => {
671
938
  device.mtu = 247;
672
939
  return Promise.resolve(device);
@@ -674,7 +941,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
674
941
 
675
942
  await expect(transport.call(uuid, 'FileWrite', {})).resolves.toBeDefined();
676
943
  expect(device.requestMTU).toHaveBeenCalledTimes(4);
677
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
944
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
945
+ writesBeforeFileWrite + 1
946
+ );
678
947
  await transport.release(uuid, true);
679
948
  });
680
949
 
@@ -683,12 +952,13 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
683
952
  device.mtu = undefined;
684
953
 
685
954
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
955
+ const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
686
956
 
687
957
  await expect(transport.call(uuid, 'FileWrite', {})).rejects.toMatchObject({
688
958
  errorCode: HardwareErrorCode.BleConnectedError,
689
959
  });
690
960
  expect(device.requestMTU).toHaveBeenCalledTimes(4);
691
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
961
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(writesBeforeFileWrite);
692
962
  await transport.release(uuid, true);
693
963
  });
694
964
 
@@ -808,19 +1078,19 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
808
1078
  await transport.release(uuid, true);
809
1079
  });
810
1080
 
811
- test('uses withResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
1081
+ test('uses withoutResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
812
1082
  const { transport, uuid, writeCharacteristic } = createHarness();
813
1083
 
814
1084
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
815
1085
  const releaseNative = jest.spyOn(transport as any, 'releaseNative');
816
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
817
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
1086
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
1087
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
818
1088
 
819
1089
  await transport.call(uuid, 'DeviceInfoGet', {});
820
1090
  await transport.call(uuid, 'ProtocolInfoRequest', {});
821
1091
 
822
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(3);
823
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
1092
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
1093
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
824
1094
  expect(releaseNative).not.toHaveBeenCalled();
825
1095
 
826
1096
  await transport.release(uuid, true);
@@ -833,8 +1103,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
833
1103
 
834
1104
  await transport.call(uuid, 'FileWrite', {});
835
1105
  await transport.call(uuid, 'FileWrite', {});
836
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
837
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
1106
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
1107
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
838
1108
  expect(
839
1109
  logger.debug.mock.calls.filter(
840
1110
  ([message]) =>
@@ -867,8 +1137,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
867
1137
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
868
1138
 
869
1139
  await transport.call(uuid, 'FileWrite', {}, { writeWithResponse: true });
870
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
871
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
1140
+ expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
1141
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
872
1142
  await transport.release(uuid, true);
873
1143
  });
874
1144
 
@@ -0,0 +1,64 @@
1
+ import {
2
+ ERRORS,
3
+ HardwareErrorCode,
4
+ isBleStaleBondErrorText,
5
+ isBleStaleBondHardwareError,
6
+ } from '@onekeyfe/hd-shared';
7
+
8
+ export { isBleStaleBondHardwareError };
9
+
10
+ const ATT_INSUFFICIENT_AUTHENTICATION = 5;
11
+ const ATT_INSUFFICIENT_ENCRYPTION = 15;
12
+ const IOS_PEER_REMOVED_PAIRING_INFORMATION = 14;
13
+
14
+ type NativeBleErrorFields = {
15
+ attErrorCode?: unknown;
16
+ androidErrorCode?: unknown;
17
+ iosErrorCode?: unknown;
18
+ reason?: unknown;
19
+ message?: unknown;
20
+ };
21
+
22
+ const nativeErrorText = (error: NativeBleErrorFields) =>
23
+ [error.reason, error.message]
24
+ .filter((value): value is string => typeof value === 'string')
25
+ .join(' ');
26
+
27
+ export const isNativeBleStaleBondError = (error: unknown): boolean => {
28
+ if (!error || typeof error !== 'object') {
29
+ return typeof error === 'string' ? isBleStaleBondErrorText(error) : false;
30
+ }
31
+
32
+ const nativeError = error as NativeBleErrorFields;
33
+ if (
34
+ nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
35
+ nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
36
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
37
+ nativeError.androidErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
38
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION
39
+ ) {
40
+ return true;
41
+ }
42
+
43
+ return isBleStaleBondErrorText(nativeErrorText(nativeError));
44
+ };
45
+
46
+ export const toBleStaleBondHardwareError = (error: unknown) => {
47
+ if (isBleStaleBondHardwareError(error)) {
48
+ return error as Error;
49
+ }
50
+
51
+ const nativeError = (error ?? {}) as NativeBleErrorFields;
52
+ const text = nativeErrorText(nativeError);
53
+ const normalizedText = text.toLowerCase();
54
+ const peerRemoved =
55
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
56
+ normalizedText.includes('peer removed pairing information');
57
+
58
+ return ERRORS.TypedError(
59
+ peerRemoved
60
+ ? HardwareErrorCode.BlePeerRemovedPairingInformation
61
+ : HardwareErrorCode.BleDeviceBondError,
62
+ text || undefined
63
+ );
64
+ };
@@ -34,8 +34,6 @@ export function shouldRefreshNegotiatedMtu(mtu?: number | null) {
34
34
  }
35
35
 
36
36
  export function shouldWriteProtocolV2WithResponse({
37
- platform,
38
- highThroughput,
39
37
  requestedWithResponse,
40
38
  characteristic,
41
39
  }: {
@@ -46,5 +44,5 @@ export function shouldWriteProtocolV2WithResponse({
46
44
  }) {
47
45
  if (!characteristic.isWritableWithResponse) return false;
48
46
  if (!characteristic.isWritableWithoutResponse) return true;
49
- return requestedWithResponse === true || (platform === 'ios' && !highThroughput);
47
+ return requestedWithResponse === true;
50
48
  }