@onekeyfe/hd-transport-react-native 1.2.0-alpha.6 → 1.2.0-alpha.63

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,2 @@
1
+ export { createTransportCallLog } from '@onekeyfe/hd-transport';
2
+ //# sourceMappingURL=transportLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -7,5 +7,6 @@ export type BleAcquireInput = {
7
7
  uuid: string;
8
8
  forceCleanRunPromise?: boolean;
9
9
  expectedProtocol?: ProtocolType;
10
+ protocolHint?: ProtocolType;
10
11
  };
11
12
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,YAAY,EAAE,UAAU,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,YAAY,EAAE,UAAU,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC"}
package/jest.config.js ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ preset: '../../jest.config.js',
3
+ testEnvironment: 'node',
4
+ modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
5
+ moduleNameMapper: {
6
+ // The workspace symlinks resolve to prebuilt dist outputs that can lag behind src.
7
+ '^@onekeyfe/hd-transport$': '<rootDir>/../hd-transport/src/index.ts',
8
+ '^@onekeyfe/hd-shared$': '<rootDir>/../shared/src/index.ts',
9
+ },
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.0-alpha.6",
3
+ "version": "1.2.0-alpha.63",
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.6",
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.6",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.6",
25
- "@onekeyfe/react-native-ble-utils": "^0.1.4",
23
+ "@onekeyfe/hd-core": "1.2.0-alpha.63",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.63",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.63",
26
+ "@onekeyfe/react-native-ble-utils": "^0.1.6",
26
27
  "react-native-ble-plx": "3.5.1"
27
28
  },
28
- "gitHead": "b0a8d032546250b02bc737d72ac23932bdc07a61"
29
+ "gitHead": "c5f9c9e47a1198a4ff1022ffafeeb6ca7af83fa0"
29
30
  }
@@ -1,12 +1,5 @@
1
- import { BleErrorCode } from 'react-native-ble-plx';
2
- import { wait } from '@onekeyfe/hd-shared';
3
-
4
- import { bleLogger } from './logger';
5
-
6
1
  import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
7
2
 
8
- const Log = bleLogger;
9
-
10
3
  export default class BleTransport {
11
4
  id: string;
12
5
 
@@ -28,10 +21,6 @@ export default class BleTransport {
28
21
 
29
22
  monitorToken?: number;
30
23
 
31
- static MAX_RETRIES = 5;
32
-
33
- static RETRY_DELAY = 2000;
34
-
35
24
  constructor(
36
25
  device: Device,
37
26
  writeCharacteristic: Characteristic,
@@ -43,37 +32,7 @@ export default class BleTransport {
43
32
  this.notifyCharacteristic = notifyCharacteristic;
44
33
  }
45
34
 
46
- /**
47
- * @description only for pro / touch , while upgrade firmware
48
- * @param data
49
- * @param retryCount
50
- * @returns
51
- */
52
- async writeWithRetry(data: string, retryCount = BleTransport.MAX_RETRIES): Promise<void> {
53
- try {
54
- await this.writeCharacteristic.writeWithoutResponse(data);
55
- } catch (error) {
56
- Log?.debug(
57
- `Write retry attempt ${BleTransport.MAX_RETRIES - retryCount + 1}, error: ${error}`
58
- );
59
- if (retryCount > 0) {
60
- await wait(BleTransport.RETRY_DELAY);
61
- if (
62
- error.errorCode === BleErrorCode.DeviceDisconnected ||
63
- error.errorCode === BleErrorCode.CharacteristicNotFound
64
- ) {
65
- try {
66
- await this.device.connect();
67
- await this.device.discoverAllServicesAndCharacteristics();
68
- } catch (e) {
69
- Log?.debug(`Connect or discoverAllServicesAndCharacteristics error: ${e}`);
70
- }
71
- } else {
72
- Log?.debug(`writeCharacteristic error: ${error}`);
73
- }
74
- return this.writeWithRetry(data, retryCount - 1);
75
- }
76
- throw error;
77
- }
35
+ async writeWithRetry(data: string): Promise<void> {
36
+ await this.writeCharacteristic.writeWithoutResponse(data);
78
37
  }
79
38
  }
@@ -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
+ });
@@ -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,192 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
4
+
5
+ import ReactNativeBleTransport, {
6
+ BLE_CONNECT_TIMEOUT_MS,
7
+ BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD,
8
+ } from '../index';
9
+
10
+ import messages from '@onekeyfe/hd-transport/messages.json';
11
+
12
+ jest.mock(
13
+ 'react-native',
14
+ () => ({
15
+ Platform: { OS: 'ios', select: (spec: Record<string, unknown>) => spec.ios },
16
+ PermissionsAndroid: {
17
+ PERMISSIONS: {},
18
+ RESULTS: {},
19
+ request: jest.fn(),
20
+ requestMultiple: jest.fn(),
21
+ },
22
+ }),
23
+ { virtual: true }
24
+ );
25
+
26
+ jest.mock('react-native-ble-plx', () => ({
27
+ BleATTErrorCode: { InvalidHandle: 1, UnlikelyError: 14 },
28
+ BleError: Error,
29
+ BleErrorCode: {
30
+ DeviceDisconnected: 201,
31
+ OperationStartFailed: 601,
32
+ DeviceMTUChangeFailed: 401,
33
+ OperationCancelled: 2,
34
+ DeviceAlreadyConnected: 203,
35
+ },
36
+ BleManager: jest.fn(),
37
+ ScanMode: { LowLatency: 2 },
38
+ }));
39
+
40
+ jest.mock('@onekeyfe/react-native-ble-utils', () => ({
41
+ __esModule: true,
42
+ default: {
43
+ getConnectedPeripherals: jest.fn(() => Promise.resolve([])),
44
+ getBondedPeripherals: jest.fn(() => Promise.resolve([])),
45
+ pairDevice: jest.fn(() => Promise.resolve()),
46
+ },
47
+ }));
48
+
49
+ const UUID = 'stalled-connect-device';
50
+
51
+ const flush = () =>
52
+ new Promise(resolve => {
53
+ setImmediate(resolve);
54
+ });
55
+
56
+ async function advanceUntil(settled: () => boolean, totalMs: number, stepMs = 250) {
57
+ for (let elapsed = 0; elapsed < totalMs; elapsed += stepMs) {
58
+ jest.advanceTimersByTime(stepMs);
59
+ // eslint-disable-next-line no-await-in-loop
60
+ await flush();
61
+ if (settled()) return;
62
+ }
63
+ throw new Error(`fake timers exhausted after ${totalMs}ms before the connect settled`);
64
+ }
65
+
66
+ /** A device whose native connect() never settles — the observed iOS failure mode. */
67
+ function createHarness(connectImpl: () => Promise<unknown>) {
68
+ const connect = jest.fn(connectImpl);
69
+ const device = {
70
+ id: UUID,
71
+ name: 'OneKey Classic',
72
+ localName: 'OneKey Classic',
73
+ serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
74
+ isConnected: jest.fn(() => Promise.resolve(false)),
75
+ cancelConnection: jest.fn(() => Promise.resolve()),
76
+ connect,
77
+ onDisconnected: jest.fn(() => ({ remove: jest.fn() })),
78
+ };
79
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
80
+ const bleManager = {
81
+ devices: jest.fn(() => Promise.resolve([device])),
82
+ connectedDevices: jest.fn(() => Promise.resolve([])),
83
+ connectToDevice: jest.fn(connectImpl),
84
+ cancelTransaction: jest.fn(() => Promise.resolve()),
85
+ cancelDeviceConnection: jest.fn(() => Promise.resolve()),
86
+ onStateChange: jest.fn((listener: (state: string) => void) => {
87
+ // Dispatch asynchronously: subscribeBleOn wires its own cleanup after
88
+ // registering, so a synchronous callback would run before it is ready.
89
+ setImmediate(() => listener('PoweredOn'));
90
+ return { remove: jest.fn() };
91
+ }),
92
+ state: jest.fn(() => Promise.resolve('PoweredOn')),
93
+ startDeviceScan: jest.fn(),
94
+ stopDeviceScan: jest.fn(),
95
+ };
96
+ (transport as any).blePlxManager = bleManager;
97
+ transport.init(
98
+ { debug: jest.fn(), error: jest.fn(), warn: jest.fn() } as any,
99
+ new EventEmitter()
100
+ );
101
+ transport.configure(messages);
102
+ return { transport, device, bleManager, connect };
103
+ }
104
+
105
+ describe('BLE connect timeout', () => {
106
+ beforeAll(() => {
107
+ jest.useFakeTimers({ doNotFake: ['setImmediate', 'performance'] });
108
+ });
109
+
110
+ afterAll(() => {
111
+ jest.useRealTimers();
112
+ });
113
+
114
+ afterEach(() => {
115
+ jest.clearAllTimers();
116
+ jest.restoreAllMocks();
117
+ });
118
+
119
+ test('a native connect that never settles is bounded instead of blocking forever', async () => {
120
+ // iOS applies its own connect timeout on a serial queue; when that queue is busy
121
+ // the timeout never fires and acquire() blocks until the app-level 60s timeout.
122
+ const { transport } = createHarness(
123
+ () =>
124
+ new Promise(() => {
125
+ // never settles
126
+ })
127
+ );
128
+
129
+ const errors: Array<{ errorCode?: unknown }> = [];
130
+ let settled = false;
131
+ transport.acquire({ uuid: UUID }).catch(e => {
132
+ errors.push(e);
133
+ settled = true;
134
+ });
135
+ await flush();
136
+
137
+ await advanceUntil(() => settled, BLE_CONNECT_TIMEOUT_MS + 5000);
138
+
139
+ expect(errors).toHaveLength(1);
140
+ expect(errors[0]?.errorCode).toBe(HardwareErrorCode.BleConnectedError);
141
+ });
142
+
143
+ test('the connect budget leaves generous headroom over a healthy connect', () => {
144
+ // Healthy connects finish in ~2-3s (the native budget is 3s); this backstop only
145
+ // fires when the native timeout itself fails to.
146
+ expect(BLE_CONNECT_TIMEOUT_MS).toBeGreaterThanOrEqual(6000);
147
+ expect(BLE_CONNECT_TIMEOUT_MS).toBeLessThanOrEqual(12000);
148
+ });
149
+
150
+ test('a stalled connect is abandoned natively so the next attempt is not cancelled by it', async () => {
151
+ const { transport, bleManager } = createHarness(
152
+ () =>
153
+ new Promise(() => {
154
+ // never settles
155
+ })
156
+ );
157
+
158
+ let settled = false;
159
+ transport.acquire({ uuid: UUID }).catch(() => {
160
+ settled = true;
161
+ });
162
+ await flush();
163
+ await advanceUntil(() => settled, BLE_CONNECT_TIMEOUT_MS + 5000);
164
+
165
+ expect(bleManager.cancelDeviceConnection).toHaveBeenCalledWith(UUID);
166
+ });
167
+
168
+ test('repeated stalled connects recreate the BLE manager', async () => {
169
+ const { transport, bleManager } = createHarness(
170
+ () =>
171
+ new Promise(() => {
172
+ // never settles
173
+ })
174
+ );
175
+ const destroy = jest.fn();
176
+ (bleManager as unknown as { destroy: jest.Mock }).destroy = destroy;
177
+
178
+ for (let attempt = 0; attempt < BLE_CONNECT_TIMEOUT_MANAGER_RESET_THRESHOLD; attempt += 1) {
179
+ let settled = false;
180
+ transport.acquire({ uuid: UUID }).catch(() => {
181
+ settled = true;
182
+ });
183
+ // eslint-disable-next-line no-await-in-loop
184
+ await flush();
185
+ // eslint-disable-next-line no-await-in-loop
186
+ await advanceUntil(() => settled, BLE_CONNECT_TIMEOUT_MS + 5000);
187
+ }
188
+
189
+ expect(destroy).toHaveBeenCalledTimes(1);
190
+ expect((transport as unknown as { blePlxManager?: unknown }).blePlxManager).toBeUndefined();
191
+ });
192
+ });
@@ -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
+ });