@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/BleManager.d.ts +5 -6
- package/dist/BleManager.d.ts.map +1 -1
- package/dist/index.d.ts +10 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +151 -396
- package/package.json +6 -6
- package/src/BleManager.ts +38 -78
- package/src/__tests__/enumerate.test.ts +0 -15
- package/src/__tests__/protocolReprobe.test.ts +93 -1
- package/src/__tests__/protocolV2Link.test.ts +0 -15
- package/src/index.ts +178 -162
- package/dist/bleEncryption.d.ts +0 -17
- package/dist/bleEncryption.d.ts.map +0 -1
- package/dist/bleKeyMissing.d.ts +0 -7
- package/dist/bleKeyMissing.d.ts.map +0 -1
- package/src/__tests__/androidEncryption.test.ts +0 -482
- package/src/__tests__/androidKeyMissing.test.ts +0 -307
- package/src/bleEncryption.ts +0 -267
- package/src/bleKeyMissing.ts +0 -106
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.2.3-alpha.
|
|
3
|
+
"version": "1.2.3-alpha.11",
|
|
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.3-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.3-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.3-alpha.
|
|
26
|
-
"@onekeyfe/react-native-ble-utils": "0.1.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.3-alpha.11",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.3-alpha.11",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.3-alpha.11",
|
|
26
|
+
"@onekeyfe/react-native-ble-utils": "0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "b4254228fd77d7263e059e5f2a2fe0e1f8ea8060"
|
|
30
30
|
}
|
package/src/BleManager.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import BleUtils from '@onekeyfe/react-native-ble-utils';
|
|
2
2
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
3
|
|
|
4
|
-
import { onBleKeyMissing, startBleKeyMissingTracking } from './bleKeyMissing';
|
|
5
4
|
import { bleLogger } from './logger';
|
|
6
5
|
|
|
7
6
|
import type { Peripheral } from '@onekeyfe/react-native-ble-utils';
|
|
@@ -33,71 +32,20 @@ export const getBondedDevices = () => BleUtils.getBondedPeripherals();
|
|
|
33
32
|
|
|
34
33
|
export const pairDevice = (macAddress: string) => BleUtils.pairDevice(macAddress);
|
|
35
34
|
|
|
36
|
-
/**
|
|
37
|
-
* Android replaces a bond as BONDING -> NONE, then NONE -> BONDING within milliseconds.
|
|
38
|
-
* The window covers slow broadcast delivery before the first half counts as a failure.
|
|
39
|
-
*/
|
|
40
|
-
export const SYSTEM_BONDING_RESTART_WINDOW_MS = 1500;
|
|
41
|
-
|
|
42
|
-
export type DeviceBondStateOptions = {
|
|
43
|
-
/**
|
|
44
|
-
* The system, not this transport, started the bonding in progress. Android 16+ re-pairs
|
|
45
|
-
* by itself after it detects a lost bond, and a successful re-pair replaces the old bond
|
|
46
|
-
* instead of going straight to BONDED.
|
|
47
|
-
*/
|
|
48
|
-
systemInitiated?: boolean;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const createBondFailureError = (bondState: Peripheral['bondState']) => {
|
|
52
|
-
const nativeReason =
|
|
53
|
-
'reason' in bondState && typeof bondState.reason === 'number' ? bondState.reason : undefined;
|
|
54
|
-
const reason =
|
|
55
|
-
nativeReason === undefined ? 'unknown' : bondFailureReasons[nativeReason] ?? 'unknown';
|
|
56
|
-
const params = {
|
|
57
|
-
phase: 'bond',
|
|
58
|
-
reason,
|
|
59
|
-
...(nativeReason === undefined ? {} : { nativeReason }),
|
|
60
|
-
};
|
|
61
|
-
if (reason === 'timeout') {
|
|
62
|
-
// Connection timeouts are retried by Core; pairing requires a new user attempt.
|
|
63
|
-
return ERRORS.TypedError(
|
|
64
|
-
HardwareErrorCode.BleDeviceNotBonded,
|
|
65
|
-
'Bluetooth pairing timed out',
|
|
66
|
-
params
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
if (reason === 'canceled') {
|
|
70
|
-
return ERRORS.TypedError(
|
|
71
|
-
HardwareErrorCode.BleDeviceBondedCanceled,
|
|
72
|
-
'Bluetooth pairing canceled',
|
|
73
|
-
params
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
return ERRORS.TypedError(
|
|
77
|
-
HardwareErrorCode.BleDeviceNotBonded,
|
|
78
|
-
'Bluetooth pairing failed',
|
|
79
|
-
params
|
|
80
|
-
);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
35
|
export const onDeviceBondState = (
|
|
84
36
|
bleMacAddress: string,
|
|
85
|
-
signal?: AbortSignal
|
|
86
|
-
options?: DeviceBondStateOptions
|
|
37
|
+
signal?: AbortSignal
|
|
87
38
|
): Promise<Peripheral | undefined> =>
|
|
88
39
|
new Promise((resolve, reject) => {
|
|
89
40
|
if (signal?.aborted) {
|
|
90
41
|
reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
91
42
|
return;
|
|
92
43
|
}
|
|
93
|
-
let pendingFailure: ReturnType<typeof setTimeout> | undefined;
|
|
94
44
|
const cleanup = () => {
|
|
95
45
|
if (timeout) {
|
|
96
46
|
clearTimeout(timeout);
|
|
97
47
|
}
|
|
98
|
-
if (pendingFailure) clearTimeout(pendingFailure);
|
|
99
48
|
if (cleanupListener) cleanupListener();
|
|
100
|
-
cleanupKeyMissing?.();
|
|
101
49
|
signal?.removeEventListener('abort', onAbort);
|
|
102
50
|
};
|
|
103
51
|
const onAbort = () => {
|
|
@@ -117,20 +65,6 @@ export const onDeviceBondState = (
|
|
|
117
65
|
);
|
|
118
66
|
}, 60 * 1000);
|
|
119
67
|
|
|
120
|
-
// A failed system re-pair restores the old bond, so it also ends in BONDED. Key
|
|
121
|
-
// missing is what tells it apart from a bond that now works.
|
|
122
|
-
const cleanupKeyMissing = startBleKeyMissingTracking()
|
|
123
|
-
? onBleKeyMissing(bleMacAddress, () => {
|
|
124
|
-
cleanup();
|
|
125
|
-
reject(
|
|
126
|
-
ERRORS.TypedError(HardwareErrorCode.BleBondInvalid, undefined, {
|
|
127
|
-
phase: 'bond',
|
|
128
|
-
reason: 'key_missing',
|
|
129
|
-
})
|
|
130
|
-
);
|
|
131
|
-
})
|
|
132
|
-
: undefined;
|
|
133
|
-
|
|
134
68
|
const cleanupListener = BleUtils.onDeviceBondState(peripheral => {
|
|
135
69
|
if (peripheral.id?.toLowerCase() !== bleMacAddress.toLowerCase()) {
|
|
136
70
|
return;
|
|
@@ -139,23 +73,49 @@ export const onDeviceBondState = (
|
|
|
139
73
|
|
|
140
74
|
const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
|
|
141
75
|
const hasFailed = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
|
|
142
|
-
const hasRestarted = bondState.preState === 'BOND_NONE' && bondState.state === 'BOND_BONDING';
|
|
143
76
|
Logger.debug('onDeviceBondState bondState:', bondState);
|
|
144
77
|
if (hasBonded) {
|
|
145
78
|
cleanup();
|
|
146
79
|
resolve(peripheral);
|
|
147
|
-
} else if (hasRestarted && pendingFailure) {
|
|
148
|
-
clearTimeout(pendingFailure);
|
|
149
|
-
pendingFailure = undefined;
|
|
150
|
-
} else if (hasFailed && options?.systemInitiated) {
|
|
151
|
-
if (pendingFailure) clearTimeout(pendingFailure);
|
|
152
|
-
pendingFailure = setTimeout(() => {
|
|
153
|
-
cleanup();
|
|
154
|
-
reject(createBondFailureError(bondState));
|
|
155
|
-
}, SYSTEM_BONDING_RESTART_WINDOW_MS);
|
|
156
80
|
} else if (hasFailed) {
|
|
157
81
|
cleanup();
|
|
158
|
-
|
|
82
|
+
const nativeReason =
|
|
83
|
+
'reason' in bondState && typeof bondState.reason === 'number'
|
|
84
|
+
? bondState.reason
|
|
85
|
+
: undefined;
|
|
86
|
+
const reason =
|
|
87
|
+
nativeReason === undefined ? 'unknown' : bondFailureReasons[nativeReason] ?? 'unknown';
|
|
88
|
+
const params = {
|
|
89
|
+
phase: 'bond',
|
|
90
|
+
reason,
|
|
91
|
+
...(nativeReason === undefined ? {} : { nativeReason }),
|
|
92
|
+
};
|
|
93
|
+
if (reason === 'timeout') {
|
|
94
|
+
// Connection timeouts are retried by Core; pairing requires a new user attempt.
|
|
95
|
+
reject(
|
|
96
|
+
ERRORS.TypedError(
|
|
97
|
+
HardwareErrorCode.BleDeviceNotBonded,
|
|
98
|
+
'Bluetooth pairing timed out',
|
|
99
|
+
params
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
} else if (reason === 'canceled') {
|
|
103
|
+
reject(
|
|
104
|
+
ERRORS.TypedError(
|
|
105
|
+
HardwareErrorCode.BleDeviceBondedCanceled,
|
|
106
|
+
'Bluetooth pairing canceled',
|
|
107
|
+
params
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
reject(
|
|
112
|
+
ERRORS.TypedError(
|
|
113
|
+
HardwareErrorCode.BleDeviceNotBonded,
|
|
114
|
+
'Bluetooth pairing failed',
|
|
115
|
+
params
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
159
119
|
}
|
|
160
120
|
});
|
|
161
121
|
});
|
|
@@ -25,21 +25,6 @@ jest.mock('../BleManager', () => ({
|
|
|
25
25
|
pairDevice: jest.fn(),
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
|
-
// The native facade is replaced wholesale above; key missing and link encryption are covered
|
|
29
|
-
// by their own suites.
|
|
30
|
-
jest.mock('../bleKeyMissing', () => ({
|
|
31
|
-
isBleKeyMissingSupported: jest.fn(() => false),
|
|
32
|
-
startBleKeyMissingTracking: jest.fn(() => false),
|
|
33
|
-
stopBleKeyMissingTracking: jest.fn(),
|
|
34
|
-
waitForBleKeyMissing: jest.fn(() => Promise.resolve(false)),
|
|
35
|
-
}));
|
|
36
|
-
jest.mock('../bleEncryption', () => ({
|
|
37
|
-
markBleLinkEncrypted: jest.fn(),
|
|
38
|
-
startBleEncryptionTracking: jest.fn(() => false),
|
|
39
|
-
stopBleEncryptionTracking: jest.fn(),
|
|
40
|
-
waitForAndroidLinkEncryption: jest.fn(() => Promise.resolve('unresolved')),
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
28
|
jest.mock('../subscribeBleOn', () => ({
|
|
44
29
|
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
45
30
|
}));
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
|
|
1
4
|
import ReactNativeBleTransport, { PROTOCOL_REPROBE_FALLBACK_ATTEMPTS } from '../index';
|
|
2
5
|
import protocolV1Schema from './protocolV1SchemaFixture';
|
|
3
6
|
|
|
@@ -89,7 +92,9 @@ describe('protocol re-probe after a known device goes away', () => {
|
|
|
89
92
|
test('an unknown device still probes both protocols', async () => {
|
|
90
93
|
const { transport, probeV1, probeV2 } = createHarness({ v1: false, v2: false });
|
|
91
94
|
|
|
92
|
-
await expect(detect(transport)).rejects.
|
|
95
|
+
await expect(detect(transport)).rejects.toMatchObject({
|
|
96
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
97
|
+
});
|
|
93
98
|
|
|
94
99
|
expect(probeV1).toHaveBeenCalledTimes(1);
|
|
95
100
|
expect(probeV2).toHaveBeenCalledTimes(1);
|
|
@@ -154,3 +159,90 @@ describe('protocol re-probe after a known device goes away', () => {
|
|
|
154
159
|
expect((known.transport as any).protocolReprobeFailures.get(UUID)).toBeUndefined();
|
|
155
160
|
});
|
|
156
161
|
});
|
|
162
|
+
|
|
163
|
+
describe('iOS reset-device stale bond confirmation', () => {
|
|
164
|
+
afterEach(() => {
|
|
165
|
+
Object.assign(Platform, { OS: 'ios' });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('promotes a probe disconnect followed by a silent reconnect to BleBondInvalid', async () => {
|
|
169
|
+
const transport = new ReactNativeBleTransport({});
|
|
170
|
+
transport.configure(protocolV1Schema);
|
|
171
|
+
(transport as any).callProtocolV1 = jest
|
|
172
|
+
.fn()
|
|
173
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded));
|
|
174
|
+
|
|
175
|
+
await expect(detect(transport)).rejects.toMatchObject({
|
|
176
|
+
errorCode: HardwareErrorCode.BleDeviceNotBonded,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
(transport as any).probeProtocolV1 = jest.fn(() => Promise.resolve(false));
|
|
180
|
+
(transport as any).probeProtocolV2 = jest.fn(() => Promise.resolve(false));
|
|
181
|
+
(transport as any).resetProbeStateAfterProtocolProbe = jest.fn(() => Promise.resolve());
|
|
182
|
+
|
|
183
|
+
await expect(detect(transport)).rejects.toMatchObject({
|
|
184
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('promotes a repeated iOS probe disconnect without waiting for probe timeouts', () => {
|
|
189
|
+
const { transport } = createHarness({ v1: false, v2: false });
|
|
190
|
+
const firstError = Object.assign(new Error('The specified device has disconnected from us'), {
|
|
191
|
+
errorCode: 201,
|
|
192
|
+
iosErrorCode: 7,
|
|
193
|
+
});
|
|
194
|
+
const secondError = ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
(transport as any).rememberIosProbeDisconnect(UUID, 'protocol-v1-get-features', firstError)
|
|
198
|
+
).toBeUndefined();
|
|
199
|
+
expect(
|
|
200
|
+
(transport as any).rememberIosProbeDisconnect(UUID, 'protocol-v2-ping', secondError)
|
|
201
|
+
).toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('clears the disconnect evidence after a valid protocol response', async () => {
|
|
205
|
+
const { transport } = createHarness({ v1: true, v2: false });
|
|
206
|
+
(transport as any).rememberIosProbeDisconnect(
|
|
207
|
+
UUID,
|
|
208
|
+
'protocol-v1-get-features',
|
|
209
|
+
ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
await expect(detect(transport)).resolves.toBe('V1');
|
|
213
|
+
expect((transport as any).iosProbeDisconnectEvidence.has(UUID)).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('preserves evidence for acquire retry cleanup and clears it on an explicit disconnect', async () => {
|
|
217
|
+
const { transport } = createHarness({ v1: false, v2: false });
|
|
218
|
+
(transport as any).rememberIosProbeDisconnect(
|
|
219
|
+
UUID,
|
|
220
|
+
'protocol-v1-get-features',
|
|
221
|
+
ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
await (transport as any).disconnectUnlocked(UUID, {
|
|
225
|
+
preserveIosProbeDisconnect: true,
|
|
226
|
+
});
|
|
227
|
+
expect((transport as any).iosProbeDisconnectEvidence.has(UUID)).toBe(true);
|
|
228
|
+
|
|
229
|
+
await (transport as any).disconnectUnlocked(UUID);
|
|
230
|
+
expect((transport as any).iosProbeDisconnectEvidence.has(UUID)).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('does not classify the same sequence as a stale bond on Android', async () => {
|
|
234
|
+
Object.assign(Platform, { OS: 'android' });
|
|
235
|
+
const { transport } = createHarness({ v1: false, v2: false });
|
|
236
|
+
|
|
237
|
+
expect(
|
|
238
|
+
(transport as any).rememberIosProbeDisconnect(
|
|
239
|
+
UUID,
|
|
240
|
+
'protocol-v1-get-features',
|
|
241
|
+
ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded)
|
|
242
|
+
)
|
|
243
|
+
).toBeUndefined();
|
|
244
|
+
await expect(detect(transport)).rejects.toMatchObject({
|
|
245
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -45,21 +45,6 @@ jest.mock('../BleManager', () => ({
|
|
|
45
45
|
pairDevice: jest.fn(() => Promise.resolve({ bonded: true, bonding: false })),
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
|
-
// The native facade is replaced wholesale above; key missing and link encryption are covered
|
|
49
|
-
// by their own suites.
|
|
50
|
-
jest.mock('../bleKeyMissing', () => ({
|
|
51
|
-
isBleKeyMissingSupported: jest.fn(() => false),
|
|
52
|
-
startBleKeyMissingTracking: jest.fn(() => false),
|
|
53
|
-
stopBleKeyMissingTracking: jest.fn(),
|
|
54
|
-
waitForBleKeyMissing: jest.fn(() => Promise.resolve(false)),
|
|
55
|
-
}));
|
|
56
|
-
jest.mock('../bleEncryption', () => ({
|
|
57
|
-
markBleLinkEncrypted: jest.fn(),
|
|
58
|
-
startBleEncryptionTracking: jest.fn(() => false),
|
|
59
|
-
stopBleEncryptionTracking: jest.fn(),
|
|
60
|
-
waitForAndroidLinkEncryption: jest.fn(() => Promise.resolve('unresolved')),
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
48
|
jest.mock('../subscribeBleOn', () => ({
|
|
64
49
|
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
65
50
|
}));
|