@onekeyfe/hd-transport-react-native 1.2.0-alpha.9 → 1.2.0-alpha.91

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,20 @@
1
+ import { getBluetoothServiceUuids, getInfosForServiceUuid } from '../constants';
2
+
3
+ describe('React Native BLE service filters', () => {
4
+ test('does not scan or configure the ignored FFFD service', () => {
5
+ expect(getBluetoothServiceUuids()).not.toContain('fffd');
6
+ expect(getInfosForServiceUuid('fffd', 'classic')).toBeNull();
7
+ expect(getInfosForServiceUuid('0000fffd-0000-1000-8000-00805f9b34fb', 'classic')).toBeNull();
8
+ });
9
+
10
+ test('keeps the OneKey communication service configured', () => {
11
+ expect(getBluetoothServiceUuids()).toContain('00000001-0000-1000-8000-00805f9b34fb');
12
+ expect(getInfosForServiceUuid('0001', 'classic')).toMatchObject({
13
+ serviceUuid: '00000001-0000-1000-8000-00805f9b34fb',
14
+ });
15
+ });
16
+
17
+ test('does not match a vendor-specific UUID containing the OneKey short key', () => {
18
+ expect(getInfosForServiceUuid('abcd0001-1234-5678-9012-abcdefabcdef', 'classic')).toBeNull();
19
+ });
20
+ });
@@ -0,0 +1,132 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ import { getConnectedDeviceIds } from '../BleManager';
4
+ import ReactNativeBleTransport from '../index';
5
+
6
+ jest.mock(
7
+ 'react-native',
8
+ () => ({
9
+ PermissionsAndroid: {},
10
+ Platform: { OS: 'ios' },
11
+ }),
12
+ { virtual: true }
13
+ );
14
+
15
+ jest.mock('react-native-ble-plx', () => ({
16
+ BleError: class BleError extends Error {},
17
+ BleErrorCode: {},
18
+ BleManager: jest.fn(),
19
+ ScanMode: { LowLatency: 2 },
20
+ }));
21
+
22
+ jest.mock('../BleManager', () => ({
23
+ getConnectedDeviceIds: jest.fn(),
24
+ onDeviceBondState: jest.fn(),
25
+ pairDevice: jest.fn(),
26
+ }));
27
+
28
+ jest.mock('../subscribeBleOn', () => ({
29
+ subscribeBleOn: jest.fn(() => Promise.resolve()),
30
+ }));
31
+
32
+ const ONEKEY_SERVICE_UUID = '00000001-0000-1000-8000-00805f9b34fb';
33
+
34
+ describe('ReactNativeBleTransport iOS discovery', () => {
35
+ test('filters a bonded Pro2 Find My peripheral while keeping the wallet peripheral', async () => {
36
+ jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([
37
+ {
38
+ id: 'find-my-peripheral',
39
+ name: 'Pro2 6E9E - Find My',
40
+ localName: null,
41
+ serviceUUIDs: [ONEKEY_SERVICE_UUID],
42
+ },
43
+ {
44
+ id: 'wallet-peripheral',
45
+ name: 'Pro2 6E9E',
46
+ localName: 'Pro2 6E9E',
47
+ serviceUUIDs: [ONEKEY_SERVICE_UUID],
48
+ },
49
+ ] as never);
50
+ const blePlxManager = {
51
+ startDeviceScan: jest.fn(),
52
+ stopDeviceScan: jest.fn(),
53
+ };
54
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
55
+ transport.blePlxManager = blePlxManager as never;
56
+ transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
57
+
58
+ const devices = await transport.enumerate();
59
+
60
+ expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
61
+ });
62
+
63
+ test('filters a scanned Pro2 Find My peripheral by name when localName is null', async () => {
64
+ jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([]);
65
+ const blePlxManager = {
66
+ startDeviceScan: jest.fn((_serviceUUIDs, _options, listener) => {
67
+ queueMicrotask(() => {
68
+ listener(null, {
69
+ id: 'find-my-peripheral',
70
+ name: 'Pro2 6E9E - Find My',
71
+ localName: null,
72
+ serviceUUIDs: [ONEKEY_SERVICE_UUID],
73
+ });
74
+ listener(null, {
75
+ id: 'wallet-peripheral',
76
+ name: 'Pro2 6E9E',
77
+ localName: 'Pro2 6E9E',
78
+ serviceUUIDs: [ONEKEY_SERVICE_UUID],
79
+ });
80
+ });
81
+ }),
82
+ stopDeviceScan: jest.fn(),
83
+ };
84
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
85
+ transport.blePlxManager = blePlxManager as never;
86
+ transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
87
+
88
+ const devices = await transport.enumerate();
89
+
90
+ expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
91
+ });
92
+
93
+ test('ignores an unnamed scanned advertisement while keeping the named wallet peripheral', async () => {
94
+ jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([]);
95
+ const blePlxManager = {
96
+ startDeviceScan: jest.fn((_serviceUUIDs, _options, listener) => {
97
+ queueMicrotask(() => {
98
+ listener(null, {
99
+ id: 'unnamed-peripheral',
100
+ name: null,
101
+ localName: null,
102
+ serviceUUIDs: [
103
+ '0000180a-0000-1000-8000-00805f9b34fb',
104
+ '0000180f-0000-1000-8000-00805f9b34fb',
105
+ '0000fffd-0000-1000-8000-00805f9b34fb',
106
+ ONEKEY_SERVICE_UUID,
107
+ ],
108
+ });
109
+ listener(null, {
110
+ id: 'wallet-peripheral',
111
+ name: 'Pro2 769D',
112
+ localName: 'Pro2 769D',
113
+ serviceUUIDs: [
114
+ '0000180a-0000-1000-8000-00805f9b34fb',
115
+ '0000180f-0000-1000-8000-00805f9b34fb',
116
+ '0000fffd-0000-1000-8000-00805f9b34fb',
117
+ ONEKEY_SERVICE_UUID,
118
+ ],
119
+ });
120
+ });
121
+ }),
122
+ stopDeviceScan: jest.fn(),
123
+ };
124
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
125
+ transport.blePlxManager = blePlxManager as never;
126
+ transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
127
+
128
+ const devices = await transport.enumerate();
129
+
130
+ expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
131
+ });
132
+ });
@@ -0,0 +1,132 @@
1
+ import ReactNativeBleTransport, { PROTOCOL_REPROBE_FALLBACK_ATTEMPTS } from '../index';
2
+ import protocolV1Schema from './protocolV1SchemaFixture';
3
+
4
+ jest.mock(
5
+ 'react-native',
6
+ () => ({
7
+ Platform: { OS: 'ios', select: (spec: Record<string, unknown>) => spec.ios },
8
+ PermissionsAndroid: {
9
+ PERMISSIONS: {},
10
+ RESULTS: {},
11
+ request: jest.fn(),
12
+ requestMultiple: jest.fn(),
13
+ },
14
+ }),
15
+ { virtual: true }
16
+ );
17
+
18
+ jest.mock('react-native-ble-plx', () => ({
19
+ BleATTErrorCode: { InvalidHandle: 1, UnlikelyError: 14 },
20
+ BleError: Error,
21
+ BleErrorCode: { DeviceDisconnected: 201, OperationStartFailed: 601 },
22
+ BleManager: jest.fn(),
23
+ ScanMode: { LowLatency: 2 },
24
+ }));
25
+
26
+ jest.mock('@onekeyfe/react-native-ble-utils', () => ({
27
+ __esModule: true,
28
+ default: {
29
+ getConnectedPeripherals: jest.fn(() => Promise.resolve([])),
30
+ getBondedPeripherals: jest.fn(() => Promise.resolve([])),
31
+ pairDevice: jest.fn(() => Promise.resolve()),
32
+ },
33
+ }));
34
+
35
+ const UUID = 'reprobe-device';
36
+
37
+ /** Drive detectProtocol with stubbed probes so we can observe which are attempted. */
38
+ function createHarness({ v1, v2 }: { v1: boolean; v2: boolean }) {
39
+ const transport = new ReactNativeBleTransport({});
40
+ transport.configure(protocolV1Schema);
41
+ const probeV1 = jest.fn(() => {
42
+ if (v1) {
43
+ (transport as any).deviceProtocol.set(UUID, 'V1');
44
+ }
45
+ return Promise.resolve(v1);
46
+ });
47
+ const probeV2 = jest.fn(() => {
48
+ if (v2) {
49
+ (transport as any).deviceProtocol.set(UUID, 'V2');
50
+ }
51
+ return Promise.resolve(v2);
52
+ });
53
+ (transport as any).probeProtocolV1 = probeV1;
54
+ (transport as any).probeProtocolV2 = probeV2;
55
+ (transport as any).resetProbeStateAfterProtocolProbe = jest.fn(() => Promise.resolve());
56
+ return { transport, probeV1, probeV2 };
57
+ }
58
+
59
+ const detect = (transport: ReactNativeBleTransport) =>
60
+ (transport as any).detectProtocol(UUID, undefined, undefined, () =>
61
+ Promise.resolve()
62
+ ) as Promise<string>;
63
+
64
+ describe('protocol re-probe after a known device goes away', () => {
65
+ test('an unknown device still probes both protocols', async () => {
66
+ const { transport, probeV1, probeV2 } = createHarness({ v1: false, v2: false });
67
+
68
+ await expect(detect(transport)).rejects.toBeDefined();
69
+
70
+ expect(probeV1).toHaveBeenCalledTimes(1);
71
+ expect(probeV2).toHaveBeenCalledTimes(1);
72
+ });
73
+
74
+ test('a device already known to speak V1 does not pay the V2 probe while it is away', async () => {
75
+ const known = createHarness({ v1: true, v2: false });
76
+ // First detection confirms V1 (this is the session that just talked to the device).
77
+ await expect(detect(known.transport)).resolves.toBe('V1');
78
+
79
+ // The device now reboots after a firmware install: V1 stops answering.
80
+ (known.transport as any).deviceProtocol.delete(UUID);
81
+ const probeV1 = jest.fn(() => Promise.resolve(false));
82
+ const probeV2 = jest.fn(() => Promise.resolve(false));
83
+ (known.transport as any).probeProtocolV1 = probeV1;
84
+ (known.transport as any).probeProtocolV2 = probeV2;
85
+
86
+ await expect(detect(known.transport)).rejects.toBeDefined();
87
+
88
+ expect(probeV1).toHaveBeenCalledTimes(1);
89
+ // The 10s Protocol V2 ping is pure waste for a device we just spoke V1 to.
90
+ expect(probeV2).not.toHaveBeenCalled();
91
+ });
92
+
93
+ test('after repeated failures it re-probes every protocol again', async () => {
94
+ const known = createHarness({ v1: true, v2: false });
95
+ await expect(detect(known.transport)).resolves.toBe('V1');
96
+
97
+ (known.transport as any).deviceProtocol.delete(UUID);
98
+ const probeV1 = jest.fn(() => Promise.resolve(false));
99
+ const probeV2 = jest.fn(() => Promise.resolve(false));
100
+ (known.transport as any).probeProtocolV1 = probeV1;
101
+ (known.transport as any).probeProtocolV2 = probeV2;
102
+
103
+ for (let attempt = 0; attempt < PROTOCOL_REPROBE_FALLBACK_ATTEMPTS; attempt += 1) {
104
+ // eslint-disable-next-line no-await-in-loop
105
+ await expect(detect(known.transport)).rejects.toBeDefined();
106
+ }
107
+ expect(probeV2).not.toHaveBeenCalled();
108
+
109
+ // The device may genuinely have changed protocol, so the shortcut must expire.
110
+ await expect(detect(known.transport)).rejects.toBeDefined();
111
+ expect(probeV2).toHaveBeenCalled();
112
+ });
113
+
114
+ test('a successful detection clears the failure streak', async () => {
115
+ const known = createHarness({ v1: true, v2: false });
116
+ await expect(detect(known.transport)).resolves.toBe('V1');
117
+ (known.transport as any).deviceProtocol.delete(UUID);
118
+
119
+ const failingV1 = jest.fn(() => Promise.resolve(false));
120
+ (known.transport as any).probeProtocolV1 = failingV1;
121
+ await expect(detect(known.transport)).rejects.toBeDefined();
122
+
123
+ // Device comes back.
124
+ (known.transport as any).probeProtocolV1 = jest.fn(() => {
125
+ (known.transport as any).deviceProtocol.set(UUID, 'V1');
126
+ return Promise.resolve(true);
127
+ });
128
+ await expect(detect(known.transport)).resolves.toBe('V1');
129
+
130
+ expect((known.transport as any).protocolReprobeFailures.get(UUID)).toBeUndefined();
131
+ });
132
+ });
@@ -0,0 +1,39 @@
1
+ const protocolV1Schema = {
2
+ nested: {
3
+ Initialize: {
4
+ fields: {
5
+ session_id: { type: 'bytes', id: 1 },
6
+ derive_cardano: { type: 'bool', id: 3 },
7
+ },
8
+ },
9
+ GetFeatures: { fields: {} },
10
+ Success: {
11
+ fields: {
12
+ message: { type: 'string', id: 1 },
13
+ },
14
+ },
15
+ Ping: {
16
+ fields: {
17
+ message: { type: 'string', id: 1 },
18
+ button_protection: { type: 'bool', id: 2 },
19
+ },
20
+ },
21
+ FirmwareUpload: {
22
+ fields: {
23
+ payload: { rule: 'required', type: 'bytes', id: 1 },
24
+ hash: { type: 'bytes', id: 2 },
25
+ },
26
+ },
27
+ MessageType: {
28
+ values: {
29
+ MessageType_Initialize: 0,
30
+ MessageType_Ping: 1,
31
+ MessageType_Success: 2,
32
+ MessageType_FirmwareUpload: 7,
33
+ MessageType_GetFeatures: 55,
34
+ },
35
+ },
36
+ },
37
+ };
38
+
39
+ export default protocolV1Schema;