@onekeyfe/hd-transport-react-native 1.2.0-alpha.50 → 1.2.0-alpha.51
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/index.d.ts.map +1 -1
- package/dist/index.js +29 -62
- package/jest.config.js +5 -0
- package/package.json +5 -5
- package/src/__tests__/protocolV2Link.test.ts +33 -229
- package/src/__tests__/staleCallTimeout.test.ts +211 -0
- package/src/index.ts +32 -65
- package/src/__tests__/enumerate.test.ts +0 -132
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAIL,UAAU,IAAI,aAAa,EAE5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,SAAS,EAAE,EAEhB,KAAK,oBAAoB,EAIzB,KAAK,YAAY,EAKjB,KAAK,oBAAoB,EAG1B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -222,7 +222,6 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = reactNative.Platform.OS === 'ios' ? 4 :
|
|
|
222
222
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = reactNative.Platform.OS === 'ios' ? 8 : 10;
|
|
223
223
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = reactNative.Platform.OS === 'ios' ? 24 : 30;
|
|
224
224
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
225
|
-
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
226
225
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
227
226
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY = reactNative.Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
228
227
|
const ANDROID_GATT_CONGESTED_STATUS = 143;
|
|
@@ -615,17 +614,12 @@ class ReactNativeBleTransport {
|
|
|
615
614
|
return;
|
|
616
615
|
}
|
|
617
616
|
const displayName = getDeviceDisplayName(device);
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
id: device === null || device === void 0 ? void 0 : device.id,
|
|
625
|
-
name: device === null || device === void 0 ? void 0 : device.name,
|
|
626
|
-
localName: device === null || device === void 0 ? void 0 : device.localName,
|
|
627
|
-
serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
|
|
628
|
-
});
|
|
617
|
+
const isOneKey = hdShared.isOnekeyBluetoothDevice({
|
|
618
|
+
id: device === null || device === void 0 ? void 0 : device.id,
|
|
619
|
+
name: device === null || device === void 0 ? void 0 : device.name,
|
|
620
|
+
localName: device === null || device === void 0 ? void 0 : device.localName,
|
|
621
|
+
serviceUuids: device === null || device === void 0 ? void 0 : device.serviceUUIDs,
|
|
622
|
+
});
|
|
629
623
|
if (isOneKey) {
|
|
630
624
|
addDevice(device);
|
|
631
625
|
}
|
|
@@ -643,15 +637,12 @@ class ReactNativeBleTransport {
|
|
|
643
637
|
const localName = 'localName' in device && typeof device.localName === 'string'
|
|
644
638
|
? device.localName
|
|
645
639
|
: null;
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
localName,
|
|
653
|
-
serviceUuids: device.serviceUUIDs,
|
|
654
|
-
})) {
|
|
640
|
+
if (hdShared.isOnekeyBluetoothDevice({
|
|
641
|
+
id: device.id,
|
|
642
|
+
name: device.name,
|
|
643
|
+
localName,
|
|
644
|
+
serviceUuids: device.serviceUUIDs,
|
|
645
|
+
})) {
|
|
655
646
|
Log === null || Log === void 0 ? void 0 : Log.debug('search connected peripheral: ', device.id);
|
|
656
647
|
addDevice(device);
|
|
657
648
|
}
|
|
@@ -1044,8 +1035,18 @@ class ReactNativeBleTransport {
|
|
|
1044
1035
|
const transport = this.getCachedTransport(uuid);
|
|
1045
1036
|
const runPromise = hdShared.createDeferred();
|
|
1046
1037
|
runPromise.promise.catch(() => undefined);
|
|
1038
|
+
const supersededRunPromise = this.runPromise;
|
|
1039
|
+
if (supersededRunPromise) {
|
|
1040
|
+
supersededRunPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise));
|
|
1041
|
+
}
|
|
1047
1042
|
this.runPromise = runPromise;
|
|
1048
1043
|
this.runPromiseDeviceId = uuid;
|
|
1044
|
+
const releaseOwnershipIfCurrent = () => {
|
|
1045
|
+
if (this.runPromise === runPromise) {
|
|
1046
|
+
this.runPromise = null;
|
|
1047
|
+
this.runPromiseDeviceId = null;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1049
1050
|
const messages = this._messages;
|
|
1050
1051
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1051
1052
|
let timeout;
|
|
@@ -1104,7 +1105,7 @@ class ReactNativeBleTransport {
|
|
|
1104
1105
|
}
|
|
1105
1106
|
if (name === 'EmmcFileWrite') {
|
|
1106
1107
|
yield writeChunkedData(buffers, data => transport.writeWithRetry(data), e => {
|
|
1107
|
-
|
|
1108
|
+
releaseOwnershipIfCurrent();
|
|
1108
1109
|
Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
|
|
1109
1110
|
});
|
|
1110
1111
|
}
|
|
@@ -1139,7 +1140,7 @@ class ReactNativeBleTransport {
|
|
|
1139
1140
|
}
|
|
1140
1141
|
}
|
|
1141
1142
|
}), e => {
|
|
1142
|
-
|
|
1143
|
+
releaseOwnershipIfCurrent();
|
|
1143
1144
|
Log === null || Log === void 0 ? void 0 : Log.error('writeCharacteristic write error: ', e);
|
|
1144
1145
|
});
|
|
1145
1146
|
}
|
|
@@ -1147,17 +1148,11 @@ class ReactNativeBleTransport {
|
|
|
1147
1148
|
for (const o of buffers) {
|
|
1148
1149
|
const outData = o.toString('base64');
|
|
1149
1150
|
try {
|
|
1150
|
-
|
|
1151
|
-
if (shouldUseWriteWithResponse) {
|
|
1152
|
-
yield transport.writeCharacteristic.writeWithResponse(outData);
|
|
1153
|
-
}
|
|
1154
|
-
else {
|
|
1155
|
-
yield transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1156
|
-
}
|
|
1151
|
+
yield transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1157
1152
|
}
|
|
1158
1153
|
catch (e) {
|
|
1159
1154
|
Log === null || Log === void 0 ? void 0 : Log.debug('writeCharacteristic write error: ', e);
|
|
1160
|
-
|
|
1155
|
+
releaseOwnershipIfCurrent();
|
|
1161
1156
|
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceDisconnected) {
|
|
1162
1157
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceNotBonded);
|
|
1163
1158
|
}
|
|
@@ -1197,7 +1192,9 @@ class ReactNativeBleTransport {
|
|
|
1197
1192
|
Log === null || Log === void 0 ? void 0 : Log.error('call error: ', e);
|
|
1198
1193
|
}
|
|
1199
1194
|
const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1195
|
+
const isStaleCall = this.runPromise !== runPromise;
|
|
1200
1196
|
if (!isProbeTimeout &&
|
|
1197
|
+
!isStaleCall &&
|
|
1201
1198
|
(e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
|
|
1202
1199
|
yield this.disconnect(uuid);
|
|
1203
1200
|
}
|
|
@@ -1308,25 +1305,7 @@ class ReactNativeBleTransport {
|
|
|
1308
1305
|
}
|
|
1309
1306
|
}
|
|
1310
1307
|
detectProtocol(uuid, expectedProtocol, protocolHint, rebuildTransport) {
|
|
1311
|
-
var _a;
|
|
1312
1308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1313
|
-
if (reactNative.Platform.OS === 'ios') {
|
|
1314
|
-
const protocol = (_a = expectedProtocol !== null && expectedProtocol !== void 0 ? expectedProtocol : protocolHint) !== null && _a !== void 0 ? _a : 'V1';
|
|
1315
|
-
let source = 'ios-legacy-default';
|
|
1316
|
-
if (expectedProtocol) {
|
|
1317
|
-
source = 'expected';
|
|
1318
|
-
}
|
|
1319
|
-
else if (protocolHint) {
|
|
1320
|
-
source = 'hint';
|
|
1321
|
-
}
|
|
1322
|
-
this.deviceProtocol.set(uuid, protocol);
|
|
1323
|
-
Log === null || Log === void 0 ? void 0 : Log.debug('[ReactNativeBleTransport] protocol selected', {
|
|
1324
|
-
deviceId: uuid,
|
|
1325
|
-
protocol,
|
|
1326
|
-
source,
|
|
1327
|
-
});
|
|
1328
|
-
return protocol;
|
|
1329
|
-
}
|
|
1330
1309
|
if (expectedProtocol === 'V1') {
|
|
1331
1310
|
if (yield this.probeProtocolV1(uuid)) {
|
|
1332
1311
|
this.deviceProtocol.set(uuid, 'V1');
|
|
@@ -1532,9 +1511,6 @@ class ReactNativeBleTransport {
|
|
|
1532
1511
|
}
|
|
1533
1512
|
writeProtocolV2Packet(transport, base64, context, assertCurrentGeneration) {
|
|
1534
1513
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1535
|
-
const shouldUseWriteWithResponse = reactNative.Platform.OS === 'ios' &&
|
|
1536
|
-
!context.highVolume &&
|
|
1537
|
-
transport.writeCharacteristic.isWritableWithResponse;
|
|
1538
1514
|
let attempt = 0;
|
|
1539
1515
|
for (;;) {
|
|
1540
1516
|
assertCurrentGeneration();
|
|
@@ -1542,12 +1518,7 @@ class ReactNativeBleTransport {
|
|
|
1542
1518
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1543
1519
|
}
|
|
1544
1520
|
try {
|
|
1545
|
-
|
|
1546
|
-
yield transport.writeCharacteristic.writeWithResponse(base64);
|
|
1547
|
-
}
|
|
1548
|
-
else {
|
|
1549
|
-
yield transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1550
|
-
}
|
|
1521
|
+
yield transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1551
1522
|
assertCurrentGeneration();
|
|
1552
1523
|
return;
|
|
1553
1524
|
}
|
|
@@ -1577,16 +1548,12 @@ class ReactNativeBleTransport {
|
|
|
1577
1548
|
androidPacketLength: tuning.androidPacketLength,
|
|
1578
1549
|
mtu: reactNative.Platform.OS === 'android' ? transport$1.mtuSize : undefined,
|
|
1579
1550
|
});
|
|
1580
|
-
const initialDelayMs = reactNative.Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
1581
|
-
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
1582
|
-
: 0;
|
|
1583
1551
|
yield transport.writeProtocolV2BleFrame({
|
|
1584
1552
|
frame,
|
|
1585
1553
|
packetCapacity,
|
|
1586
1554
|
assertActive: assertCurrentGeneration,
|
|
1587
1555
|
signal: context.signal,
|
|
1588
1556
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1589
|
-
initialDelayMs,
|
|
1590
1557
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1591
1558
|
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1592
1559
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
package/jest.config.js
CHANGED
|
@@ -2,4 +2,9 @@ module.exports = {
|
|
|
2
2
|
preset: '../../jest.config.js',
|
|
3
3
|
testEnvironment: 'node',
|
|
4
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
|
+
},
|
|
5
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.
|
|
3
|
+
"version": "1.2.0-alpha.51",
|
|
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.0-alpha.
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
23
|
+
"@onekeyfe/hd-core": "1.2.0-alpha.51",
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.51",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.51",
|
|
26
26
|
"@onekeyfe/react-native-ble-utils": "^0.1.6",
|
|
27
27
|
"react-native-ble-plx": "3.5.1"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "c947101d26542c9fd27264b9ddff7f7bba0ad24d"
|
|
30
30
|
}
|
|
@@ -3,6 +3,7 @@ import transportPackage, {
|
|
|
3
3
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
4
4
|
ProtocolV2,
|
|
5
5
|
TRANSPORT_EVENT,
|
|
6
|
+
bytesToHex,
|
|
6
7
|
} from '@onekeyfe/hd-transport';
|
|
7
8
|
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
8
9
|
|
|
@@ -45,11 +46,6 @@ jest.mock('../subscribeBleOn', () => ({
|
|
|
45
46
|
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
46
47
|
}));
|
|
47
48
|
|
|
48
|
-
const setPlatformOS = (os: 'ios' | 'android') => {
|
|
49
|
-
const reactNative: { Platform: { OS: string } } = jest.requireMock('react-native');
|
|
50
|
-
reactNative.Platform.OS = os;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
49
|
const { parseConfigure } = transportPackage;
|
|
54
50
|
|
|
55
51
|
const protocolV1Schema = {
|
|
@@ -73,13 +69,11 @@ const protocolV1Schema = {
|
|
|
73
69
|
|
|
74
70
|
const protocolV2Schema = {
|
|
75
71
|
nested: {
|
|
76
|
-
ProtocolInfoRequest: { fields: {} },
|
|
77
72
|
Ping: {
|
|
78
73
|
fields: {
|
|
79
74
|
message: { type: 'string', id: 1 },
|
|
80
75
|
},
|
|
81
76
|
},
|
|
82
|
-
DeviceInfoGet: { fields: {} },
|
|
83
77
|
FileWrite: { fields: {} },
|
|
84
78
|
Success: {
|
|
85
79
|
fields: {
|
|
@@ -88,10 +82,8 @@ const protocolV2Schema = {
|
|
|
88
82
|
},
|
|
89
83
|
MessageType: {
|
|
90
84
|
values: {
|
|
91
|
-
MessageType_ProtocolInfoRequest: 60200,
|
|
92
85
|
MessageType_Ping: 60206,
|
|
93
86
|
MessageType_Success: 60207,
|
|
94
|
-
MessageType_DeviceInfoGet: 60600,
|
|
95
87
|
MessageType_FileWrite: 60805,
|
|
96
88
|
},
|
|
97
89
|
},
|
|
@@ -103,13 +95,7 @@ const schemas = {
|
|
|
103
95
|
protocolV2: parseConfigure(protocolV2Schema),
|
|
104
96
|
};
|
|
105
97
|
|
|
106
|
-
const createHarness = ({
|
|
107
|
-
deviceName = 'OneKey Pro 2',
|
|
108
|
-
isWritableWithResponse = true,
|
|
109
|
-
}: {
|
|
110
|
-
deviceName?: string;
|
|
111
|
-
isWritableWithResponse?: boolean;
|
|
112
|
-
} = {}) => {
|
|
98
|
+
const createHarness = () => {
|
|
113
99
|
const uuid = 'rn-pro2-id';
|
|
114
100
|
const sentSeqs: number[] = [];
|
|
115
101
|
let responseSeq = 0;
|
|
@@ -148,15 +134,15 @@ const createHarness = ({
|
|
|
148
134
|
const writeCharacteristic = {
|
|
149
135
|
uuid: '0002',
|
|
150
136
|
deviceID: uuid,
|
|
151
|
-
isWritableWithResponse,
|
|
137
|
+
isWritableWithResponse: true,
|
|
152
138
|
isWritableWithoutResponse: true,
|
|
153
139
|
writeWithResponse: jest.fn(handleWrite),
|
|
154
140
|
writeWithoutResponse: jest.fn(handleWrite),
|
|
155
141
|
};
|
|
156
142
|
const device = {
|
|
157
143
|
id: uuid,
|
|
158
|
-
name:
|
|
159
|
-
localName:
|
|
144
|
+
name: 'OneKey Pro 2',
|
|
145
|
+
localName: 'OneKey Pro 2',
|
|
160
146
|
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
161
147
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
162
148
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
@@ -198,13 +184,7 @@ const createHarness = ({
|
|
|
198
184
|
};
|
|
199
185
|
};
|
|
200
186
|
|
|
201
|
-
const createV1Harness = ({
|
|
202
|
-
respondOnWriteCount = 1,
|
|
203
|
-
isWritableWithResponse = true,
|
|
204
|
-
}: {
|
|
205
|
-
respondOnWriteCount?: number | number[];
|
|
206
|
-
isWritableWithResponse?: boolean;
|
|
207
|
-
} = {}) => {
|
|
187
|
+
const createV1Harness = () => {
|
|
208
188
|
const uuid = 'rn-classic-id';
|
|
209
189
|
const notifySubscriptionRemovers: jest.Mock[] = [];
|
|
210
190
|
const disconnectSubscriptionRemovers: jest.Mock[] = [];
|
|
@@ -223,25 +203,20 @@ const createV1Harness = ({
|
|
|
223
203
|
}),
|
|
224
204
|
};
|
|
225
205
|
let writeCount = 0;
|
|
226
|
-
const responseWriteCounts = new Set(
|
|
227
|
-
Array.isArray(respondOnWriteCount) ? respondOnWriteCount : [respondOnWriteCount]
|
|
228
|
-
);
|
|
229
|
-
const handleWrite = () => {
|
|
230
|
-
writeCount += 1;
|
|
231
|
-
if (responseWriteCounts.has(writeCount)) {
|
|
232
|
-
notifyCallback?.(null, {
|
|
233
|
-
value: Buffer.from('3f23230002000000040a026f6b', 'hex').toString('base64'),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return Promise.resolve();
|
|
237
|
-
};
|
|
238
206
|
const writeCharacteristic = {
|
|
239
207
|
uuid: '0002',
|
|
240
208
|
deviceID: uuid,
|
|
241
|
-
isWritableWithResponse,
|
|
209
|
+
isWritableWithResponse: true,
|
|
242
210
|
isWritableWithoutResponse: true,
|
|
243
|
-
|
|
244
|
-
|
|
211
|
+
writeWithoutResponse: jest.fn(() => {
|
|
212
|
+
writeCount += 1;
|
|
213
|
+
if (writeCount === 1) {
|
|
214
|
+
notifyCallback?.(null, {
|
|
215
|
+
value: Buffer.from('3f23230002000000040a026f6b', 'hex').toString('base64'),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return Promise.resolve();
|
|
219
|
+
}),
|
|
245
220
|
};
|
|
246
221
|
const device = {
|
|
247
222
|
id: uuid,
|
|
@@ -274,7 +249,6 @@ const createV1Harness = ({
|
|
|
274
249
|
uuid,
|
|
275
250
|
device,
|
|
276
251
|
bleManager,
|
|
277
|
-
writeCharacteristic,
|
|
278
252
|
notifySubscriptionRemovers,
|
|
279
253
|
disconnectSubscriptionRemovers,
|
|
280
254
|
};
|
|
@@ -328,81 +302,12 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
328
302
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
329
303
|
});
|
|
330
304
|
|
|
331
|
-
test('uses withResponse for consecutive iOS Protocol V1 control commands without releasing', async () => {
|
|
332
|
-
const { transport, uuid, writeCharacteristic } = createV1Harness({
|
|
333
|
-
respondOnWriteCount: [1, 2],
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
337
|
-
uuid,
|
|
338
|
-
protocolType: 'V1',
|
|
339
|
-
});
|
|
340
|
-
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
341
|
-
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
342
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
343
|
-
|
|
344
|
-
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
345
|
-
await expect(transport.call(uuid, 'GetFeatures', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
346
|
-
|
|
347
|
-
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
|
|
348
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
349
|
-
expect(releaseNative).not.toHaveBeenCalled();
|
|
350
|
-
await transport.release(uuid, true);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test('falls back to withoutResponse for an iOS Protocol V1 control command when required', async () => {
|
|
354
|
-
const { transport, uuid, writeCharacteristic } = createV1Harness({
|
|
355
|
-
isWritableWithResponse: false,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
await transport.acquire({ uuid });
|
|
359
|
-
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
360
|
-
|
|
361
|
-
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
362
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
363
|
-
await transport.release(uuid, true);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test('does not resend a failed iOS Protocol V1 control write without response', async () => {
|
|
367
|
-
const { transport, uuid, writeCharacteristic } = createV1Harness();
|
|
368
|
-
const writeError = new Error('write with response failed');
|
|
369
|
-
|
|
370
|
-
await transport.acquire({ uuid });
|
|
371
|
-
writeCharacteristic.writeWithResponse.mockRejectedValueOnce(writeError);
|
|
372
|
-
|
|
373
|
-
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
|
|
374
|
-
errorCode: HardwareErrorCode.BleWriteCharacteristicError,
|
|
375
|
-
});
|
|
376
|
-
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
377
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
378
|
-
await transport.release(uuid, true);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
test('keeps the first Core command as the first iOS BLE request for a Protocol V2 device', async () => {
|
|
382
|
-
const { transport, uuid, sentSeqs, writeCharacteristic } = createHarness({
|
|
383
|
-
deviceName: 'Pro2 6E9E',
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
387
|
-
uuid,
|
|
388
|
-
protocolType: 'V2',
|
|
389
|
-
});
|
|
390
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
391
|
-
|
|
392
|
-
await expect(
|
|
393
|
-
transport.call(uuid, 'Ping', { message: 'first-core-command' })
|
|
394
|
-
).resolves.toBeDefined();
|
|
395
|
-
expect(sentSeqs).toEqual([1]);
|
|
396
|
-
await transport.release(uuid, true);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
305
|
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
400
|
-
setPlatformOS('android');
|
|
401
306
|
const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
|
|
402
307
|
createV1Harness();
|
|
403
308
|
const probeProtocolV2 = jest
|
|
404
309
|
.spyOn(transport as any, 'probeProtocolV2')
|
|
405
|
-
.
|
|
310
|
+
.mockImplementationOnce(async () => {
|
|
406
311
|
await (transport as any).releaseNative(uuid, true);
|
|
407
312
|
return false;
|
|
408
313
|
});
|
|
@@ -428,9 +333,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
428
333
|
});
|
|
429
334
|
|
|
430
335
|
test('cleans the rebuilt transport when Protocol V1 fallback also fails', async () => {
|
|
431
|
-
setPlatformOS('android');
|
|
432
336
|
const { transport, uuid, device, bleManager, notifySubscriptionRemovers } = createV1Harness();
|
|
433
|
-
jest.spyOn(transport as any, 'probeProtocolV2').
|
|
337
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockImplementationOnce(async () => {
|
|
434
338
|
await (transport as any).releaseNative(uuid, true);
|
|
435
339
|
return false;
|
|
436
340
|
});
|
|
@@ -449,9 +353,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
449
353
|
});
|
|
450
354
|
|
|
451
355
|
test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
|
|
452
|
-
const { transport, uuid, device } = createV1Harness(
|
|
453
|
-
respondOnWriteCount: Number.POSITIVE_INFINITY,
|
|
454
|
-
});
|
|
356
|
+
const { transport, uuid, device } = createV1Harness();
|
|
455
357
|
|
|
456
358
|
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
457
359
|
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
|
|
@@ -463,17 +365,17 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
463
365
|
});
|
|
464
366
|
|
|
465
367
|
afterEach(() => {
|
|
466
|
-
setPlatformOS('ios');
|
|
467
368
|
resetProtocolV2BleTuning();
|
|
468
369
|
});
|
|
469
370
|
|
|
470
|
-
test('
|
|
371
|
+
test('keeps the Protocol V2 sequence across probe and the next call', async () => {
|
|
471
372
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
472
373
|
|
|
473
374
|
await transport.acquire({ uuid });
|
|
474
|
-
await transport.call(uuid, 'Ping', { message: '
|
|
375
|
+
await transport.call(uuid, 'Ping', { message: 'after-probe' });
|
|
475
376
|
|
|
476
|
-
expect(sentSeqs).toEqual([1]);
|
|
377
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
378
|
+
expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
|
|
477
379
|
await transport.release(uuid, true);
|
|
478
380
|
});
|
|
479
381
|
|
|
@@ -484,10 +386,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
484
386
|
harness.setShouldRespond(false);
|
|
485
387
|
|
|
486
388
|
const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
|
|
487
|
-
while (sentSeqs.length <
|
|
488
|
-
await
|
|
489
|
-
setTimeout(resolve, 0);
|
|
490
|
-
});
|
|
389
|
+
while (sentSeqs.length < 2) {
|
|
390
|
+
await Promise.resolve();
|
|
491
391
|
}
|
|
492
392
|
await new Promise(resolve => {
|
|
493
393
|
setTimeout(resolve, 0);
|
|
@@ -503,124 +403,30 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
503
403
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
504
404
|
|
|
505
405
|
await transport.acquire({ uuid });
|
|
506
|
-
await transport.call(uuid, 'Ping', { message: 'first-generation' });
|
|
507
406
|
await transport.release(uuid, true);
|
|
508
407
|
await transport.acquire({ uuid });
|
|
509
|
-
await transport.call(uuid, 'Ping', { message: 'second-generation' });
|
|
510
408
|
|
|
511
409
|
expect(sentSeqs).toEqual([1, 2]);
|
|
512
410
|
await transport.release(uuid, true);
|
|
513
411
|
});
|
|
514
412
|
|
|
515
|
-
test('uses
|
|
413
|
+
test('uses withoutResponse for normal and high-volume calls', async () => {
|
|
516
414
|
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
517
415
|
|
|
518
416
|
await transport.acquire({ uuid });
|
|
519
|
-
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
520
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
521
|
-
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
522
|
-
|
|
523
|
-
await transport.call(uuid, 'DeviceInfoGet', {});
|
|
524
|
-
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
525
|
-
|
|
526
|
-
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
|
|
527
|
-
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
528
|
-
expect(releaseNative).not.toHaveBeenCalled();
|
|
529
|
-
|
|
530
|
-
await transport.release(uuid, true);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
test('keeps iOS Protocol V2 high-volume calls on withoutResponse', async () => {
|
|
534
|
-
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
535
|
-
|
|
536
|
-
await transport.acquire({ uuid });
|
|
537
|
-
|
|
538
|
-
await transport.call(uuid, 'FileWrite', {});
|
|
539
417
|
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
540
418
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
541
|
-
await transport.release(uuid, true);
|
|
542
|
-
});
|
|
543
419
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
await transport.acquire({ uuid });
|
|
550
|
-
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
420
|
+
await transport.call(uuid, 'Ping', { message: 'normal' });
|
|
421
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
|
|
422
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
551
423
|
|
|
424
|
+
await transport.call(uuid, 'FileWrite', {});
|
|
425
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
|
|
552
426
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
553
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
554
427
|
await transport.release(uuid, true);
|
|
555
428
|
});
|
|
556
429
|
|
|
557
|
-
test('does not resend a failed iOS Protocol V2 control write without response', async () => {
|
|
558
|
-
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
559
|
-
const writeError = new Error('write with response failed');
|
|
560
|
-
const writeWithResponse = jest.fn().mockRejectedValue(writeError);
|
|
561
|
-
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
562
|
-
const context = {
|
|
563
|
-
messageName: 'ProtocolInfoRequest',
|
|
564
|
-
timeoutMs: 1000,
|
|
565
|
-
highVolume: false,
|
|
566
|
-
generation: 1,
|
|
567
|
-
signal: new AbortController().signal,
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
await expect(
|
|
571
|
-
transport.writeProtocolV2Packet(
|
|
572
|
-
{
|
|
573
|
-
writeCharacteristic: {
|
|
574
|
-
isWritableWithResponse: true,
|
|
575
|
-
writeWithResponse,
|
|
576
|
-
writeWithoutResponse,
|
|
577
|
-
},
|
|
578
|
-
},
|
|
579
|
-
Buffer.from('control').toString('base64'),
|
|
580
|
-
context,
|
|
581
|
-
jest.fn()
|
|
582
|
-
)
|
|
583
|
-
).rejects.toBe(writeError);
|
|
584
|
-
expect(writeWithResponse).toHaveBeenCalledTimes(1);
|
|
585
|
-
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
test('paces a one-packet Protocol V2 control write on iOS', async () => {
|
|
589
|
-
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
590
|
-
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
591
|
-
const bleTransport = {
|
|
592
|
-
mtuSize: 23,
|
|
593
|
-
writeCharacteristic: { writeWithoutResponse },
|
|
594
|
-
};
|
|
595
|
-
const context = {
|
|
596
|
-
messageName: 'ProtocolInfoRequest',
|
|
597
|
-
timeoutMs: 1000,
|
|
598
|
-
highVolume: false,
|
|
599
|
-
generation: 1,
|
|
600
|
-
signal: new AbortController().signal,
|
|
601
|
-
};
|
|
602
|
-
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
603
|
-
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const call = transport.writeProtocolV2Frame(
|
|
607
|
-
bleTransport,
|
|
608
|
-
new Uint8Array(10),
|
|
609
|
-
context,
|
|
610
|
-
jest.fn()
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
await Promise.resolve();
|
|
614
|
-
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5);
|
|
615
|
-
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
616
|
-
|
|
617
|
-
await call;
|
|
618
|
-
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
619
|
-
} finally {
|
|
620
|
-
setTimeoutSpy.mockRestore();
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
|
|
624
430
|
test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
|
|
625
431
|
const harness = createHarness();
|
|
626
432
|
const { transport, uuid, sentSeqs } = harness;
|
|
@@ -628,10 +434,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
628
434
|
harness.setShouldRespond(false);
|
|
629
435
|
|
|
630
436
|
const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
|
|
631
|
-
while (sentSeqs.length <
|
|
632
|
-
await
|
|
633
|
-
setTimeout(resolve, 0);
|
|
634
|
-
});
|
|
437
|
+
while (sentSeqs.length < 2) {
|
|
438
|
+
await Promise.resolve();
|
|
635
439
|
}
|
|
636
440
|
|
|
637
441
|
const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import ReactNativeBleTransport from '../index';
|
|
4
|
+
|
|
5
|
+
import messages from '@onekeyfe/hd-transport/messages.json';
|
|
6
|
+
|
|
7
|
+
jest.mock(
|
|
8
|
+
'react-native',
|
|
9
|
+
() => ({
|
|
10
|
+
Platform: { OS: 'ios', select: (spec: Record<string, unknown>) => spec.ios },
|
|
11
|
+
PermissionsAndroid: {
|
|
12
|
+
PERMISSIONS: {},
|
|
13
|
+
RESULTS: {},
|
|
14
|
+
request: jest.fn(),
|
|
15
|
+
requestMultiple: jest.fn(),
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
{ virtual: true }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
jest.mock('react-native-ble-plx', () => ({
|
|
22
|
+
BleATTErrorCode: { InvalidHandle: 1 },
|
|
23
|
+
BleError: Error,
|
|
24
|
+
BleErrorCode: { DeviceDisconnected: 201, OperationStartFailed: 601 },
|
|
25
|
+
BleManager: jest.fn(),
|
|
26
|
+
ScanMode: { LowLatency: 2 },
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
jest.mock('@onekeyfe/react-native-ble-utils', () => ({
|
|
30
|
+
__esModule: true,
|
|
31
|
+
default: {
|
|
32
|
+
getConnectedPeripherals: jest.fn(() => Promise.resolve([])),
|
|
33
|
+
getBondedPeripherals: jest.fn(() => Promise.resolve([])),
|
|
34
|
+
pairDevice: jest.fn(() => Promise.resolve()),
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const UUID = 'stale-timeout-device';
|
|
39
|
+
|
|
40
|
+
const flush = () =>
|
|
41
|
+
new Promise(resolve => {
|
|
42
|
+
setImmediate(resolve);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function createHarness() {
|
|
46
|
+
const t = new ReactNativeBleTransport({});
|
|
47
|
+
t.configure(messages);
|
|
48
|
+
(t as any).deviceProtocol.set(UUID, 'V1');
|
|
49
|
+
const writeWithoutResponse = jest.fn(() => Promise.resolve());
|
|
50
|
+
const fakeBleTransport = {
|
|
51
|
+
writeCharacteristic: { writeWithoutResponse },
|
|
52
|
+
writeWithRetry: jest.fn(() => Promise.resolve()),
|
|
53
|
+
};
|
|
54
|
+
(t as any).getCachedTransport = () => fakeBleTransport;
|
|
55
|
+
const disconnectSpy = jest.spyOn(t, 'disconnect').mockResolvedValue(undefined);
|
|
56
|
+
return { t, disconnectSpy, writeWithoutResponse };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('Protocol V1 stale call timeout', () => {
|
|
60
|
+
beforeAll(() => {
|
|
61
|
+
jest.useFakeTimers({ doNotFake: ['setImmediate', 'performance'] });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterAll(() => {
|
|
65
|
+
jest.useRealTimers();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
jest.clearAllTimers();
|
|
70
|
+
jest.restoreAllMocks();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('superseded Initialize timeout does not tear down the shared transport', async () => {
|
|
74
|
+
const { t, disconnectSpy } = createHarness();
|
|
75
|
+
|
|
76
|
+
// Initialize #1: written while the device reboots, never answered.
|
|
77
|
+
const firstErrors: unknown[] = [];
|
|
78
|
+
const first = t.call(UUID, 'Initialize', {}, { timeoutMs: 25000 });
|
|
79
|
+
first.catch(e => firstErrors.push(e));
|
|
80
|
+
await flush();
|
|
81
|
+
|
|
82
|
+
// Initialize #2 (forceRun) supersedes #1 three seconds later.
|
|
83
|
+
jest.advanceTimersByTime(3000);
|
|
84
|
+
const secondErrors: unknown[] = [];
|
|
85
|
+
const second = t.call(UUID, 'Initialize', {}, { timeoutMs: 25000 });
|
|
86
|
+
second.catch(e => secondErrors.push(e));
|
|
87
|
+
await flush();
|
|
88
|
+
|
|
89
|
+
// The device answers Initialize #2 (settle its deferred at the transport seam).
|
|
90
|
+
expect(t.runPromise).not.toBeNull();
|
|
91
|
+
t.runPromise?.reject(new Error('settled by device response'));
|
|
92
|
+
await flush();
|
|
93
|
+
|
|
94
|
+
// FirmwareUpload is now the active call on the same transport, awaiting its response.
|
|
95
|
+
const uploadErrors: unknown[] = [];
|
|
96
|
+
const upload = t.call(UUID, 'FirmwareUpload', { payload: Buffer.alloc(300) });
|
|
97
|
+
upload.catch(e => uploadErrors.push(e));
|
|
98
|
+
await flush();
|
|
99
|
+
jest.advanceTimersByTime(100); // firmware upload flush delay
|
|
100
|
+
await flush();
|
|
101
|
+
|
|
102
|
+
// Initialize #1's 25s response timer elapses while the upload is in flight.
|
|
103
|
+
jest.advanceTimersByTime(25000);
|
|
104
|
+
await flush();
|
|
105
|
+
|
|
106
|
+
expect(disconnectSpy).not.toHaveBeenCalled();
|
|
107
|
+
expect(firstErrors).toHaveLength(1);
|
|
108
|
+
expect(uploadErrors).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('forceRun supersede settles the previous pending call immediately', async () => {
|
|
112
|
+
const { t } = createHarness();
|
|
113
|
+
|
|
114
|
+
const firstErrors: Array<{ errorCode?: unknown }> = [];
|
|
115
|
+
const first = t.call(UUID, 'Initialize', {}, { timeoutMs: 25000 });
|
|
116
|
+
first.catch(e => firstErrors.push(e));
|
|
117
|
+
await flush();
|
|
118
|
+
|
|
119
|
+
const second = t.call(UUID, 'Initialize', {}, { timeoutMs: 25000 });
|
|
120
|
+
second.catch(() => undefined);
|
|
121
|
+
await flush();
|
|
122
|
+
|
|
123
|
+
expect(firstErrors).toHaveLength(1);
|
|
124
|
+
expect(firstErrors[0]?.errorCode).toBe(HardwareErrorCode.BleForceCleanRunPromise);
|
|
125
|
+
|
|
126
|
+
t.runPromise?.reject(new Error('settle second call'));
|
|
127
|
+
await flush();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('late write failure of a superseded call keeps the successor as owner', async () => {
|
|
131
|
+
const { t, disconnectSpy } = createHarness();
|
|
132
|
+
|
|
133
|
+
// First call's write hangs; its promise is controlled by the test.
|
|
134
|
+
let rejectFirstWrite: ((e: Error) => void) | undefined;
|
|
135
|
+
const fakeBleTransport = {
|
|
136
|
+
writeCharacteristic: {
|
|
137
|
+
writeWithoutResponse: jest
|
|
138
|
+
.fn()
|
|
139
|
+
.mockImplementationOnce(
|
|
140
|
+
() =>
|
|
141
|
+
new Promise((_resolve, reject) => {
|
|
142
|
+
rejectFirstWrite = reject;
|
|
143
|
+
})
|
|
144
|
+
)
|
|
145
|
+
.mockImplementation(() => Promise.resolve()),
|
|
146
|
+
},
|
|
147
|
+
writeWithRetry: jest.fn(() => Promise.resolve()),
|
|
148
|
+
};
|
|
149
|
+
(t as any).getCachedTransport = () => fakeBleTransport;
|
|
150
|
+
|
|
151
|
+
const firstErrors: unknown[] = [];
|
|
152
|
+
const first = t.call(UUID, 'Initialize', {}, { timeoutMs: 25000 });
|
|
153
|
+
first.catch(e => firstErrors.push(e));
|
|
154
|
+
await flush();
|
|
155
|
+
|
|
156
|
+
// forceRun successor takes ownership while the first call is stuck writing.
|
|
157
|
+
const secondErrors: unknown[] = [];
|
|
158
|
+
const second = t.call(UUID, 'Initialize', {}, { timeoutMs: 5000 });
|
|
159
|
+
second.catch(e => secondErrors.push(e));
|
|
160
|
+
await flush();
|
|
161
|
+
|
|
162
|
+
// The first call's write now fails late; it must not clear the successor's slot.
|
|
163
|
+
rejectFirstWrite?.(new Error('late write failure'));
|
|
164
|
+
await flush();
|
|
165
|
+
|
|
166
|
+
expect(t.runPromise).not.toBeNull();
|
|
167
|
+
|
|
168
|
+
// The successor's genuine timeout must still tear the connection down.
|
|
169
|
+
jest.advanceTimersByTime(5000);
|
|
170
|
+
await flush();
|
|
171
|
+
|
|
172
|
+
expect(disconnectSpy).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(secondErrors).toHaveLength(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('orphan timer left behind by cancel() must not disconnect the transport', async () => {
|
|
177
|
+
const { t, disconnectSpy } = createHarness();
|
|
178
|
+
|
|
179
|
+
// cancel() nulls the ownership slot without settling the deferred, so the
|
|
180
|
+
// call's response timer stays armed (reachable via DeviceCommands.dispose).
|
|
181
|
+
const errors: Array<{ errorCode?: unknown }> = [];
|
|
182
|
+
const p = t.call(UUID, 'GetFeatures', {}, { timeoutMs: 5000 });
|
|
183
|
+
p.catch(e => errors.push(e));
|
|
184
|
+
await flush();
|
|
185
|
+
|
|
186
|
+
t.cancel();
|
|
187
|
+
|
|
188
|
+
jest.advanceTimersByTime(5000);
|
|
189
|
+
await flush();
|
|
190
|
+
|
|
191
|
+
expect(disconnectSpy).not.toHaveBeenCalled();
|
|
192
|
+
expect(errors).toHaveLength(1);
|
|
193
|
+
expect(errors[0]?.errorCode).toBe(HardwareErrorCode.BleTimeoutError);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('timeout on the active call still disconnects the transport', async () => {
|
|
197
|
+
const { t, disconnectSpy } = createHarness();
|
|
198
|
+
|
|
199
|
+
const errors: Array<{ errorCode?: unknown }> = [];
|
|
200
|
+
const p = t.call(UUID, 'GetFeatures', {}, { timeoutMs: 5000 });
|
|
201
|
+
p.catch(e => errors.push(e));
|
|
202
|
+
await flush();
|
|
203
|
+
|
|
204
|
+
jest.advanceTimersByTime(5000);
|
|
205
|
+
await flush();
|
|
206
|
+
|
|
207
|
+
expect(disconnectSpy).toHaveBeenCalledTimes(1);
|
|
208
|
+
expect(errors).toHaveLength(1);
|
|
209
|
+
expect(errors[0]?.errorCode).toBe(HardwareErrorCode.BleTimeoutError);
|
|
210
|
+
});
|
|
211
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
HardwareErrorCode,
|
|
29
29
|
createDeferred,
|
|
30
30
|
isOnekeyBluetoothDevice,
|
|
31
|
-
isPro2FindMyAdvertisementName,
|
|
32
31
|
} from '@onekeyfe/hd-shared';
|
|
33
32
|
|
|
34
33
|
import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
|
|
@@ -61,7 +60,6 @@ const FIRMWARE_UPLOAD_WRITE_BURST_SIZE = Platform.OS === 'ios' ? 4 : 5;
|
|
|
61
60
|
const FIRMWARE_UPLOAD_WRITE_PAUSE_MS = Platform.OS === 'ios' ? 8 : 10;
|
|
62
61
|
const FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS = Platform.OS === 'ios' ? 24 : 30;
|
|
63
62
|
const FIRMWARE_UPLOAD_WRITE_MAX_RETRIES = 8;
|
|
64
|
-
const IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS = 5;
|
|
65
63
|
const ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH = 192;
|
|
66
64
|
const FIRMWARE_UPLOAD_WRITE_PACKET_CAPACITY =
|
|
67
65
|
Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_FIRMWARE_UPLOAD_PACKET_LENGTH;
|
|
@@ -582,21 +580,12 @@ export default class ReactNativeBleTransport {
|
|
|
582
580
|
}
|
|
583
581
|
|
|
584
582
|
const displayName = getDeviceDisplayName(device);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const isOneKey =
|
|
592
|
-
!isUnnamedIOSPeripheral &&
|
|
593
|
-
!isFindMyPeripheral &&
|
|
594
|
-
isOnekeyBluetoothDevice({
|
|
595
|
-
id: device?.id,
|
|
596
|
-
name: device?.name,
|
|
597
|
-
localName: device?.localName,
|
|
598
|
-
serviceUuids: device?.serviceUUIDs,
|
|
599
|
-
});
|
|
583
|
+
const isOneKey = isOnekeyBluetoothDevice({
|
|
584
|
+
id: device?.id,
|
|
585
|
+
name: device?.name,
|
|
586
|
+
localName: device?.localName,
|
|
587
|
+
serviceUuids: device?.serviceUUIDs,
|
|
588
|
+
});
|
|
600
589
|
if (isOneKey) {
|
|
601
590
|
addDevice(device as unknown as Device);
|
|
602
591
|
} else if (displayName && /\bpro\s*2\b/i.test(displayName)) {
|
|
@@ -617,12 +606,7 @@ export default class ReactNativeBleTransport {
|
|
|
617
606
|
'localName' in device && typeof device.localName === 'string'
|
|
618
607
|
? device.localName
|
|
619
608
|
: null;
|
|
620
|
-
const isFindMyPeripheral =
|
|
621
|
-
isPro2FindMyAdvertisementName(device.name) ||
|
|
622
|
-
isPro2FindMyAdvertisementName(localName);
|
|
623
|
-
|
|
624
609
|
if (
|
|
625
|
-
!isFindMyPeripheral &&
|
|
626
610
|
isOnekeyBluetoothDevice({
|
|
627
611
|
id: device.id,
|
|
628
612
|
name: device.name,
|
|
@@ -1129,8 +1113,24 @@ export default class ReactNativeBleTransport {
|
|
|
1129
1113
|
const transport = this.getCachedTransport(uuid);
|
|
1130
1114
|
const runPromise = createDeferred<string>();
|
|
1131
1115
|
runPromise.promise.catch(() => undefined);
|
|
1116
|
+
const supersededRunPromise = this.runPromise;
|
|
1117
|
+
if (supersededRunPromise) {
|
|
1118
|
+
// Only forceRun calls (Initialize/Cancel) reach here with a pending call. Settle
|
|
1119
|
+
// the superseded deferred now so its response race resolves and its finally block
|
|
1120
|
+
// clears its timeout timer; an orphaned timer would otherwise fire much later and
|
|
1121
|
+
// tear down the shared connection while another call is using it.
|
|
1122
|
+
supersededRunPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
|
|
1123
|
+
}
|
|
1132
1124
|
this.runPromise = runPromise;
|
|
1133
1125
|
this.runPromiseDeviceId = uuid;
|
|
1126
|
+
// A superseded call's late write failure must not clear the successor's ownership;
|
|
1127
|
+
// only the call that still owns the slot may release it.
|
|
1128
|
+
const releaseOwnershipIfCurrent = () => {
|
|
1129
|
+
if (this.runPromise === runPromise) {
|
|
1130
|
+
this.runPromise = null;
|
|
1131
|
+
this.runPromiseDeviceId = null;
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
1134
|
const messages = this._messages;
|
|
1135
1135
|
const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
|
|
1136
1136
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1202,7 +1202,7 @@ export default class ReactNativeBleTransport {
|
|
|
1202
1202
|
buffers,
|
|
1203
1203
|
data => transport.writeWithRetry(data),
|
|
1204
1204
|
e => {
|
|
1205
|
-
|
|
1205
|
+
releaseOwnershipIfCurrent();
|
|
1206
1206
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1207
1207
|
}
|
|
1208
1208
|
);
|
|
@@ -1242,7 +1242,7 @@ export default class ReactNativeBleTransport {
|
|
|
1242
1242
|
}
|
|
1243
1243
|
},
|
|
1244
1244
|
e => {
|
|
1245
|
-
|
|
1245
|
+
releaseOwnershipIfCurrent();
|
|
1246
1246
|
Log?.error('writeCharacteristic write error: ', e);
|
|
1247
1247
|
}
|
|
1248
1248
|
);
|
|
@@ -1251,16 +1251,10 @@ export default class ReactNativeBleTransport {
|
|
|
1251
1251
|
const outData = o.toString('base64');
|
|
1252
1252
|
// Upload resources on low-end phones may OOM
|
|
1253
1253
|
try {
|
|
1254
|
-
|
|
1255
|
-
Platform.OS === 'ios' && transport.writeCharacteristic.isWritableWithResponse;
|
|
1256
|
-
if (shouldUseWriteWithResponse) {
|
|
1257
|
-
await transport.writeCharacteristic.writeWithResponse(outData);
|
|
1258
|
-
} else {
|
|
1259
|
-
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1260
|
-
}
|
|
1254
|
+
await transport.writeCharacteristic.writeWithoutResponse(outData);
|
|
1261
1255
|
} catch (e) {
|
|
1262
1256
|
Log?.debug('writeCharacteristic write error: ', e);
|
|
1263
|
-
|
|
1257
|
+
releaseOwnershipIfCurrent();
|
|
1264
1258
|
if (e.errorCode === BleErrorCode.DeviceDisconnected) {
|
|
1265
1259
|
throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
|
|
1266
1260
|
} else if (e.errorCode === BleErrorCode.OperationStartFailed) {
|
|
@@ -1303,8 +1297,13 @@ export default class ReactNativeBleTransport {
|
|
|
1303
1297
|
}
|
|
1304
1298
|
const isProbeTimeout =
|
|
1305
1299
|
name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
|
|
1300
|
+
// A call that has been superseded (forceRun) or cleaned up no longer owns the
|
|
1301
|
+
// transport; its late timeout must not tear down the connection the current
|
|
1302
|
+
// call is actively using.
|
|
1303
|
+
const isStaleCall = this.runPromise !== runPromise;
|
|
1306
1304
|
if (
|
|
1307
1305
|
!isProbeTimeout &&
|
|
1306
|
+
!isStaleCall &&
|
|
1308
1307
|
(e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError
|
|
1309
1308
|
) {
|
|
1310
1309
|
await this.disconnect(uuid);
|
|
@@ -1444,23 +1443,6 @@ export default class ReactNativeBleTransport {
|
|
|
1444
1443
|
protocolHint?: ProtocolType,
|
|
1445
1444
|
rebuildTransport?: () => Promise<void>
|
|
1446
1445
|
): Promise<ProtocolType> {
|
|
1447
|
-
if (Platform.OS === 'ios') {
|
|
1448
|
-
const protocol = expectedProtocol ?? protocolHint ?? 'V1';
|
|
1449
|
-
let source = 'ios-legacy-default';
|
|
1450
|
-
if (expectedProtocol) {
|
|
1451
|
-
source = 'expected';
|
|
1452
|
-
} else if (protocolHint) {
|
|
1453
|
-
source = 'hint';
|
|
1454
|
-
}
|
|
1455
|
-
this.deviceProtocol.set(uuid, protocol);
|
|
1456
|
-
Log?.debug('[ReactNativeBleTransport] protocol selected', {
|
|
1457
|
-
deviceId: uuid,
|
|
1458
|
-
protocol,
|
|
1459
|
-
source,
|
|
1460
|
-
});
|
|
1461
|
-
return protocol;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
1446
|
if (expectedProtocol === 'V1') {
|
|
1465
1447
|
if (await this.probeProtocolV1(uuid)) {
|
|
1466
1448
|
this.deviceProtocol.set(uuid, 'V1');
|
|
@@ -1697,10 +1679,6 @@ export default class ReactNativeBleTransport {
|
|
|
1697
1679
|
context: ProtocolV2CallContext,
|
|
1698
1680
|
assertCurrentGeneration: () => void
|
|
1699
1681
|
) {
|
|
1700
|
-
const shouldUseWriteWithResponse =
|
|
1701
|
-
Platform.OS === 'ios' &&
|
|
1702
|
-
!context.highVolume &&
|
|
1703
|
-
transport.writeCharacteristic.isWritableWithResponse;
|
|
1704
1682
|
let attempt = 0;
|
|
1705
1683
|
for (;;) {
|
|
1706
1684
|
assertCurrentGeneration();
|
|
@@ -1708,11 +1686,7 @@ export default class ReactNativeBleTransport {
|
|
|
1708
1686
|
throw new Error(`Protocol V2 BLE write aborted for ${context.messageName}`);
|
|
1709
1687
|
}
|
|
1710
1688
|
try {
|
|
1711
|
-
|
|
1712
|
-
await transport.writeCharacteristic.writeWithResponse(base64);
|
|
1713
|
-
} else {
|
|
1714
|
-
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1715
|
-
}
|
|
1689
|
+
await transport.writeCharacteristic.writeWithoutResponse(base64);
|
|
1716
1690
|
assertCurrentGeneration();
|
|
1717
1691
|
return;
|
|
1718
1692
|
} catch (error) {
|
|
@@ -1747,19 +1721,12 @@ export default class ReactNativeBleTransport {
|
|
|
1747
1721
|
androidPacketLength: tuning.androidPacketLength,
|
|
1748
1722
|
mtu: Platform.OS === 'android' ? transport.mtuSize : undefined,
|
|
1749
1723
|
});
|
|
1750
|
-
// Match Desktop BLE pacing so Pro2 firmware can finish the previous response
|
|
1751
|
-
// before the next single-packet control command is written.
|
|
1752
|
-
const initialDelayMs =
|
|
1753
|
-
Platform.OS === 'ios' && !context.highVolume && frame.length <= packetCapacity
|
|
1754
|
-
? IOS_PROTOCOL_V2_CONTROL_WRITE_DELAY_MS
|
|
1755
|
-
: 0;
|
|
1756
1724
|
await writeProtocolV2BleFrame({
|
|
1757
1725
|
frame,
|
|
1758
1726
|
packetCapacity,
|
|
1759
1727
|
assertActive: assertCurrentGeneration,
|
|
1760
1728
|
signal: context.signal,
|
|
1761
1729
|
abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
|
|
1762
|
-
initialDelayMs,
|
|
1763
1730
|
burstSize: FIRMWARE_UPLOAD_WRITE_BURST_SIZE,
|
|
1764
1731
|
burstPauseMs: FIRMWARE_UPLOAD_WRITE_PAUSE_MS,
|
|
1765
1732
|
flushDelayMs: FIRMWARE_UPLOAD_WRITE_FLUSH_DELAY_MS,
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
3
|
-
import { getConnectedDeviceIds } from '../BleManager';
|
|
4
|
-
import ReactNativeBleTransport from '../index';
|
|
5
|
-
|
|
6
|
-
jest.mock(
|
|
7
|
-
'react-native',
|
|
8
|
-
() => ({
|
|
9
|
-
PermissionsAndroid: {},
|
|
10
|
-
Platform: { OS: 'ios' },
|
|
11
|
-
}),
|
|
12
|
-
{ virtual: true }
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
jest.mock('react-native-ble-plx', () => ({
|
|
16
|
-
BleError: class BleError extends Error {},
|
|
17
|
-
BleErrorCode: {},
|
|
18
|
-
BleManager: jest.fn(),
|
|
19
|
-
ScanMode: { LowLatency: 2 },
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
jest.mock('../BleManager', () => ({
|
|
23
|
-
getConnectedDeviceIds: jest.fn(),
|
|
24
|
-
onDeviceBondState: jest.fn(),
|
|
25
|
-
pairDevice: jest.fn(),
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
jest.mock('../subscribeBleOn', () => ({
|
|
29
|
-
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
const ONEKEY_SERVICE_UUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
33
|
-
|
|
34
|
-
describe('ReactNativeBleTransport iOS discovery', () => {
|
|
35
|
-
test('filters a bonded Pro2 Find My peripheral while keeping the wallet peripheral', async () => {
|
|
36
|
-
jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([
|
|
37
|
-
{
|
|
38
|
-
id: 'find-my-peripheral',
|
|
39
|
-
name: 'Pro2 6E9E - Find My',
|
|
40
|
-
localName: null,
|
|
41
|
-
serviceUUIDs: [ONEKEY_SERVICE_UUID],
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: 'wallet-peripheral',
|
|
45
|
-
name: 'Pro2 6E9E',
|
|
46
|
-
localName: 'Pro2 6E9E',
|
|
47
|
-
serviceUUIDs: [ONEKEY_SERVICE_UUID],
|
|
48
|
-
},
|
|
49
|
-
] as never);
|
|
50
|
-
const blePlxManager = {
|
|
51
|
-
startDeviceScan: jest.fn(),
|
|
52
|
-
stopDeviceScan: jest.fn(),
|
|
53
|
-
};
|
|
54
|
-
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
55
|
-
transport.blePlxManager = blePlxManager as never;
|
|
56
|
-
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
57
|
-
|
|
58
|
-
const devices = await transport.enumerate();
|
|
59
|
-
|
|
60
|
-
expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('filters a scanned Pro2 Find My peripheral by name when localName is null', async () => {
|
|
64
|
-
jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([]);
|
|
65
|
-
const blePlxManager = {
|
|
66
|
-
startDeviceScan: jest.fn((_serviceUUIDs, _options, listener) => {
|
|
67
|
-
queueMicrotask(() => {
|
|
68
|
-
listener(null, {
|
|
69
|
-
id: 'find-my-peripheral',
|
|
70
|
-
name: 'Pro2 6E9E - Find My',
|
|
71
|
-
localName: null,
|
|
72
|
-
serviceUUIDs: [ONEKEY_SERVICE_UUID],
|
|
73
|
-
});
|
|
74
|
-
listener(null, {
|
|
75
|
-
id: 'wallet-peripheral',
|
|
76
|
-
name: 'Pro2 6E9E',
|
|
77
|
-
localName: 'Pro2 6E9E',
|
|
78
|
-
serviceUUIDs: [ONEKEY_SERVICE_UUID],
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}),
|
|
82
|
-
stopDeviceScan: jest.fn(),
|
|
83
|
-
};
|
|
84
|
-
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
85
|
-
transport.blePlxManager = blePlxManager as never;
|
|
86
|
-
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
87
|
-
|
|
88
|
-
const devices = await transport.enumerate();
|
|
89
|
-
|
|
90
|
-
expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('ignores an unnamed scanned advertisement while keeping the named wallet peripheral', async () => {
|
|
94
|
-
jest.mocked(getConnectedDeviceIds).mockResolvedValueOnce([]);
|
|
95
|
-
const blePlxManager = {
|
|
96
|
-
startDeviceScan: jest.fn((_serviceUUIDs, _options, listener) => {
|
|
97
|
-
queueMicrotask(() => {
|
|
98
|
-
listener(null, {
|
|
99
|
-
id: 'unnamed-peripheral',
|
|
100
|
-
name: null,
|
|
101
|
-
localName: null,
|
|
102
|
-
serviceUUIDs: [
|
|
103
|
-
'0000180a-0000-1000-8000-00805f9b34fb',
|
|
104
|
-
'0000180f-0000-1000-8000-00805f9b34fb',
|
|
105
|
-
'0000fffd-0000-1000-8000-00805f9b34fb',
|
|
106
|
-
ONEKEY_SERVICE_UUID,
|
|
107
|
-
],
|
|
108
|
-
});
|
|
109
|
-
listener(null, {
|
|
110
|
-
id: 'wallet-peripheral',
|
|
111
|
-
name: 'Pro2 769D',
|
|
112
|
-
localName: 'Pro2 769D',
|
|
113
|
-
serviceUUIDs: [
|
|
114
|
-
'0000180a-0000-1000-8000-00805f9b34fb',
|
|
115
|
-
'0000180f-0000-1000-8000-00805f9b34fb',
|
|
116
|
-
'0000fffd-0000-1000-8000-00805f9b34fb',
|
|
117
|
-
ONEKEY_SERVICE_UUID,
|
|
118
|
-
],
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}),
|
|
122
|
-
stopDeviceScan: jest.fn(),
|
|
123
|
-
};
|
|
124
|
-
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
125
|
-
transport.blePlxManager = blePlxManager as never;
|
|
126
|
-
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
127
|
-
|
|
128
|
-
const devices = await transport.enumerate();
|
|
129
|
-
|
|
130
|
-
expect(devices.map(device => device.id)).toEqual(['wallet-peripheral']);
|
|
131
|
-
});
|
|
132
|
-
});
|