@onekeyfe/hd-transport-react-native 1.2.0-alpha.32 → 1.2.0-alpha.34
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/BleTransport.d.ts +1 -3
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +6 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -121
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleTransport.ts +2 -43
- package/src/__tests__/BleTransport.test.ts +41 -0
- package/src/__tests__/constants.test.ts +20 -0
- package/src/__tests__/protocolV2Link.test.ts +134 -2
- package/src/constants.ts +4 -19
- package/src/index.ts +71 -92
- package/src/types.ts +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BleErrorCode } from 'react-native-ble-plx';
|
|
2
|
+
|
|
3
|
+
import BleTransport from '../BleTransport';
|
|
4
|
+
|
|
5
|
+
jest.mock(
|
|
6
|
+
'react-native-ble-plx',
|
|
7
|
+
() => ({
|
|
8
|
+
BleErrorCode: {
|
|
9
|
+
DeviceDisconnected: 205,
|
|
10
|
+
CharacteristicNotFound: 404,
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
{ virtual: true }
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
jest.mock('@onekeyfe/hd-shared', () => ({
|
|
17
|
+
...jest.requireActual('@onekeyfe/hd-shared'),
|
|
18
|
+
wait: jest.fn(() => Promise.resolve()),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('BleTransport side-effecting writes', () => {
|
|
22
|
+
test('does not reconnect or replay after the device disconnects', async () => {
|
|
23
|
+
const error = Object.assign(new Error('device disconnected'), {
|
|
24
|
+
errorCode: BleErrorCode.DeviceDisconnected,
|
|
25
|
+
});
|
|
26
|
+
const writeCharacteristic = {
|
|
27
|
+
writeWithoutResponse: jest.fn(() => Promise.reject(error)),
|
|
28
|
+
};
|
|
29
|
+
const device = {
|
|
30
|
+
id: 'classic-id',
|
|
31
|
+
connect: jest.fn(() => Promise.resolve()),
|
|
32
|
+
discoverAllServicesAndCharacteristics: jest.fn(() => Promise.resolve()),
|
|
33
|
+
};
|
|
34
|
+
const transport = new BleTransport(device as any, writeCharacteristic as any, {} as any);
|
|
35
|
+
|
|
36
|
+
await expect(transport.writeWithRetry('payload')).rejects.toBe(error);
|
|
37
|
+
|
|
38
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(device.connect).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -5,10 +5,11 @@ import transportPackage, {
|
|
|
5
5
|
TRANSPORT_EVENT,
|
|
6
6
|
bytesToHex,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
8
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
9
9
|
|
|
10
10
|
import ReactNativeBleTransport, {
|
|
11
11
|
configureProtocolV2BleTuning,
|
|
12
|
+
getFirmwareUploadWriteRetryType,
|
|
12
13
|
resetProtocolV2BleTuning,
|
|
13
14
|
} from '../index';
|
|
14
15
|
|
|
@@ -50,6 +51,7 @@ const { parseConfigure } = transportPackage;
|
|
|
50
51
|
const protocolV1Schema = {
|
|
51
52
|
nested: {
|
|
52
53
|
Initialize: { fields: {} },
|
|
54
|
+
GetFeatures: { fields: {} },
|
|
53
55
|
Success: {
|
|
54
56
|
fields: {
|
|
55
57
|
message: { type: 'string', id: 1 },
|
|
@@ -59,6 +61,7 @@ const protocolV1Schema = {
|
|
|
59
61
|
values: {
|
|
60
62
|
MessageType_Initialize: 1,
|
|
61
63
|
MessageType_Success: 2,
|
|
64
|
+
MessageType_GetFeatures: 55,
|
|
62
65
|
},
|
|
63
66
|
},
|
|
64
67
|
},
|
|
@@ -140,7 +143,7 @@ const createHarness = () => {
|
|
|
140
143
|
id: uuid,
|
|
141
144
|
name: 'OneKey Pro 2',
|
|
142
145
|
localName: 'OneKey Pro 2',
|
|
143
|
-
serviceUUIDs: ['
|
|
146
|
+
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
144
147
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
145
148
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
146
149
|
onDisconnected: jest.fn(callback => {
|
|
@@ -181,11 +184,140 @@ const createHarness = () => {
|
|
|
181
184
|
};
|
|
182
185
|
};
|
|
183
186
|
|
|
187
|
+
const createV1Harness = () => {
|
|
188
|
+
const uuid = 'rn-classic-id';
|
|
189
|
+
let notifyCallback:
|
|
190
|
+
| ((error: Error | null, characteristic: { value: string } | null) => void)
|
|
191
|
+
| undefined;
|
|
192
|
+
const notifyCharacteristic = {
|
|
193
|
+
uuid: '0003',
|
|
194
|
+
deviceID: uuid,
|
|
195
|
+
isNotifiable: true,
|
|
196
|
+
monitor: jest.fn(callback => {
|
|
197
|
+
notifyCallback = callback;
|
|
198
|
+
return { remove: jest.fn() };
|
|
199
|
+
}),
|
|
200
|
+
};
|
|
201
|
+
let writeCount = 0;
|
|
202
|
+
const writeCharacteristic = {
|
|
203
|
+
uuid: '0002',
|
|
204
|
+
deviceID: uuid,
|
|
205
|
+
isWritableWithResponse: true,
|
|
206
|
+
isWritableWithoutResponse: true,
|
|
207
|
+
writeWithoutResponse: jest.fn(() => {
|
|
208
|
+
writeCount += 1;
|
|
209
|
+
if (writeCount === 1) {
|
|
210
|
+
notifyCallback?.(null, {
|
|
211
|
+
value: Buffer.from('3f23230002000000040a026f6b', 'hex').toString('base64'),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return Promise.resolve();
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
const device = {
|
|
218
|
+
id: uuid,
|
|
219
|
+
name: 'OneKey Classic',
|
|
220
|
+
localName: 'OneKey Classic',
|
|
221
|
+
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
222
|
+
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
223
|
+
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
224
|
+
onDisconnected: jest.fn(() => ({ remove: jest.fn() })),
|
|
225
|
+
};
|
|
226
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
227
|
+
transport.blePlxManager = {
|
|
228
|
+
devices: jest.fn(() => Promise.resolve([device])),
|
|
229
|
+
connectedDevices: jest.fn(() => Promise.resolve([])),
|
|
230
|
+
cancelTransaction: jest.fn(() => Promise.resolve()),
|
|
231
|
+
} as any;
|
|
232
|
+
transport.resolveCharacteristics = jest.fn(() =>
|
|
233
|
+
Promise.resolve({ writeCharacteristic, notifyCharacteristic })
|
|
234
|
+
);
|
|
235
|
+
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
236
|
+
transport.configure(protocolV1Schema);
|
|
237
|
+
transport.configureProtocolV2(protocolV2Schema);
|
|
238
|
+
return { transport, uuid, device };
|
|
239
|
+
};
|
|
240
|
+
|
|
184
241
|
describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
242
|
+
test('does not classify disconnects as retryable firmware writes', () => {
|
|
243
|
+
expect(
|
|
244
|
+
getFirmwareUploadWriteRetryType({
|
|
245
|
+
errorCode: 205,
|
|
246
|
+
message: 'Device disconnected after write',
|
|
247
|
+
})
|
|
248
|
+
).toBeNull();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('keeps another device reader when releasing a device with an active V1 call', async () => {
|
|
252
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
253
|
+
const activeV1Call = createDeferred<string>();
|
|
254
|
+
const otherDeviceReader = createDeferred<Uint8Array>();
|
|
255
|
+
activeV1Call.promise.catch(() => undefined);
|
|
256
|
+
otherDeviceReader.promise.catch(() => undefined);
|
|
257
|
+
transport.runPromise = activeV1Call;
|
|
258
|
+
transport.runPromiseDeviceId = 'device-a';
|
|
259
|
+
transport.protocolV2FramePromises.set('device-b', otherDeviceReader);
|
|
260
|
+
|
|
261
|
+
await transport.releaseNative('device-a', true);
|
|
262
|
+
|
|
263
|
+
expect(transport.protocolV2FramePromises.get('device-b')).toBe(otherDeviceReader);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('rejects a pending reader when its device frame state resets', async () => {
|
|
267
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
268
|
+
const reader = createDeferred<Uint8Array>();
|
|
269
|
+
transport.protocolV2FramePromises.set('device-a', reader);
|
|
270
|
+
const result = Promise.race([
|
|
271
|
+
reader.promise.then(
|
|
272
|
+
() => 'resolved',
|
|
273
|
+
() => 'rejected'
|
|
274
|
+
),
|
|
275
|
+
new Promise(resolve => {
|
|
276
|
+
setTimeout(() => resolve('pending'), 20);
|
|
277
|
+
}),
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
transport.resetProtocolV2Frames('device-a');
|
|
281
|
+
|
|
282
|
+
await expect(result).resolves.toBe('rejected');
|
|
283
|
+
});
|
|
284
|
+
|
|
185
285
|
test('keeps the legacy default BLE scan timeout', () => {
|
|
186
286
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
187
287
|
});
|
|
188
288
|
|
|
289
|
+
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
290
|
+
const { transport, uuid } = createV1Harness();
|
|
291
|
+
const probeProtocolV2 = jest
|
|
292
|
+
.spyOn(transport as any, 'probeProtocolV2')
|
|
293
|
+
.mockImplementationOnce(async () => {
|
|
294
|
+
await (transport as any).releaseNative(uuid, true);
|
|
295
|
+
return false;
|
|
296
|
+
});
|
|
297
|
+
const resolveCharacteristics = jest.spyOn(transport as any, 'resolveCharacteristics');
|
|
298
|
+
|
|
299
|
+
await expect(transport.acquire({ uuid, protocolHint: 'V2' })).resolves.toEqual({
|
|
300
|
+
uuid,
|
|
301
|
+
protocolType: 'V1',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
305
|
+
expect(resolveCharacteristics).toHaveBeenCalledTimes(2);
|
|
306
|
+
expect(transport.getProtocolType(uuid)).toBe('V1');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
|
|
310
|
+
const { transport, uuid, device } = createV1Harness();
|
|
311
|
+
|
|
312
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
313
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
|
|
314
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
318
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
189
321
|
afterEach(() => {
|
|
190
322
|
resetProtocolV2BleTuning();
|
|
191
323
|
});
|
package/src/constants.ts
CHANGED
|
@@ -12,7 +12,6 @@ type BluetoothServices = Record<
|
|
|
12
12
|
>;
|
|
13
13
|
|
|
14
14
|
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
15
|
-
const Pro2ServiceUUID = 'fffd';
|
|
16
15
|
|
|
17
16
|
const OneKeyServices: Record<string, BluetoothServices> = {
|
|
18
17
|
classic: {
|
|
@@ -21,11 +20,6 @@ const OneKeyServices: Record<string, BluetoothServices> = {
|
|
|
21
20
|
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
22
21
|
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
23
22
|
},
|
|
24
|
-
[Pro2ServiceUUID]: {
|
|
25
|
-
serviceUuid: Pro2ServiceUUID,
|
|
26
|
-
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
27
|
-
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
28
|
-
},
|
|
29
23
|
},
|
|
30
24
|
};
|
|
31
25
|
|
|
@@ -48,7 +42,7 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
|
|
|
48
42
|
Object.values(services).find(
|
|
49
43
|
item =>
|
|
50
44
|
normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid ||
|
|
51
|
-
|
|
45
|
+
matchesKnownBleUuid(serviceUuid, createKnownBleUuidAliases(item.serviceUuid))
|
|
52
46
|
);
|
|
53
47
|
if (!service) {
|
|
54
48
|
return null;
|
|
@@ -59,17 +53,8 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
|
|
|
59
53
|
export const normalizeBleUuid = (uuid?: string | null) =>
|
|
60
54
|
(uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
61
55
|
|
|
62
|
-
export const getBleUuidKey = (uuid?: string | null) => {
|
|
63
|
-
const normalized = normalizeBleUuid(uuid);
|
|
64
|
-
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
56
|
export const isSameBleUuid = (left?: string | null, right?: string | null) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
normalizedLeft === normalizedRight ||
|
|
73
|
-
(getBleUuidKey(left) !== '' && getBleUuidKey(left) === getBleUuidKey(right))
|
|
74
|
-
);
|
|
57
|
+
if (!left || !right) return false;
|
|
58
|
+
return matchesKnownBleUuid(left, createKnownBleUuidAliases(right));
|
|
75
59
|
};
|
|
60
|
+
import { createKnownBleUuidAliases, matchesKnownBleUuid } from '@onekeyfe/hd-shared';
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,12 @@ import transport, {
|
|
|
23
23
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
24
24
|
writeProtocolV2BleFrame,
|
|
25
25
|
} from '@onekeyfe/hd-transport';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ERRORS,
|
|
28
|
+
HardwareErrorCode,
|
|
29
|
+
createDeferred,
|
|
30
|
+
isOnekeyBluetoothDevice,
|
|
31
|
+
} from '@onekeyfe/hd-shared';
|
|
27
32
|
|
|
28
33
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
29
34
|
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from './bleStrategy';
|
|
@@ -31,7 +36,6 @@ import { subscribeBleOn } from './subscribeBleOn';
|
|
|
31
36
|
import {
|
|
32
37
|
ANDROID_PACKET_LENGTH,
|
|
33
38
|
IOS_PACKET_LENGTH,
|
|
34
|
-
getBleUuidKey,
|
|
35
39
|
getBluetoothServiceUuids,
|
|
36
40
|
getInfosForServiceUuid,
|
|
37
41
|
isSameBleUuid,
|
|
@@ -56,13 +60,12 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
56
60
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
57
61
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
58
62
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
59
|
-
const FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS = 2000;
|
|
60
63
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
61
64
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
62
65
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
63
66
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
64
67
|
|
|
65
|
-
type FirmwareUploadWriteRetryType = 'congested'
|
|
68
|
+
type FirmwareUploadWriteRetryType = 'congested';
|
|
66
69
|
type ResolvedBleCharacteristics = {
|
|
67
70
|
writeCharacteristic: Characteristic;
|
|
68
71
|
notifyCharacteristic: Characteristic;
|
|
@@ -73,7 +76,9 @@ const delay = (ms: number) =>
|
|
|
73
76
|
setTimeout(resolve, ms);
|
|
74
77
|
});
|
|
75
78
|
|
|
76
|
-
const getFirmwareUploadWriteRetryType = (
|
|
79
|
+
export const getFirmwareUploadWriteRetryType = (
|
|
80
|
+
error: unknown
|
|
81
|
+
): FirmwareUploadWriteRetryType | null => {
|
|
77
82
|
if (!error || typeof error !== 'object') return null;
|
|
78
83
|
const bleWriteError = error as {
|
|
79
84
|
androidErrorCode?: unknown;
|
|
@@ -84,13 +89,6 @@ const getFirmwareUploadWriteRetryType = (error: unknown): FirmwareUploadWriteRet
|
|
|
84
89
|
name?: unknown;
|
|
85
90
|
};
|
|
86
91
|
|
|
87
|
-
if (
|
|
88
|
-
bleWriteError.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
89
|
-
bleWriteError.errorCode === BleErrorCode.CharacteristicNotFound
|
|
90
|
-
) {
|
|
91
|
-
return 'reconnectable';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
92
|
if (
|
|
95
93
|
bleWriteError.androidErrorCode === ANDROID_GATT_CONGESTED_STATUS ||
|
|
96
94
|
bleWriteError.status === ANDROID_GATT_CONGESTED_STATUS
|
|
@@ -162,16 +160,6 @@ function getDeviceDisplayName(device?: Device | null) {
|
|
|
162
160
|
return device?.name || device?.localName || null;
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
function isGenericBleService(uuid?: string | null) {
|
|
166
|
-
return ['1800', '1801', '180a'].includes(getBleUuidKey(uuid));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function hasKnownOneKeyService(device?: Device | null) {
|
|
170
|
-
return (device?.serviceUUIDs ?? []).some(serviceUuid =>
|
|
171
|
-
getInfosForServiceUuid(serviceUuid, 'classic')
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
163
|
const ANDROID_REQUEST_MTU = 256;
|
|
176
164
|
|
|
177
165
|
const connectOptions: Record<string, unknown> = {
|
|
@@ -259,6 +247,8 @@ export default class ReactNativeBleTransport {
|
|
|
259
247
|
|
|
260
248
|
runPromise: Deferred<any> | null = null;
|
|
261
249
|
|
|
250
|
+
private runPromiseDeviceId: string | null = null;
|
|
251
|
+
|
|
262
252
|
emitter?: EventEmitter;
|
|
263
253
|
|
|
264
254
|
firmwareUploadWriteRecoveryIds = new Set<string>();
|
|
@@ -359,29 +349,15 @@ export default class ReactNativeBleTransport {
|
|
|
359
349
|
}
|
|
360
350
|
}
|
|
361
351
|
|
|
362
|
-
let fallbackServiceUuid: string | undefined;
|
|
363
|
-
|
|
364
352
|
if (!infos) {
|
|
365
353
|
const services = await device.services();
|
|
366
354
|
Log?.debug(
|
|
367
355
|
'[ReactNativeBleTransport] Known OneKey service UUID not found, discovered services:',
|
|
368
356
|
services?.map(service => service.uuid)
|
|
369
357
|
);
|
|
370
|
-
|
|
371
|
-
const knownService = services.find(service =>
|
|
372
|
-
getInfosForServiceUuid(service.uuid, 'classic')
|
|
373
|
-
);
|
|
374
|
-
const fallbackService =
|
|
375
|
-
knownService ?? services.find(service => !isGenericBleService(service.uuid)) ?? services[0];
|
|
376
|
-
|
|
377
|
-
if (fallbackService) {
|
|
378
|
-
fallbackServiceUuid = fallbackService.uuid;
|
|
379
|
-
characteristics = await device.characteristicsForService(fallbackService.uuid);
|
|
380
|
-
Log?.debug('[ReactNativeBleTransport] Using fallback BLE service:', fallbackService.uuid);
|
|
381
|
-
}
|
|
382
358
|
}
|
|
383
359
|
|
|
384
|
-
if (!infos
|
|
360
|
+
if (!infos) {
|
|
385
361
|
try {
|
|
386
362
|
Log?.debug('cancel connection when service not found');
|
|
387
363
|
await device.cancelConnection();
|
|
@@ -391,9 +367,7 @@ export default class ReactNativeBleTransport {
|
|
|
391
367
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
392
368
|
}
|
|
393
369
|
|
|
394
|
-
const serviceUuid = infos
|
|
395
|
-
const writeUuid = infos?.writeUuid ?? '00000002-0000-1000-8000-00805f9b34fb';
|
|
396
|
-
const notifyUuid = infos?.notifyUuid ?? '00000003-0000-1000-8000-00805f9b34fb';
|
|
370
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
397
371
|
|
|
398
372
|
if (!serviceUuid) {
|
|
399
373
|
throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
|
|
@@ -461,10 +435,9 @@ export default class ReactNativeBleTransport {
|
|
|
461
435
|
try {
|
|
462
436
|
Log?.debug('device disconnect: ', device?.id);
|
|
463
437
|
this.emitDeviceDisconnect(uuid, device?.name, monitorToken);
|
|
464
|
-
if (this.runPromise) {
|
|
438
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
465
439
|
const error = ERRORS.TypedError(HardwareErrorCode.BleConnectedError);
|
|
466
440
|
this.runPromise.reject(error);
|
|
467
|
-
this.rejectAllProtocolV2Frames(error);
|
|
468
441
|
}
|
|
469
442
|
} catch (e) {
|
|
470
443
|
Log?.debug('device disconnect error: ', e);
|
|
@@ -607,10 +580,12 @@ export default class ReactNativeBleTransport {
|
|
|
607
580
|
}
|
|
608
581
|
|
|
609
582
|
const displayName = getDeviceDisplayName(device);
|
|
610
|
-
const isOneKey =
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
583
|
+
const isOneKey = isOnekeyBluetoothDevice({
|
|
584
|
+
id: device?.id,
|
|
585
|
+
name: device?.name,
|
|
586
|
+
localName: device?.localName,
|
|
587
|
+
serviceUuids: device?.serviceUUIDs,
|
|
588
|
+
});
|
|
614
589
|
if (isOneKey) {
|
|
615
590
|
addDevice(device as unknown as Device);
|
|
616
591
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
@@ -627,10 +602,18 @@ export default class ReactNativeBleTransport {
|
|
|
627
602
|
getConnectedDeviceIds(Platform.OS === 'ios' ? getBluetoothServiceUuids() : []).then(
|
|
628
603
|
devices => {
|
|
629
604
|
for (const device of devices) {
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
605
|
+
const localName =
|
|
606
|
+
'localName' in device && typeof device.localName === 'string'
|
|
607
|
+
? device.localName
|
|
608
|
+
: null;
|
|
609
|
+
if (
|
|
610
|
+
isOnekeyBluetoothDevice({
|
|
611
|
+
id: device.id,
|
|
612
|
+
name: device.name,
|
|
613
|
+
localName,
|
|
614
|
+
serviceUuids: device.serviceUUIDs,
|
|
615
|
+
})
|
|
616
|
+
) {
|
|
634
617
|
Log?.debug('search connected peripheral: ', device.id);
|
|
635
618
|
addDevice(device as unknown as Device);
|
|
636
619
|
}
|
|
@@ -699,8 +682,8 @@ export default class ReactNativeBleTransport {
|
|
|
699
682
|
if (forceCleanRunPromise && this.runPromise) {
|
|
700
683
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
701
684
|
this.runPromise.reject(error);
|
|
702
|
-
this.rejectAllProtocolV2Frames(error);
|
|
703
685
|
this.runPromise = null;
|
|
686
|
+
this.runPromiseDeviceId = null;
|
|
704
687
|
Log?.debug('Force clean Bluetooth run promise, forceCleanRunPromise: ', forceCleanRunPromise);
|
|
705
688
|
}
|
|
706
689
|
|
|
@@ -793,7 +776,8 @@ export default class ReactNativeBleTransport {
|
|
|
793
776
|
|
|
794
777
|
const protocolHint = expectedProtocol
|
|
795
778
|
? undefined
|
|
796
|
-
:
|
|
779
|
+
: input.protocolHint ??
|
|
780
|
+
this.deviceProtocolHints.get(uuid) ??
|
|
797
781
|
inferProtocolHintFromDeviceName(getDeviceDisplayName(device));
|
|
798
782
|
|
|
799
783
|
// release transport before new transport instance
|
|
@@ -887,7 +871,7 @@ export default class ReactNativeBleTransport {
|
|
|
887
871
|
this.rejectProtocolV2Frames(uuid, ERRORS.TypedError(errorCode));
|
|
888
872
|
return;
|
|
889
873
|
}
|
|
890
|
-
if (this.runPromise) {
|
|
874
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
891
875
|
let ERROR:
|
|
892
876
|
| typeof HardwareErrorCode.BleDeviceBondError
|
|
893
877
|
| typeof HardwareErrorCode.BleCharacteristicNotifyError
|
|
@@ -910,7 +894,6 @@ export default class ReactNativeBleTransport {
|
|
|
910
894
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure
|
|
911
895
|
);
|
|
912
896
|
this.runPromise.reject(notifyError);
|
|
913
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
914
897
|
Log?.debug(
|
|
915
898
|
`${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
|
|
916
899
|
);
|
|
@@ -918,7 +901,6 @@ export default class ReactNativeBleTransport {
|
|
|
918
901
|
}
|
|
919
902
|
const notifyError = ERRORS.TypedError(ERROR);
|
|
920
903
|
this.runPromise.reject(notifyError);
|
|
921
|
-
this.rejectAllProtocolV2Frames(notifyError);
|
|
922
904
|
Log?.debug(': monitor notify error, and has unreleased Promise', Error);
|
|
923
905
|
}
|
|
924
906
|
|
|
@@ -963,14 +945,16 @@ export default class ReactNativeBleTransport {
|
|
|
963
945
|
// );
|
|
964
946
|
bufferLength = 0;
|
|
965
947
|
buffer = [];
|
|
966
|
-
this.
|
|
948
|
+
if (this.runPromiseDeviceId === uuid) {
|
|
949
|
+
this.runPromise?.resolve(value.toString('hex'));
|
|
950
|
+
}
|
|
967
951
|
}
|
|
968
952
|
} catch (error) {
|
|
969
953
|
Log?.debug('monitor data error: ', error);
|
|
970
954
|
const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
|
|
971
955
|
if (this.deviceProtocol.get(uuid) === 'V2') {
|
|
972
956
|
this.rejectProtocolV2Frames(uuid, notifyError);
|
|
973
|
-
} else {
|
|
957
|
+
} else if (this.runPromiseDeviceId === uuid) {
|
|
974
958
|
this.runPromise?.reject(notifyError);
|
|
975
959
|
}
|
|
976
960
|
}
|
|
@@ -986,11 +970,12 @@ export default class ReactNativeBleTransport {
|
|
|
986
970
|
|
|
987
971
|
private async releaseNative(uuid: string, onclose = false) {
|
|
988
972
|
const transport = transportCache[uuid];
|
|
989
|
-
if (this.runPromise) {
|
|
973
|
+
if (this.runPromise && this.runPromiseDeviceId === uuid) {
|
|
990
974
|
const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
|
|
991
975
|
this.runPromise.reject(error);
|
|
992
976
|
this.runPromise = null;
|
|
993
|
-
this.
|
|
977
|
+
this.runPromiseDeviceId = null;
|
|
978
|
+
this.rejectProtocolV2Frames(uuid, error);
|
|
994
979
|
} else {
|
|
995
980
|
this.resetProtocolV2Frames(uuid);
|
|
996
981
|
}
|
|
@@ -1098,6 +1083,7 @@ export default class ReactNativeBleTransport {
|
|
|
1098
1083
|
const runPromise = createDeferred<string>();
|
|
1099
1084
|
runPromise.promise.catch(() => undefined);
|
|
1100
1085
|
this.runPromise = runPromise;
|
|
1086
|
+
this.runPromiseDeviceId = uuid;
|
|
1101
1087
|
const messages = this._messages;
|
|
1102
1088
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1103
1089
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1197,31 +1183,14 @@ export default class ReactNativeBleTransport {
|
|
|
1197
1183
|
if (!retryType || attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1198
1184
|
throw error;
|
|
1199
1185
|
}
|
|
1200
|
-
const
|
|
1201
|
-
const delayMs = shouldReconnect
|
|
1202
|
-
? FIRMWARE_UPLOAD_RECONNECT_RETRY_DELAY_MS
|
|
1203
|
-
: resolveFirmwareUploadRetryDelay(attempt);
|
|
1186
|
+
const delayMs = resolveFirmwareUploadRetryDelay(attempt);
|
|
1204
1187
|
Log?.debug('[ReactNativeBleTransport] FirmwareUpload write retry:', {
|
|
1205
1188
|
attempt: attempt + 1,
|
|
1206
1189
|
delayMs,
|
|
1207
|
-
reconnect: shouldReconnect,
|
|
1208
1190
|
error,
|
|
1209
1191
|
});
|
|
1210
|
-
if (shouldReconnect) {
|
|
1211
|
-
this.firmwareUploadWriteRecoveryIds.add(uuid);
|
|
1212
|
-
}
|
|
1213
1192
|
await delay(delayMs);
|
|
1214
1193
|
attempt += 1;
|
|
1215
|
-
if (shouldReconnect) {
|
|
1216
|
-
try {
|
|
1217
|
-
await this.reconnectFirmwareUploadTransport(uuid, transport);
|
|
1218
|
-
} catch (e) {
|
|
1219
|
-
Log?.debug('[ReactNativeBleTransport] FirmwareUpload reconnect error:', e);
|
|
1220
|
-
if (attempt >= FIRMWARE_UPLOAD_WRITE_MAX_RETRIES) {
|
|
1221
|
-
throw e;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
1194
|
}
|
|
1226
1195
|
}
|
|
1227
1196
|
},
|
|
@@ -1274,16 +1243,25 @@ export default class ReactNativeBleTransport {
|
|
|
1274
1243
|
const jsonData = ProtocolV1.decodeMessage(messages, response);
|
|
1275
1244
|
return check.call(jsonData);
|
|
1276
1245
|
} catch (e) {
|
|
1277
|
-
if (name === '
|
|
1278
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1246
|
+
if (name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
|
|
1247
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe call failed:', e);
|
|
1279
1248
|
} else {
|
|
1280
1249
|
Log?.error('call error: ', e);
|
|
1281
1250
|
}
|
|
1251
|
+
const isProbeTimeout =
|
|
1252
|
+
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1253
|
+
if (
|
|
1254
|
+
!isProbeTimeout &&
|
|
1255
|
+
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1256
|
+
) {
|
|
1257
|
+
await this.disconnect(uuid);
|
|
1258
|
+
}
|
|
1282
1259
|
throw e;
|
|
1283
1260
|
} finally {
|
|
1284
1261
|
if (timeout) clearTimeout(timeout);
|
|
1285
1262
|
if (this.runPromise === runPromise) {
|
|
1286
1263
|
this.runPromise = null;
|
|
1264
|
+
this.runPromiseDeviceId = null;
|
|
1287
1265
|
}
|
|
1288
1266
|
}
|
|
1289
1267
|
}
|
|
@@ -1376,6 +1354,7 @@ export default class ReactNativeBleTransport {
|
|
|
1376
1354
|
// this.runPromise.reject(new Error('Transport_CallCanceled'));
|
|
1377
1355
|
}
|
|
1378
1356
|
this.runPromise = null;
|
|
1357
|
+
this.runPromiseDeviceId = null;
|
|
1379
1358
|
}
|
|
1380
1359
|
|
|
1381
1360
|
private getCachedTransport(uuid: string) {
|
|
@@ -1396,7 +1375,7 @@ export default class ReactNativeBleTransport {
|
|
|
1396
1375
|
private createProtocolDetectionError() {
|
|
1397
1376
|
return ERRORS.TypedError(
|
|
1398
1377
|
HardwareErrorCode.BleTimeoutError,
|
|
1399
|
-
'Unable to detect BLE protocol: device did not respond to Protocol V1
|
|
1378
|
+
'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
|
|
1400
1379
|
);
|
|
1401
1380
|
}
|
|
1402
1381
|
|
|
@@ -1447,6 +1426,13 @@ export default class ReactNativeBleTransport {
|
|
|
1447
1426
|
if (i > 0) {
|
|
1448
1427
|
// Reset subscriptions and buffers after a failed probe before trying another protocol.
|
|
1449
1428
|
await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
|
|
1429
|
+
if (!transportCache[uuid]) {
|
|
1430
|
+
const reacquired = await this.acquire({
|
|
1431
|
+
uuid,
|
|
1432
|
+
expectedProtocol: protocol,
|
|
1433
|
+
});
|
|
1434
|
+
return reacquired.protocolType;
|
|
1435
|
+
}
|
|
1450
1436
|
}
|
|
1451
1437
|
const detected =
|
|
1452
1438
|
protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
|
|
@@ -1524,11 +1510,13 @@ export default class ReactNativeBleTransport {
|
|
|
1524
1510
|
|
|
1525
1511
|
try {
|
|
1526
1512
|
this.deviceProtocol.set(uuid, 'V1');
|
|
1527
|
-
|
|
1513
|
+
// GetFeatures identifies Protocol V1 without resetting an existing wallet
|
|
1514
|
+
// session before Core has a chance to restore a hidden wallet.
|
|
1515
|
+
await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
|
|
1528
1516
|
return true;
|
|
1529
1517
|
} catch (error) {
|
|
1530
1518
|
this.clearProbeProtocol(uuid, 'V1');
|
|
1531
|
-
Log?.debug('[ReactNativeBleTransport] Protocol V1
|
|
1519
|
+
Log?.debug('[ReactNativeBleTransport] Protocol V1 GetFeatures probe failed:', error);
|
|
1532
1520
|
return false;
|
|
1533
1521
|
}
|
|
1534
1522
|
}
|
|
@@ -1603,17 +1591,8 @@ export default class ReactNativeBleTransport {
|
|
|
1603
1591
|
this.getProtocolV2FrameQueue(uuid).push(frame);
|
|
1604
1592
|
}
|
|
1605
1593
|
|
|
1606
|
-
private rejectAllProtocolV2Frames(error: Error) {
|
|
1607
|
-
this.protocolV2FrameQueues.clear();
|
|
1608
|
-
for (const framePromise of this.protocolV2FramePromises.values()) {
|
|
1609
|
-
framePromise.reject(error);
|
|
1610
|
-
}
|
|
1611
|
-
this.protocolV2FramePromises.clear();
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
1594
|
private resetProtocolV2Frames(uuid: string) {
|
|
1615
|
-
this.
|
|
1616
|
-
this.protocolV2FramePromises.delete(uuid);
|
|
1595
|
+
this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
|
|
1617
1596
|
}
|
|
1618
1597
|
|
|
1619
1598
|
private rejectProtocolV2Frames(uuid: string, error: Error) {
|