@onekeyfe/hd-transport-react-native 1.1.34-alpha.2 → 1.1.34-alpha.3
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.map +1 -1
- package/dist/BleTransport.d.ts +2 -0
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/bleStrategy.d.ts +13 -0
- package/dist/bleStrategy.d.ts.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +62 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +799 -150
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/subscribeBleOn.d.ts.map +1 -1
- package/dist/transportLog.d.ts +13 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleManager.ts +11 -14
- package/src/BleTransport.ts +9 -5
- package/src/__tests__/bleStrategy.test.ts +42 -0
- package/src/__tests__/constants.test.ts +18 -0
- package/src/__tests__/protocolV2Link.test.ts +243 -0
- package/src/bleStrategy.ts +35 -0
- package/src/constants.ts +33 -1
- package/src/index.ts +836 -91
- package/src/logger.ts +19 -0
- package/src/subscribeBleOn.ts +0 -2
- package/src/transportLog.ts +18 -0
- package/src/types.ts +3 -0
- package/src/utils/validateNotify.ts +4 -4
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type LogMethod = (...args: unknown[]) => void;
|
|
2
|
+
type TransportLogger = {
|
|
3
|
+
debug?: LogMethod;
|
|
4
|
+
error?: LogMethod;
|
|
5
|
+
warn?: LogMethod;
|
|
6
|
+
};
|
|
7
|
+
export declare const setBleLogger: (logger?: TransportLogger) => void;
|
|
8
|
+
export declare const bleLogger: {
|
|
9
|
+
debug: (...args: unknown[]) => void | undefined;
|
|
10
|
+
error: (...args: unknown[]) => void | undefined;
|
|
11
|
+
warn: (...args: unknown[]) => void | undefined;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAE9C,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAIF,eAAO,MAAM,YAAY,YAAa,eAAe,SAEpD,CAAC;AAEF,eAAO,MAAM,SAAS;qBACH,OAAO,EAAE;qBACT,OAAO,EAAE;oBACV,OAAO,EAAE;CAC1B,CAAC"}
|
|
@@ -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,
|
|
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/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProtocolType } from '@onekeyfe/hd-transport';
|
|
1
2
|
export type { BleManager as BlePlxManager } from 'react-native-ble-plx';
|
|
2
3
|
export type TransportOptions = {
|
|
3
4
|
scanTimeout?: number;
|
|
@@ -5,5 +6,6 @@ export type TransportOptions = {
|
|
|
5
6
|
export type BleAcquireInput = {
|
|
6
7
|
uuid: string;
|
|
7
8
|
forceCleanRunPromise?: boolean;
|
|
9
|
+
expectedProtocol?: ProtocolType;
|
|
8
10
|
};
|
|
9
11
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,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;
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
-
"version": "1.1.34-alpha.
|
|
3
|
+
"version": "1.1.34-alpha.3",
|
|
4
4
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"lint:fix": "eslint . --fix"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@onekeyfe/hd-core": "1.1.34-alpha.
|
|
23
|
-
"@onekeyfe/hd-shared": "1.1.34-alpha.
|
|
24
|
-
"@onekeyfe/hd-transport": "1.1.34-alpha.
|
|
22
|
+
"@onekeyfe/hd-core": "1.1.34-alpha.3",
|
|
23
|
+
"@onekeyfe/hd-shared": "1.1.34-alpha.3",
|
|
24
|
+
"@onekeyfe/hd-transport": "1.1.34-alpha.3",
|
|
25
25
|
"@onekeyfe/react-native-ble-utils": "^0.1.6",
|
|
26
26
|
"react-native-ble-plx": "3.5.1"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "3688bc8495eeb2ffd5616270d13fa29e8967623a"
|
|
29
29
|
}
|
package/src/BleManager.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import BleUtils from '@onekeyfe/react-native-ble-utils';
|
|
2
2
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { bleLogger } from './logger';
|
|
4
5
|
|
|
5
6
|
import type { Peripheral } from '@onekeyfe/react-native-ble-utils';
|
|
6
7
|
|
|
7
|
-
const Logger =
|
|
8
|
+
const Logger = bleLogger;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* get the device basic info of connected devices
|
|
@@ -20,36 +21,32 @@ export const pairDevice = (macAddress: string) => BleUtils.pairDevice(macAddress
|
|
|
20
21
|
|
|
21
22
|
export const onDeviceBondState = (bleMacAddress: string): Promise<Peripheral | undefined> =>
|
|
22
23
|
new Promise((resolve, reject) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const cleanup = (cleanupListener: (() => void) | undefined) => {
|
|
24
|
+
const cleanup = () => {
|
|
26
25
|
if (timeout) {
|
|
27
26
|
clearTimeout(timeout);
|
|
28
27
|
}
|
|
29
28
|
if (cleanupListener) cleanupListener();
|
|
30
29
|
};
|
|
31
30
|
|
|
31
|
+
const timeout = setTimeout(() => {
|
|
32
|
+
cleanup();
|
|
33
|
+
reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
|
|
34
|
+
}, 60 * 1000);
|
|
35
|
+
|
|
32
36
|
const cleanupListener = BleUtils.onDeviceBondState(peripheral => {
|
|
33
37
|
if (peripheral.id?.toLowerCase() !== bleMacAddress.toLowerCase()) {
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
40
|
const { bondState } = peripheral;
|
|
37
41
|
|
|
38
|
-
if (bondState.preState === 'BOND_NONE' && bondState.state === 'BOND_BONDING') {
|
|
39
|
-
timeout = setTimeout(() => {
|
|
40
|
-
cleanup(cleanupListener);
|
|
41
|
-
reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded, 'device is not bonded'));
|
|
42
|
-
}, 60 * 1000);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
42
|
const hasBonded = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_BONDED';
|
|
46
43
|
const hasCanceled = bondState.preState === 'BOND_BONDING' && bondState.state === 'BOND_NONE';
|
|
47
44
|
Logger.debug('onDeviceBondState bondState:', bondState);
|
|
48
45
|
if (hasBonded) {
|
|
49
|
-
cleanup(
|
|
46
|
+
cleanup();
|
|
50
47
|
resolve(peripheral);
|
|
51
48
|
} else if (hasCanceled) {
|
|
52
|
-
cleanup(
|
|
49
|
+
cleanup();
|
|
53
50
|
reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled, 'bonding canceled'));
|
|
54
51
|
}
|
|
55
52
|
});
|
package/src/BleTransport.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BleErrorCode } from 'react-native-ble-plx';
|
|
2
|
-
import {
|
|
2
|
+
import { wait } from '@onekeyfe/hd-shared';
|
|
3
|
+
|
|
4
|
+
import { bleLogger } from './logger';
|
|
3
5
|
|
|
4
6
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
5
|
-
// import { wait } from '@onekeyfe/hd-core/src/utils';
|
|
6
7
|
|
|
7
|
-
const Log =
|
|
8
|
+
const Log = bleLogger;
|
|
8
9
|
|
|
9
10
|
export default class BleTransport {
|
|
10
11
|
id: string;
|
|
@@ -13,7 +14,7 @@ export default class BleTransport {
|
|
|
13
14
|
|
|
14
15
|
device: Device;
|
|
15
16
|
|
|
16
|
-
mtuSize =
|
|
17
|
+
mtuSize = 23;
|
|
17
18
|
|
|
18
19
|
writeCharacteristic: Characteristic;
|
|
19
20
|
|
|
@@ -23,6 +24,10 @@ export default class BleTransport {
|
|
|
23
24
|
|
|
24
25
|
disconnectSubscription?: Subscription;
|
|
25
26
|
|
|
27
|
+
notifyTransactionId?: string;
|
|
28
|
+
|
|
29
|
+
monitorToken?: number;
|
|
30
|
+
|
|
26
31
|
static MAX_RETRIES = 5;
|
|
27
32
|
|
|
28
33
|
static RETRY_DELAY = 2000;
|
|
@@ -36,7 +41,6 @@ export default class BleTransport {
|
|
|
36
41
|
this.device = device;
|
|
37
42
|
this.writeCharacteristic = writeCharacteristic;
|
|
38
43
|
this.notifyCharacteristic = notifyCharacteristic;
|
|
39
|
-
console.log(`BleTransport(${String(this.id)}) new instance`);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { hasWritableCapability, resolveProtocolV2PacketCapacity } from '../bleStrategy';
|
|
2
|
+
|
|
3
|
+
describe('React Native BLE strategy', () => {
|
|
4
|
+
test('accepts writeWithoutResponse-only characteristics', () => {
|
|
5
|
+
const characteristic = {
|
|
6
|
+
isWritableWithResponse: false,
|
|
7
|
+
isWritableWithoutResponse: true,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
expect(hasWritableCapability(characteristic)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('falls back to Android default ATT payload when MTU is unavailable', () => {
|
|
14
|
+
expect(
|
|
15
|
+
resolveProtocolV2PacketCapacity({
|
|
16
|
+
platform: 'android',
|
|
17
|
+
androidPacketLength: 192,
|
|
18
|
+
mtu: null,
|
|
19
|
+
})
|
|
20
|
+
).toBe(20);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('caps Android packet length by negotiated MTU payload', () => {
|
|
24
|
+
expect(
|
|
25
|
+
resolveProtocolV2PacketCapacity({
|
|
26
|
+
platform: 'android',
|
|
27
|
+
androidPacketLength: 192,
|
|
28
|
+
mtu: 100,
|
|
29
|
+
})
|
|
30
|
+
).toBe(97);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('keeps iOS packet length controlled by tuning profile', () => {
|
|
34
|
+
expect(
|
|
35
|
+
resolveProtocolV2PacketCapacity({
|
|
36
|
+
platform: 'ios',
|
|
37
|
+
iosPacketLength: 244,
|
|
38
|
+
mtu: 256,
|
|
39
|
+
})
|
|
40
|
+
).toBe(244);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getBleUuidKey, getBluetoothServiceUuids, getInfosForServiceUuid } from '../constants';
|
|
2
|
+
|
|
3
|
+
describe('React Native BLE service constants', () => {
|
|
4
|
+
it('includes the Pro2 advertisement service in scan filters', () => {
|
|
5
|
+
expect(getBluetoothServiceUuids().map(getBleUuidKey)).toContain('fffd');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it.each(['fffd', '0000fffd-0000-1000-8000-00805f9b34fb'])(
|
|
9
|
+
'resolves Pro2 service configuration for %s',
|
|
10
|
+
serviceUuid => {
|
|
11
|
+
expect(getInfosForServiceUuid(serviceUuid, 'classic')).toEqual({
|
|
12
|
+
serviceUuid: 'fffd',
|
|
13
|
+
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
14
|
+
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import transportPackage, {
|
|
3
|
+
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
4
|
+
ProtocolV2,
|
|
5
|
+
bytesToHex,
|
|
6
|
+
} from '@onekeyfe/hd-transport';
|
|
7
|
+
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
8
|
+
|
|
9
|
+
import ReactNativeBleTransport from '../index';
|
|
10
|
+
|
|
11
|
+
jest.mock(
|
|
12
|
+
'react-native',
|
|
13
|
+
() => ({
|
|
14
|
+
PermissionsAndroid: {},
|
|
15
|
+
Platform: { OS: 'ios' },
|
|
16
|
+
}),
|
|
17
|
+
{ virtual: true }
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
jest.mock('react-native-ble-plx', () => ({
|
|
21
|
+
BleATTErrorCode: { UnlikelyError: 14 },
|
|
22
|
+
BleError: class BleError extends Error {},
|
|
23
|
+
BleErrorCode: {
|
|
24
|
+
DeviceAlreadyConnected: 203,
|
|
25
|
+
DeviceDisconnected: 205,
|
|
26
|
+
DeviceMTUChangeFailed: 206,
|
|
27
|
+
OperationCancelled: 2,
|
|
28
|
+
CharacteristicNotFound: 404,
|
|
29
|
+
},
|
|
30
|
+
BleManager: jest.fn(),
|
|
31
|
+
ScanMode: { LowLatency: 2 },
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('../BleManager', () => ({
|
|
35
|
+
getConnectedDeviceIds: jest.fn(() => Promise.resolve([])),
|
|
36
|
+
onDeviceBondState: jest.fn(() => Promise.resolve()),
|
|
37
|
+
pairDevice: jest.fn(() => Promise.resolve({ bonded: true, bonding: false })),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock('../subscribeBleOn', () => ({
|
|
41
|
+
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const { parseConfigure } = transportPackage;
|
|
45
|
+
|
|
46
|
+
const protocolV1Schema = {
|
|
47
|
+
nested: {
|
|
48
|
+
Initialize: { fields: {} },
|
|
49
|
+
Success: {
|
|
50
|
+
fields: {
|
|
51
|
+
message: { type: 'string', id: 1 },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
MessageType: {
|
|
55
|
+
values: {
|
|
56
|
+
MessageType_Initialize: 1,
|
|
57
|
+
MessageType_Success: 2,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const protocolV2Schema = {
|
|
64
|
+
nested: {
|
|
65
|
+
Ping: {
|
|
66
|
+
fields: {
|
|
67
|
+
message: { type: 'string', id: 1 },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
FileWrite: { fields: {} },
|
|
71
|
+
Success: {
|
|
72
|
+
fields: {
|
|
73
|
+
message: { type: 'string', id: 1 },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
MessageType: {
|
|
77
|
+
values: {
|
|
78
|
+
MessageType_Ping: 60206,
|
|
79
|
+
MessageType_Success: 60207,
|
|
80
|
+
MessageType_FileWrite: 60805,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const schemas = {
|
|
87
|
+
protocolV1: parseConfigure(protocolV1Schema),
|
|
88
|
+
protocolV2: parseConfigure(protocolV2Schema),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const createHarness = () => {
|
|
92
|
+
const uuid = 'rn-pro2-id';
|
|
93
|
+
const sentSeqs: number[] = [];
|
|
94
|
+
let shouldRespond = true;
|
|
95
|
+
let notifyCallback:
|
|
96
|
+
| ((
|
|
97
|
+
error: (Error & { reason?: string }) | null,
|
|
98
|
+
characteristic: { value: string } | null
|
|
99
|
+
) => void)
|
|
100
|
+
| undefined;
|
|
101
|
+
const notifyCharacteristic = {
|
|
102
|
+
uuid: '0003',
|
|
103
|
+
deviceID: uuid,
|
|
104
|
+
isNotifiable: true,
|
|
105
|
+
monitor: jest.fn(callback => {
|
|
106
|
+
notifyCallback = callback;
|
|
107
|
+
return { remove: jest.fn() };
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
const handleWrite = (base64: string) => {
|
|
111
|
+
const frame = Buffer.from(base64, 'base64');
|
|
112
|
+
sentSeqs.push(frame[6]);
|
|
113
|
+
if (shouldRespond) {
|
|
114
|
+
const response = ProtocolV2.encodeFrame(
|
|
115
|
+
schemas,
|
|
116
|
+
'Success',
|
|
117
|
+
{ message: 'ok' },
|
|
118
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
119
|
+
);
|
|
120
|
+
notifyCallback?.(null, { value: Buffer.from(response).toString('base64') });
|
|
121
|
+
}
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
};
|
|
124
|
+
const writeCharacteristic = {
|
|
125
|
+
uuid: '0002',
|
|
126
|
+
deviceID: uuid,
|
|
127
|
+
isWritableWithResponse: true,
|
|
128
|
+
isWritableWithoutResponse: true,
|
|
129
|
+
writeWithResponse: jest.fn(handleWrite),
|
|
130
|
+
writeWithoutResponse: jest.fn(handleWrite),
|
|
131
|
+
};
|
|
132
|
+
const device = {
|
|
133
|
+
id: uuid,
|
|
134
|
+
name: 'OneKey Pro 2',
|
|
135
|
+
localName: 'OneKey Pro 2',
|
|
136
|
+
serviceUUIDs: ['fffd'],
|
|
137
|
+
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
138
|
+
onDisconnected: jest.fn(() => ({ remove: jest.fn() })),
|
|
139
|
+
};
|
|
140
|
+
const bleManager = {
|
|
141
|
+
devices: jest.fn(() => Promise.resolve([device])),
|
|
142
|
+
connectedDevices: jest.fn(() => Promise.resolve([])),
|
|
143
|
+
cancelTransaction: jest.fn(() => Promise.resolve()),
|
|
144
|
+
};
|
|
145
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
146
|
+
transport.blePlxManager = bleManager;
|
|
147
|
+
transport.resolveCharacteristics = jest.fn(() =>
|
|
148
|
+
Promise.resolve({ writeCharacteristic, notifyCharacteristic })
|
|
149
|
+
);
|
|
150
|
+
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
151
|
+
transport.configure(protocolV1Schema);
|
|
152
|
+
transport.configureProtocolV2(protocolV2Schema);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
transport,
|
|
156
|
+
uuid,
|
|
157
|
+
sentSeqs,
|
|
158
|
+
writeCharacteristic,
|
|
159
|
+
setShouldRespond(value: boolean) {
|
|
160
|
+
shouldRespond = value;
|
|
161
|
+
},
|
|
162
|
+
emitMonitorError(error: Error & { reason?: string }) {
|
|
163
|
+
notifyCallback?.(error, null);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
169
|
+
test('keeps the Protocol V2 sequence across probe and the next call', async () => {
|
|
170
|
+
const { transport, uuid, sentSeqs } = createHarness();
|
|
171
|
+
|
|
172
|
+
await transport.acquire({ uuid });
|
|
173
|
+
await transport.call(uuid, 'Ping', { message: 'after-probe' });
|
|
174
|
+
|
|
175
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
176
|
+
expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
|
|
177
|
+
await transport.release(uuid, true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('rejects the active Protocol V2 reader when the current monitor errors', async () => {
|
|
181
|
+
const harness = createHarness();
|
|
182
|
+
const { transport, uuid, sentSeqs } = harness;
|
|
183
|
+
await transport.acquire({ uuid });
|
|
184
|
+
harness.setShouldRespond(false);
|
|
185
|
+
|
|
186
|
+
const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
|
|
187
|
+
while (sentSeqs.length < 2) {
|
|
188
|
+
await Promise.resolve();
|
|
189
|
+
}
|
|
190
|
+
await new Promise(resolve => {
|
|
191
|
+
setTimeout(resolve, 0);
|
|
192
|
+
});
|
|
193
|
+
harness.emitMonitorError(Object.assign(new Error('monitor failed'), { reason: 'link lost' }));
|
|
194
|
+
|
|
195
|
+
await expect(call).rejects.toMatchObject({
|
|
196
|
+
errorCode: HardwareErrorCode.BleCharacteristicNotifyError,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('retains the sequence cursor when a new monitor generation is acquired', async () => {
|
|
201
|
+
const { transport, uuid, sentSeqs } = createHarness();
|
|
202
|
+
|
|
203
|
+
await transport.acquire({ uuid });
|
|
204
|
+
await transport.release(uuid, true);
|
|
205
|
+
await transport.acquire({ uuid });
|
|
206
|
+
|
|
207
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
208
|
+
await transport.release(uuid, true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('uses withoutResponse for normal and high-volume calls', async () => {
|
|
212
|
+
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
213
|
+
|
|
214
|
+
await transport.acquire({ uuid });
|
|
215
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
217
|
+
|
|
218
|
+
await transport.call(uuid, 'Ping', { message: 'normal' });
|
|
219
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
|
|
220
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
221
|
+
|
|
222
|
+
await transport.call(uuid, 'FileWrite', {});
|
|
223
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
|
|
224
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
225
|
+
await transport.release(uuid, true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
|
|
229
|
+
const harness = createHarness();
|
|
230
|
+
const { transport, uuid, sentSeqs } = harness;
|
|
231
|
+
await transport.acquire({ uuid });
|
|
232
|
+
harness.setShouldRespond(false);
|
|
233
|
+
|
|
234
|
+
const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
|
|
235
|
+
while (sentSeqs.length < 2) {
|
|
236
|
+
await Promise.resolve();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
|
|
240
|
+
await transport.disconnect(uuid);
|
|
241
|
+
await rejection;
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ANDROID_DEFAULT_MTU, ANDROID_PACKET_LENGTH, IOS_PACKET_LENGTH } from './constants';
|
|
2
|
+
|
|
3
|
+
export type BlePlatform = 'ios' | 'android' | string;
|
|
4
|
+
|
|
5
|
+
export type BleWriteCapability = {
|
|
6
|
+
isWritableWithResponse?: boolean | null;
|
|
7
|
+
isWritableWithoutResponse?: boolean | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function hasWritableCapability(characteristic: BleWriteCapability) {
|
|
11
|
+
return !!(characteristic.isWritableWithResponse || characteristic.isWritableWithoutResponse);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveProtocolV2PacketCapacity({
|
|
15
|
+
platform,
|
|
16
|
+
iosPacketLength = IOS_PACKET_LENGTH,
|
|
17
|
+
androidPacketLength = ANDROID_PACKET_LENGTH,
|
|
18
|
+
mtu,
|
|
19
|
+
}: {
|
|
20
|
+
platform: BlePlatform;
|
|
21
|
+
iosPacketLength?: number;
|
|
22
|
+
androidPacketLength?: number;
|
|
23
|
+
mtu?: number | null;
|
|
24
|
+
}) {
|
|
25
|
+
if (platform === 'ios') {
|
|
26
|
+
return iosPacketLength;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (platform === 'android') {
|
|
30
|
+
const payloadLength = Math.max((mtu ?? ANDROID_DEFAULT_MTU) - 3, 1);
|
|
31
|
+
return Math.min(androidPacketLength, payloadLength);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return androidPacketLength;
|
|
35
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export const IOS_PACKET_LENGTH = 128;
|
|
2
2
|
export const ANDROID_PACKET_LENGTH = 192;
|
|
3
|
+
export const ANDROID_DEFAULT_MTU = 23;
|
|
4
|
+
|
|
5
|
+
export const normalizeBleUuid = (uuid?: string | null) =>
|
|
6
|
+
(uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
7
|
+
|
|
8
|
+
export const getBleUuidKey = (uuid?: string | null) => {
|
|
9
|
+
const normalized = normalizeBleUuid(uuid);
|
|
10
|
+
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
11
|
+
};
|
|
3
12
|
|
|
4
13
|
type BluetoothServices = Record<
|
|
5
14
|
string,
|
|
@@ -11,6 +20,7 @@ type BluetoothServices = Record<
|
|
|
11
20
|
>;
|
|
12
21
|
|
|
13
22
|
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
23
|
+
const Pro2ServiceUUID = 'fffd';
|
|
14
24
|
|
|
15
25
|
const OneKeyServices: Record<string, BluetoothServices> = {
|
|
16
26
|
classic: {
|
|
@@ -19,6 +29,11 @@ const OneKeyServices: Record<string, BluetoothServices> = {
|
|
|
19
29
|
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
20
30
|
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
21
31
|
},
|
|
32
|
+
[Pro2ServiceUUID]: {
|
|
33
|
+
serviceUuid: Pro2ServiceUUID,
|
|
34
|
+
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
35
|
+
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
36
|
+
},
|
|
22
37
|
},
|
|
23
38
|
};
|
|
24
39
|
|
|
@@ -35,9 +50,26 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
|
|
|
35
50
|
if (!services) {
|
|
36
51
|
return null;
|
|
37
52
|
}
|
|
38
|
-
const
|
|
53
|
+
const normalizedServiceUuid = normalizeBleUuid(serviceUuid);
|
|
54
|
+
const service =
|
|
55
|
+
services[serviceUuid] ??
|
|
56
|
+
Object.values(services).find(
|
|
57
|
+
item =>
|
|
58
|
+
normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid ||
|
|
59
|
+
getBleUuidKey(item.serviceUuid) === getBleUuidKey(serviceUuid)
|
|
60
|
+
);
|
|
39
61
|
if (!service) {
|
|
40
62
|
return null;
|
|
41
63
|
}
|
|
42
64
|
return service;
|
|
43
65
|
};
|
|
66
|
+
|
|
67
|
+
export const isSameBleUuid = (left?: string | null, right?: string | null) => {
|
|
68
|
+
const normalizedLeft = normalizeBleUuid(left);
|
|
69
|
+
const normalizedRight = normalizeBleUuid(right);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
normalizedLeft === normalizedRight ||
|
|
73
|
+
(getBleUuidKey(left) !== '' && getBleUuidKey(left) === getBleUuidKey(right))
|
|
74
|
+
);
|
|
75
|
+
};
|