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

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.
@@ -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
  };
@@ -405,6 +410,22 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
405
410
  await transport.release(uuid, true);
406
411
  });
407
412
 
413
+ test('keeps native stale-bond write mapping out of Protocol V1 calls', async () => {
414
+ const { transport, uuid, writeCharacteristic } = createV1Harness();
415
+ const nativeError = Object.assign(new Error('Encryption is insufficient'), {
416
+ attErrorCode: 15,
417
+ reason: 'Encryption is insufficient',
418
+ });
419
+
420
+ await transport.acquire({ uuid, expectedProtocol: 'V1' });
421
+ writeCharacteristic.writeWithResponse.mockRejectedValueOnce(nativeError);
422
+
423
+ await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
424
+ errorCode: HardwareErrorCode.BleWriteCharacteristicError,
425
+ });
426
+ await transport.release(uuid, true);
427
+ });
428
+
408
429
  test('detects Protocol V2 on iOS without using the BLE name as a protocol hint', async () => {
409
430
  const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
410
431
  deviceName: 'Pro2 6E9E',
@@ -415,7 +436,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
415
436
  protocolType: 'V2',
416
437
  });
417
438
  expect(device.requestMTU).toHaveBeenCalledWith(247);
418
- expect(writeCharacteristic.writeWithResponse.mock.calls.length).toBeGreaterThan(1);
439
+ expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
440
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
419
441
 
420
442
  await expect(
421
443
  transport.call(uuid, 'Ping', { message: 'first-core-command' })
@@ -424,6 +446,93 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
424
446
  await transport.release(uuid, true);
425
447
  });
426
448
 
449
+ test('physically refreshes an uncached Protocol V2 firmware install link without Ping', async () => {
450
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
451
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
452
+ (transport as any).sessionProtocols.set(uuid, 'V2');
453
+ device.connect = jest.fn().mockResolvedValue(device);
454
+ device.isConnected.mockResolvedValueOnce(false);
455
+
456
+ await expect(
457
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
458
+ ).resolves.toEqual({
459
+ uuid,
460
+ protocolType: 'V2',
461
+ });
462
+
463
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
464
+ expect(device.cancelConnection).not.toHaveBeenCalled();
465
+ expect(device.connect).toHaveBeenCalled();
466
+ expect(probeProtocolV2).not.toHaveBeenCalled();
467
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
468
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
469
+ expect(transport.getProtocolType(uuid)).toBe('V2');
470
+ await transport.release(uuid, true);
471
+ });
472
+
473
+ test('refreshes a cached firmware install connection instead of trusting GATT state', async () => {
474
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
475
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
476
+
477
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
478
+ device.connect = jest.fn().mockResolvedValue(device);
479
+ device.isConnected.mockResolvedValueOnce(false);
480
+ writeCharacteristic.writeWithResponse.mockClear();
481
+ writeCharacteristic.writeWithoutResponse.mockClear();
482
+
483
+ await expect(
484
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
485
+ ).resolves.toEqual({
486
+ uuid,
487
+ protocolType: 'V2',
488
+ });
489
+
490
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(uuid);
491
+ expect(device.cancelConnection).toHaveBeenCalled();
492
+ expect(device.connect).toHaveBeenCalled();
493
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
494
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
495
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
496
+ await transport.release(uuid, true);
497
+ });
498
+
499
+ test('rejects no-probe acquire before the BLE endpoint has confirmed a protocol', async () => {
500
+ const { transport, uuid } = createHarness();
501
+
502
+ await expect(
503
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
504
+ ).rejects.toThrow('previously confirmed protocol');
505
+
506
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
507
+ });
508
+
509
+ test('keeps confirmed V2 authorization across a BLE manager reset', async () => {
510
+ const { transport, uuid, device, bleManager, writeCharacteristic } = createHarness();
511
+ const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2');
512
+
513
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
514
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
515
+
516
+ (transport as any).resetPlxManager();
517
+ transport.blePlxManager = bleManager as any;
518
+ device.connect = jest.fn().mockResolvedValue(device);
519
+ device.isConnected.mockResolvedValueOnce(false);
520
+ writeCharacteristic.writeWithResponse.mockClear();
521
+ writeCharacteristic.writeWithoutResponse.mockClear();
522
+
523
+ await expect(
524
+ transport.acquire({ uuid, expectedProtocol: 'V2', skipProtocolProbe: true })
525
+ ).resolves.toEqual({
526
+ uuid,
527
+ protocolType: 'V2',
528
+ });
529
+
530
+ expect(probeProtocolV2).toHaveBeenCalledTimes(1);
531
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
532
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
533
+ await transport.release(uuid, true);
534
+ });
535
+
427
536
  test.each(['ios', 'android'] as const)(
428
537
  'keeps a first expected Protocol V2 probe miss retryable on %s',
429
538
  async platform => {
@@ -459,8 +568,37 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
459
568
  }
460
569
  );
461
570
 
571
+ test.each([
572
+ [
573
+ 'Encryption is insufficient',
574
+ { reason: 'Encryption is insufficient', attErrorCode: 15 },
575
+ HardwareErrorCode.BleDeviceBondError,
576
+ ],
577
+ [
578
+ 'Peer removed pairing information',
579
+ { reason: 'Peer removed pairing information', iosErrorCode: 14 },
580
+ HardwareErrorCode.BlePeerRemovedPairingInformation,
581
+ ],
582
+ ] as const)(
583
+ 'fails Protocol V2 acquire immediately on %s instead of waiting for Ping',
584
+ async (_label, nativeError, errorCode) => {
585
+ const { transport, uuid, device } = createHarness({
586
+ monitorError: Object.assign(new Error(nativeError.reason), nativeError),
587
+ });
588
+ const probe = jest.spyOn(transport as any, 'probeProtocolV2');
589
+
590
+ await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
591
+ errorCode,
592
+ });
593
+
594
+ expect(probe).not.toHaveBeenCalled();
595
+ expect(device.cancelConnection).toHaveBeenCalled();
596
+ expect(transport.getProtocolType(uuid)).toBeUndefined();
597
+ }
598
+ );
599
+
462
600
  test.each(['ios', 'android'] as const)(
463
- 'reports a stale bond on %s when a previously confirmed Protocol V2 device stops responding',
601
+ 'keeps a confirmed Protocol V2 probe miss retryable on %s without native bond evidence',
464
602
  async platform => {
465
603
  setPlatformOS(platform);
466
604
  const { transport, uuid, device } = createHarness();
@@ -469,10 +607,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
469
607
  jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(false);
470
608
 
471
609
  await expect(transport.acquire({ uuid, expectedProtocol: 'V2' })).rejects.toMatchObject({
472
- errorCode: HardwareErrorCode.BleDeviceBondError,
610
+ errorCode: HardwareErrorCode.RuntimeError,
473
611
  });
474
612
 
475
- expect(device.cancelConnection).toHaveBeenCalled();
613
+ expect(device.cancelConnection).not.toHaveBeenCalled();
476
614
  expect(transport.getProtocolType(uuid)).toBeUndefined();
477
615
  }
478
616
  );
@@ -658,7 +796,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
658
796
  });
659
797
  expect(device.requestMTU).toHaveBeenCalledTimes(3);
660
798
  expect((transport as any).getCachedTransport(uuid).mtuSize).toBeUndefined();
661
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalled();
799
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalled();
662
800
  await transport.release(uuid, true);
663
801
  });
664
802
 
@@ -667,6 +805,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
667
805
  device.mtu = undefined;
668
806
 
669
807
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
808
+ const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
670
809
  device.requestMTU.mockImplementationOnce(() => {
671
810
  device.mtu = 247;
672
811
  return Promise.resolve(device);
@@ -674,7 +813,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
674
813
 
675
814
  await expect(transport.call(uuid, 'FileWrite', {})).resolves.toBeDefined();
676
815
  expect(device.requestMTU).toHaveBeenCalledTimes(4);
677
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
816
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
817
+ writesBeforeFileWrite + 1
818
+ );
678
819
  await transport.release(uuid, true);
679
820
  });
680
821
 
@@ -683,12 +824,13 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
683
824
  device.mtu = undefined;
684
825
 
685
826
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
827
+ const writesBeforeFileWrite = writeCharacteristic.writeWithoutResponse.mock.calls.length;
686
828
 
687
829
  await expect(transport.call(uuid, 'FileWrite', {})).rejects.toMatchObject({
688
830
  errorCode: HardwareErrorCode.BleConnectedError,
689
831
  });
690
832
  expect(device.requestMTU).toHaveBeenCalledTimes(4);
691
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
833
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(writesBeforeFileWrite);
692
834
  await transport.release(uuid, true);
693
835
  });
694
836
 
@@ -808,19 +950,19 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
808
950
  await transport.release(uuid, true);
809
951
  });
810
952
 
811
- test('uses withResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
953
+ test('uses withoutResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
812
954
  const { transport, uuid, writeCharacteristic } = createHarness();
813
955
 
814
956
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
815
957
  const releaseNative = jest.spyOn(transport as any, 'releaseNative');
816
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
817
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
958
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
959
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
818
960
 
819
961
  await transport.call(uuid, 'DeviceInfoGet', {});
820
962
  await transport.call(uuid, 'ProtocolInfoRequest', {});
821
963
 
822
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(3);
823
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
964
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
965
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
824
966
  expect(releaseNative).not.toHaveBeenCalled();
825
967
 
826
968
  await transport.release(uuid, true);
@@ -833,8 +975,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
833
975
 
834
976
  await transport.call(uuid, 'FileWrite', {});
835
977
  await transport.call(uuid, 'FileWrite', {});
836
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
837
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
978
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
979
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
838
980
  expect(
839
981
  logger.debug.mock.calls.filter(
840
982
  ([message]) =>
@@ -867,8 +1009,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
867
1009
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
868
1010
 
869
1011
  await transport.call(uuid, 'FileWrite', {}, { writeWithResponse: true });
870
- expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
871
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
1012
+ expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
1013
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
872
1014
  await transport.release(uuid, true);
873
1015
  });
874
1016
 
@@ -0,0 +1,61 @@
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
+ iosErrorCode?: unknown;
17
+ reason?: unknown;
18
+ message?: unknown;
19
+ };
20
+
21
+ const nativeErrorText = (error: NativeBleErrorFields) =>
22
+ [error.reason, error.message]
23
+ .filter((value): value is string => typeof value === 'string')
24
+ .join(' ');
25
+
26
+ export const isNativeBleStaleBondError = (error: unknown): boolean => {
27
+ if (!error || typeof error !== 'object') {
28
+ return typeof error === 'string' ? isBleStaleBondErrorText(error) : false;
29
+ }
30
+
31
+ const nativeError = error as NativeBleErrorFields;
32
+ if (
33
+ nativeError.attErrorCode === ATT_INSUFFICIENT_AUTHENTICATION ||
34
+ nativeError.attErrorCode === ATT_INSUFFICIENT_ENCRYPTION ||
35
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION
36
+ ) {
37
+ return true;
38
+ }
39
+
40
+ return isBleStaleBondErrorText(nativeErrorText(nativeError));
41
+ };
42
+
43
+ export const toBleStaleBondHardwareError = (error: unknown) => {
44
+ if (isBleStaleBondHardwareError(error)) {
45
+ return error as Error;
46
+ }
47
+
48
+ const nativeError = (error ?? {}) as NativeBleErrorFields;
49
+ const text = nativeErrorText(nativeError);
50
+ const normalizedText = text.toLowerCase();
51
+ const peerRemoved =
52
+ nativeError.iosErrorCode === IOS_PEER_REMOVED_PAIRING_INFORMATION ||
53
+ normalizedText.includes('peer removed pairing information');
54
+
55
+ return ERRORS.TypedError(
56
+ peerRemoved
57
+ ? HardwareErrorCode.BlePeerRemovedPairingInformation
58
+ : HardwareErrorCode.BleDeviceBondError,
59
+ text || undefined
60
+ );
61
+ };
@@ -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
  }