@onekeyfe/hd-transport-react-native 1.2.0-alpha.3 → 1.2.0-alpha.31

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.
@@ -1 +1 @@
1
- {"version":3,"file":"subscribeBleOn.d.ts","sourceRoot":"","sources":["../src/subscribeBleOn.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,eAAO,MAAM,cAAc,eAAgB,aAAa,kBAAc,QAAQ,IAAI,CAqB9E,CAAC"}
1
+ {"version":3,"file":"subscribeBleOn.d.ts","sourceRoot":"","sources":["../src/subscribeBleOn.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,eAAO,MAAM,cAAc,eAAgB,aAAa,kBAAc,QAAQ,IAAI,CAmB9E,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ProtocolType } from '@onekeyfe/hd-transport';
2
+ export declare function createTransportCallLog(name: string, protocol: ProtocolType, data: Record<string, unknown>): {
3
+ name: string;
4
+ protocol: ProtocolType;
5
+ file_name: unknown;
6
+ hash: unknown;
7
+ } | {
8
+ name: string;
9
+ protocol: ProtocolType;
10
+ file_name?: undefined;
11
+ hash?: undefined;
12
+ };
13
+ //# sourceMappingURL=transportLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;EAY9B"}
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: '../../jest.config.js',
3
+ testEnvironment: 'node',
4
+ modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.0-alpha.3",
3
+ "version": "1.2.0-alpha.31",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -15,15 +15,16 @@
15
15
  "scripts": {
16
16
  "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
17
17
  "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
18
+ "test": "jest",
18
19
  "lint": "eslint .",
19
20
  "lint:fix": "eslint . --fix"
20
21
  },
21
22
  "dependencies": {
22
- "@onekeyfe/hd-core": "1.2.0-alpha.3",
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.3",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.3",
25
- "@onekeyfe/react-native-ble-utils": "^0.1.4",
23
+ "@onekeyfe/hd-core": "1.2.0-alpha.31",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.31",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.31",
26
+ "@onekeyfe/react-native-ble-utils": "^0.1.6",
26
27
  "react-native-ble-plx": "3.5.1"
27
28
  },
28
- "gitHead": "54a4a40baee7e376b9a810dade60007d2c9b00c9"
29
+ "gitHead": "01354bd66a7ef042a47f45c9ba80c49ba5b241de"
29
30
  }
@@ -1,8 +1,4 @@
1
- import {
2
- hasWritableCapability,
3
- resolveBleWriteMode,
4
- resolveProtocolV2PacketCapacity,
5
- } from '../bleStrategy';
1
+ import { hasWritableCapability, resolveProtocolV2PacketCapacity } from '../bleStrategy';
6
2
 
7
3
  describe('React Native BLE strategy', () => {
8
4
  test('accepts writeWithoutResponse-only characteristics', () => {
@@ -12,7 +8,6 @@ describe('React Native BLE strategy', () => {
12
8
  };
13
9
 
14
10
  expect(hasWritableCapability(characteristic)).toBe(true);
15
- expect(resolveBleWriteMode(characteristic)).toBe('withoutResponse');
16
11
  });
17
12
 
18
13
  test('falls back to Android default ATT payload when MTU is unavailable', () => {
@@ -0,0 +1,342 @@
1
+ import { EventEmitter } from 'events';
2
+ import transportPackage, {
3
+ PROTOCOL_V2_CHANNEL_BLE_UART,
4
+ ProtocolV2,
5
+ TRANSPORT_EVENT,
6
+ bytesToHex,
7
+ } from '@onekeyfe/hd-transport';
8
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
9
+
10
+ import ReactNativeBleTransport, {
11
+ configureProtocolV2BleTuning,
12
+ resetProtocolV2BleTuning,
13
+ } from '../index';
14
+
15
+ jest.mock(
16
+ 'react-native',
17
+ () => ({
18
+ PermissionsAndroid: {},
19
+ Platform: { OS: 'ios' },
20
+ }),
21
+ { virtual: true }
22
+ );
23
+
24
+ jest.mock('react-native-ble-plx', () => ({
25
+ BleATTErrorCode: { UnlikelyError: 14 },
26
+ BleError: class BleError extends Error {},
27
+ BleErrorCode: {
28
+ DeviceAlreadyConnected: 203,
29
+ DeviceDisconnected: 205,
30
+ DeviceMTUChangeFailed: 206,
31
+ OperationCancelled: 2,
32
+ CharacteristicNotFound: 404,
33
+ },
34
+ BleManager: jest.fn(),
35
+ ScanMode: { LowLatency: 2 },
36
+ }));
37
+
38
+ jest.mock('../BleManager', () => ({
39
+ getConnectedDeviceIds: jest.fn(() => Promise.resolve([])),
40
+ onDeviceBondState: jest.fn(() => Promise.resolve()),
41
+ pairDevice: jest.fn(() => Promise.resolve({ bonded: true, bonding: false })),
42
+ }));
43
+
44
+ jest.mock('../subscribeBleOn', () => ({
45
+ subscribeBleOn: jest.fn(() => Promise.resolve()),
46
+ }));
47
+
48
+ const { parseConfigure } = transportPackage;
49
+
50
+ const protocolV1Schema = {
51
+ nested: {
52
+ Initialize: { fields: {} },
53
+ Success: {
54
+ fields: {
55
+ message: { type: 'string', id: 1 },
56
+ },
57
+ },
58
+ MessageType: {
59
+ values: {
60
+ MessageType_Initialize: 1,
61
+ MessageType_Success: 2,
62
+ },
63
+ },
64
+ },
65
+ };
66
+
67
+ const protocolV2Schema = {
68
+ nested: {
69
+ Ping: {
70
+ fields: {
71
+ message: { type: 'string', id: 1 },
72
+ },
73
+ },
74
+ FileWrite: { fields: {} },
75
+ Success: {
76
+ fields: {
77
+ message: { type: 'string', id: 1 },
78
+ },
79
+ },
80
+ MessageType: {
81
+ values: {
82
+ MessageType_Ping: 60206,
83
+ MessageType_Success: 60207,
84
+ MessageType_FileWrite: 60805,
85
+ },
86
+ },
87
+ },
88
+ };
89
+
90
+ const schemas = {
91
+ protocolV1: parseConfigure(protocolV1Schema),
92
+ protocolV2: parseConfigure(protocolV2Schema),
93
+ };
94
+
95
+ const createHarness = () => {
96
+ const uuid = 'rn-pro2-id';
97
+ const sentSeqs: number[] = [];
98
+ let responseSeq = 0;
99
+ let shouldRespond = true;
100
+ let notifyCallback:
101
+ | ((
102
+ error: (Error & { reason?: string }) | null,
103
+ characteristic: { value: string } | null
104
+ ) => void)
105
+ | undefined;
106
+ let disconnectCallback: (() => void) | undefined;
107
+ const notifyCharacteristic = {
108
+ uuid: '0003',
109
+ deviceID: uuid,
110
+ isNotifiable: true,
111
+ monitor: jest.fn(callback => {
112
+ notifyCallback = callback;
113
+ return { remove: jest.fn() };
114
+ }),
115
+ };
116
+ const handleWrite = (base64: string) => {
117
+ const frame = Buffer.from(base64, 'base64');
118
+ sentSeqs.push(frame[6]);
119
+ if (shouldRespond) {
120
+ responseSeq += 1;
121
+ const response = ProtocolV2.encodeFrame(
122
+ schemas,
123
+ 'Success',
124
+ { message: 'ok' },
125
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
126
+ );
127
+ notifyCallback?.(null, { value: Buffer.from(response).toString('base64') });
128
+ }
129
+ return Promise.resolve();
130
+ };
131
+ const writeCharacteristic = {
132
+ uuid: '0002',
133
+ deviceID: uuid,
134
+ isWritableWithResponse: true,
135
+ isWritableWithoutResponse: true,
136
+ writeWithResponse: jest.fn(handleWrite),
137
+ writeWithoutResponse: jest.fn(handleWrite),
138
+ };
139
+ const device = {
140
+ id: uuid,
141
+ name: 'OneKey Pro 2',
142
+ localName: 'OneKey Pro 2',
143
+ serviceUUIDs: ['fffd'],
144
+ isConnected: jest.fn(() => Promise.resolve(true)),
145
+ cancelConnection: jest.fn(() => Promise.resolve()),
146
+ onDisconnected: jest.fn(callback => {
147
+ disconnectCallback = callback;
148
+ return { remove: jest.fn() };
149
+ }),
150
+ };
151
+ const bleManager = {
152
+ devices: jest.fn(() => Promise.resolve([device])),
153
+ connectedDevices: jest.fn(() => Promise.resolve([])),
154
+ cancelTransaction: jest.fn(() => Promise.resolve()),
155
+ };
156
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
157
+ const emitter = new EventEmitter();
158
+ transport.blePlxManager = bleManager;
159
+ transport.resolveCharacteristics = jest.fn(() =>
160
+ Promise.resolve({ writeCharacteristic, notifyCharacteristic })
161
+ );
162
+ transport.init({ debug: jest.fn(), error: jest.fn() }, emitter);
163
+ transport.configure(protocolV1Schema);
164
+ transport.configureProtocolV2(protocolV2Schema);
165
+
166
+ return {
167
+ transport,
168
+ emitter,
169
+ uuid,
170
+ sentSeqs,
171
+ writeCharacteristic,
172
+ setShouldRespond(value: boolean) {
173
+ shouldRespond = value;
174
+ },
175
+ emitMonitorError(error: Error & { reason?: string }) {
176
+ notifyCallback?.(error, null);
177
+ },
178
+ emitDisconnect() {
179
+ disconnectCallback?.();
180
+ },
181
+ };
182
+ };
183
+
184
+ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
185
+ test('keeps the legacy default BLE scan timeout', () => {
186
+ expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
187
+ });
188
+
189
+ afterEach(() => {
190
+ resetProtocolV2BleTuning();
191
+ });
192
+
193
+ test('keeps the Protocol V2 sequence across probe and the next call', async () => {
194
+ const { transport, uuid, sentSeqs } = createHarness();
195
+
196
+ await transport.acquire({ uuid });
197
+ await transport.call(uuid, 'Ping', { message: 'after-probe' });
198
+
199
+ expect(sentSeqs).toEqual([1, 2]);
200
+ expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
201
+ await transport.release(uuid, true);
202
+ });
203
+
204
+ test('rejects the active Protocol V2 reader when the current monitor errors', async () => {
205
+ const harness = createHarness();
206
+ const { transport, uuid, sentSeqs } = harness;
207
+ await transport.acquire({ uuid });
208
+ harness.setShouldRespond(false);
209
+
210
+ const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
211
+ while (sentSeqs.length < 2) {
212
+ await Promise.resolve();
213
+ }
214
+ await new Promise(resolve => {
215
+ setTimeout(resolve, 0);
216
+ });
217
+ harness.emitMonitorError(Object.assign(new Error('monitor failed'), { reason: 'link lost' }));
218
+
219
+ await expect(call).rejects.toMatchObject({
220
+ errorCode: HardwareErrorCode.BleCharacteristicNotifyError,
221
+ });
222
+ });
223
+
224
+ test('retains the sequence cursor when a new monitor generation is acquired', async () => {
225
+ const { transport, uuid, sentSeqs } = createHarness();
226
+
227
+ await transport.acquire({ uuid });
228
+ await transport.release(uuid, true);
229
+ await transport.acquire({ uuid });
230
+
231
+ expect(sentSeqs).toEqual([1, 2]);
232
+ await transport.release(uuid, true);
233
+ });
234
+
235
+ test('uses withoutResponse for normal and high-volume calls', async () => {
236
+ const { transport, uuid, writeCharacteristic } = createHarness();
237
+
238
+ await transport.acquire({ uuid });
239
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
240
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
241
+
242
+ await transport.call(uuid, 'Ping', { message: 'normal' });
243
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
244
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
245
+
246
+ await transport.call(uuid, 'FileWrite', {});
247
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
248
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
249
+ await transport.release(uuid, true);
250
+ });
251
+
252
+ test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
253
+ const harness = createHarness();
254
+ const { transport, uuid, sentSeqs } = harness;
255
+ await transport.acquire({ uuid });
256
+ harness.setShouldRespond(false);
257
+
258
+ const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
259
+ while (sentSeqs.length < 2) {
260
+ await Promise.resolve();
261
+ }
262
+
263
+ const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
264
+ await transport.disconnect(uuid);
265
+ await rejection;
266
+ });
267
+
268
+ test('emits one disconnect event when the physical callback races manual cleanup', async () => {
269
+ const harness = createHarness();
270
+ const disconnectListener = jest.fn();
271
+ harness.emitter.on(TRANSPORT_EVENT.DEVICE_DISCONNECT, disconnectListener);
272
+
273
+ await harness.transport.acquire({ uuid: harness.uuid });
274
+ harness.emitDisconnect();
275
+ await harness.transport.disconnect(harness.uuid);
276
+
277
+ expect(disconnectListener).toHaveBeenCalledTimes(1);
278
+ expect(disconnectListener).toHaveBeenCalledWith({
279
+ name: 'OneKey Pro 2',
280
+ id: harness.uuid,
281
+ connectId: harness.uuid,
282
+ });
283
+ });
284
+
285
+ test('chunks large frames and retries only transient GATT congestion', async () => {
286
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
287
+ const congested = { status: 143, message: 'GATT_CONGESTED' };
288
+ const writeWithoutResponse = jest
289
+ .fn()
290
+ .mockRejectedValueOnce(congested)
291
+ .mockResolvedValue(undefined);
292
+ const bleTransport = {
293
+ mtuSize: 23,
294
+ writeCharacteristic: { writeWithoutResponse },
295
+ };
296
+ const context = {
297
+ messageName: 'Ping',
298
+ timeoutMs: 1000,
299
+ highVolume: false,
300
+ generation: 1,
301
+ signal: new AbortController().signal,
302
+ };
303
+ const assertCurrentGeneration = jest.fn();
304
+ configureProtocolV2BleTuning({ iosPacketLength: 20 });
305
+
306
+ await transport.writeProtocolV2Frame(
307
+ bleTransport,
308
+ new Uint8Array(30),
309
+ context,
310
+ assertCurrentGeneration
311
+ );
312
+
313
+ expect(writeWithoutResponse).toHaveBeenCalledTimes(3);
314
+ expect(writeWithoutResponse.mock.calls[0][0]).toBe(writeWithoutResponse.mock.calls[1][0]);
315
+ expect(Buffer.from(writeWithoutResponse.mock.calls[2][0], 'base64')).toHaveLength(10);
316
+ expect(assertCurrentGeneration).toHaveBeenCalled();
317
+ });
318
+
319
+ test('does not retry a disconnected Protocol V2 write inside a partial frame', async () => {
320
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
321
+ const writeWithoutResponse = jest
322
+ .fn()
323
+ .mockRejectedValue({ errorCode: 205, message: 'Device disconnected' });
324
+ const bleTransport = {
325
+ mtuSize: 23,
326
+ writeCharacteristic: { writeWithoutResponse },
327
+ };
328
+ const context = {
329
+ messageName: 'FileWrite',
330
+ timeoutMs: 1000,
331
+ highVolume: true,
332
+ generation: 1,
333
+ signal: new AbortController().signal,
334
+ };
335
+ configureProtocolV2BleTuning({ iosPacketLength: 20 });
336
+
337
+ await expect(
338
+ transport.writeProtocolV2Frame(bleTransport, new Uint8Array(30), context, jest.fn())
339
+ ).rejects.toMatchObject({ errorCode: 205 });
340
+ expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
341
+ });
342
+ });
@@ -7,35 +7,10 @@ export type BleWriteCapability = {
7
7
  isWritableWithoutResponse?: boolean | null;
8
8
  };
9
9
 
10
- export type BleWriteMode = 'withResponse' | 'withoutResponse';
11
-
12
10
  export function hasWritableCapability(characteristic: BleWriteCapability) {
13
11
  return !!(characteristic.isWritableWithResponse || characteristic.isWritableWithoutResponse);
14
12
  }
15
13
 
16
- export function resolveBleWriteMode(
17
- characteristic: BleWriteCapability,
18
- preferredMode: BleWriteMode = 'withoutResponse'
19
- ): BleWriteMode {
20
- if (preferredMode === 'withoutResponse' && characteristic.isWritableWithoutResponse) {
21
- return 'withoutResponse';
22
- }
23
-
24
- if (preferredMode === 'withResponse' && characteristic.isWritableWithResponse) {
25
- return 'withResponse';
26
- }
27
-
28
- if (characteristic.isWritableWithoutResponse) {
29
- return 'withoutResponse';
30
- }
31
-
32
- if (characteristic.isWritableWithResponse) {
33
- return 'withResponse';
34
- }
35
-
36
- return preferredMode;
37
- }
38
-
39
14
  export function resolveProtocolV2PacketCapacity({
40
15
  platform,
41
16
  iosPacketLength = IOS_PACKET_LENGTH,
package/src/constants.ts CHANGED
@@ -12,6 +12,7 @@ type BluetoothServices = Record<
12
12
  >;
13
13
 
14
14
  const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
15
+ const Pro2ServiceUUID = 'fffd';
15
16
 
16
17
  const OneKeyServices: Record<string, BluetoothServices> = {
17
18
  classic: {
@@ -20,6 +21,11 @@ const OneKeyServices: Record<string, BluetoothServices> = {
20
21
  writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
21
22
  notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
22
23
  },
24
+ [Pro2ServiceUUID]: {
25
+ serviceUuid: Pro2ServiceUUID,
26
+ writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
27
+ notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
28
+ },
23
29
  },
24
30
  };
25
31
 
@@ -40,7 +46,9 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
40
46
  const service =
41
47
  services[serviceUuid] ??
42
48
  Object.values(services).find(
43
- item => normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid
49
+ item =>
50
+ normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid ||
51
+ getBleUuidKey(item.serviceUuid) === getBleUuidKey(serviceUuid)
44
52
  );
45
53
  if (!service) {
46
54
  return null;