@onekeyfe/hd-transport-react-native 1.2.0-alpha.54 → 1.2.0-alpha.55
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 +1 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -160
- package/jest.config.js +0 -5
- package/package.json +5 -5
- package/src/__tests__/enumerate.test.ts +132 -0
- package/src/__tests__/protocolV2Link.test.ts +230 -41
- package/src/index.ts +74 -235
- package/src/__tests__/staleCallTimeout.test.ts +0 -211
- package/src/__tests__/writePacketTimeout.test.ts +0 -306
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
});
|
|
@@ -3,7 +3,6 @@ import transportPackage, {
|
|
|
3
3
|
PROTOCOL_V2_CHANNEL_BLE_UART,
|
|
4
4
|
ProtocolV2,
|
|
5
5
|
TRANSPORT_EVENT,
|
|
6
|
-
bytesToHex,
|
|
7
6
|
} from '@onekeyfe/hd-transport';
|
|
8
7
|
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
9
8
|
|
|
@@ -46,6 +45,11 @@ jest.mock('../subscribeBleOn', () => ({
|
|
|
46
45
|
subscribeBleOn: jest.fn(() => Promise.resolve()),
|
|
47
46
|
}));
|
|
48
47
|
|
|
48
|
+
const setPlatformOS = (os: 'ios' | 'android') => {
|
|
49
|
+
const reactNative: { Platform: { OS: string } } = jest.requireMock('react-native');
|
|
50
|
+
reactNative.Platform.OS = os;
|
|
51
|
+
};
|
|
52
|
+
|
|
49
53
|
const { parseConfigure } = transportPackage;
|
|
50
54
|
|
|
51
55
|
const protocolV1Schema = {
|
|
@@ -69,11 +73,13 @@ const protocolV1Schema = {
|
|
|
69
73
|
|
|
70
74
|
const protocolV2Schema = {
|
|
71
75
|
nested: {
|
|
76
|
+
ProtocolInfoRequest: { fields: {} },
|
|
72
77
|
Ping: {
|
|
73
78
|
fields: {
|
|
74
79
|
message: { type: 'string', id: 1 },
|
|
75
80
|
},
|
|
76
81
|
},
|
|
82
|
+
DeviceInfoGet: { fields: {} },
|
|
77
83
|
FileWrite: { fields: {} },
|
|
78
84
|
Success: {
|
|
79
85
|
fields: {
|
|
@@ -82,8 +88,10 @@ const protocolV2Schema = {
|
|
|
82
88
|
},
|
|
83
89
|
MessageType: {
|
|
84
90
|
values: {
|
|
91
|
+
MessageType_ProtocolInfoRequest: 60200,
|
|
85
92
|
MessageType_Ping: 60206,
|
|
86
93
|
MessageType_Success: 60207,
|
|
94
|
+
MessageType_DeviceInfoGet: 60600,
|
|
87
95
|
MessageType_FileWrite: 60805,
|
|
88
96
|
},
|
|
89
97
|
},
|
|
@@ -95,7 +103,13 @@ const schemas = {
|
|
|
95
103
|
protocolV2: parseConfigure(protocolV2Schema),
|
|
96
104
|
};
|
|
97
105
|
|
|
98
|
-
const createHarness = (
|
|
106
|
+
const createHarness = ({
|
|
107
|
+
deviceName = 'OneKey Pro 2',
|
|
108
|
+
isWritableWithResponse = true,
|
|
109
|
+
}: {
|
|
110
|
+
deviceName?: string;
|
|
111
|
+
isWritableWithResponse?: boolean;
|
|
112
|
+
} = {}) => {
|
|
99
113
|
const uuid = 'rn-pro2-id';
|
|
100
114
|
const sentSeqs: number[] = [];
|
|
101
115
|
let responseSeq = 0;
|
|
@@ -134,15 +148,15 @@ const createHarness = () => {
|
|
|
134
148
|
const writeCharacteristic = {
|
|
135
149
|
uuid: '0002',
|
|
136
150
|
deviceID: uuid,
|
|
137
|
-
isWritableWithResponse
|
|
151
|
+
isWritableWithResponse,
|
|
138
152
|
isWritableWithoutResponse: true,
|
|
139
153
|
writeWithResponse: jest.fn(handleWrite),
|
|
140
154
|
writeWithoutResponse: jest.fn(handleWrite),
|
|
141
155
|
};
|
|
142
156
|
const device = {
|
|
143
157
|
id: uuid,
|
|
144
|
-
name:
|
|
145
|
-
localName:
|
|
158
|
+
name: deviceName,
|
|
159
|
+
localName: deviceName,
|
|
146
160
|
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
147
161
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
148
162
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
@@ -184,7 +198,13 @@ const createHarness = () => {
|
|
|
184
198
|
};
|
|
185
199
|
};
|
|
186
200
|
|
|
187
|
-
const createV1Harness = (
|
|
201
|
+
const createV1Harness = ({
|
|
202
|
+
respondOnWriteCount = 1,
|
|
203
|
+
isWritableWithResponse = true,
|
|
204
|
+
}: {
|
|
205
|
+
respondOnWriteCount?: number | number[];
|
|
206
|
+
isWritableWithResponse?: boolean;
|
|
207
|
+
} = {}) => {
|
|
188
208
|
const uuid = 'rn-classic-id';
|
|
189
209
|
const notifySubscriptionRemovers: jest.Mock[] = [];
|
|
190
210
|
const disconnectSubscriptionRemovers: jest.Mock[] = [];
|
|
@@ -203,20 +223,25 @@ const createV1Harness = () => {
|
|
|
203
223
|
}),
|
|
204
224
|
};
|
|
205
225
|
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
|
+
};
|
|
206
238
|
const writeCharacteristic = {
|
|
207
239
|
uuid: '0002',
|
|
208
240
|
deviceID: uuid,
|
|
209
|
-
isWritableWithResponse
|
|
241
|
+
isWritableWithResponse,
|
|
210
242
|
isWritableWithoutResponse: true,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (writeCount === 1) {
|
|
214
|
-
notifyCallback?.(null, {
|
|
215
|
-
value: Buffer.from('3f23230002000000040a026f6b', 'hex').toString('base64'),
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
return Promise.resolve();
|
|
219
|
-
}),
|
|
243
|
+
writeWithResponse: jest.fn(handleWrite),
|
|
244
|
+
writeWithoutResponse: jest.fn(handleWrite),
|
|
220
245
|
};
|
|
221
246
|
const device = {
|
|
222
247
|
id: uuid,
|
|
@@ -249,6 +274,7 @@ const createV1Harness = () => {
|
|
|
249
274
|
uuid,
|
|
250
275
|
device,
|
|
251
276
|
bleManager,
|
|
277
|
+
writeCharacteristic,
|
|
252
278
|
notifySubscriptionRemovers,
|
|
253
279
|
disconnectSubscriptionRemovers,
|
|
254
280
|
};
|
|
@@ -302,12 +328,81 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
302
328
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
303
329
|
});
|
|
304
330
|
|
|
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
|
+
|
|
305
399
|
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
400
|
+
setPlatformOS('android');
|
|
306
401
|
const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
|
|
307
402
|
createV1Harness();
|
|
308
403
|
const probeProtocolV2 = jest
|
|
309
404
|
.spyOn(transport as any, 'probeProtocolV2')
|
|
310
|
-
.
|
|
405
|
+
.mockImplementation(async () => {
|
|
311
406
|
await (transport as any).releaseNative(uuid, true);
|
|
312
407
|
return false;
|
|
313
408
|
});
|
|
@@ -333,8 +428,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
333
428
|
});
|
|
334
429
|
|
|
335
430
|
test('cleans the rebuilt transport when Protocol V1 fallback also fails', async () => {
|
|
431
|
+
setPlatformOS('android');
|
|
336
432
|
const { transport, uuid, device, bleManager, notifySubscriptionRemovers } = createV1Harness();
|
|
337
|
-
jest.spyOn(transport as any, 'probeProtocolV2').
|
|
433
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockImplementation(async () => {
|
|
338
434
|
await (transport as any).releaseNative(uuid, true);
|
|
339
435
|
return false;
|
|
340
436
|
});
|
|
@@ -353,7 +449,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
353
449
|
});
|
|
354
450
|
|
|
355
451
|
test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
|
|
356
|
-
const { transport, uuid, device } = createV1Harness(
|
|
452
|
+
const { transport, uuid, device } = createV1Harness({
|
|
453
|
+
respondOnWriteCount: Number.POSITIVE_INFINITY,
|
|
454
|
+
});
|
|
357
455
|
|
|
358
456
|
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
359
457
|
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
|
|
@@ -365,17 +463,17 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
365
463
|
});
|
|
366
464
|
|
|
367
465
|
afterEach(() => {
|
|
466
|
+
setPlatformOS('ios');
|
|
368
467
|
resetProtocolV2BleTuning();
|
|
369
468
|
});
|
|
370
469
|
|
|
371
|
-
test('
|
|
470
|
+
test('starts the Protocol V2 sequence with the first Core call on iOS', async () => {
|
|
372
471
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
373
472
|
|
|
374
473
|
await transport.acquire({ uuid });
|
|
375
|
-
await transport.call(uuid, 'Ping', { message: '
|
|
474
|
+
await transport.call(uuid, 'Ping', { message: 'first-core-command' });
|
|
376
475
|
|
|
377
|
-
expect(sentSeqs).toEqual([1
|
|
378
|
-
expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
|
|
476
|
+
expect(sentSeqs).toEqual([1]);
|
|
379
477
|
await transport.release(uuid, true);
|
|
380
478
|
});
|
|
381
479
|
|
|
@@ -386,8 +484,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
386
484
|
harness.setShouldRespond(false);
|
|
387
485
|
|
|
388
486
|
const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
|
|
389
|
-
while (sentSeqs.length <
|
|
390
|
-
await Promise
|
|
487
|
+
while (sentSeqs.length < 1) {
|
|
488
|
+
await new Promise(resolve => {
|
|
489
|
+
setTimeout(resolve, 0);
|
|
490
|
+
});
|
|
391
491
|
}
|
|
392
492
|
await new Promise(resolve => {
|
|
393
493
|
setTimeout(resolve, 0);
|
|
@@ -403,30 +503,124 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
403
503
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
404
504
|
|
|
405
505
|
await transport.acquire({ uuid });
|
|
506
|
+
await transport.call(uuid, 'Ping', { message: 'first-generation' });
|
|
406
507
|
await transport.release(uuid, true);
|
|
407
508
|
await transport.acquire({ uuid });
|
|
509
|
+
await transport.call(uuid, 'Ping', { message: 'second-generation' });
|
|
408
510
|
|
|
409
511
|
expect(sentSeqs).toEqual([1, 2]);
|
|
410
512
|
await transport.release(uuid, true);
|
|
411
513
|
});
|
|
412
514
|
|
|
413
|
-
test('uses
|
|
515
|
+
test('uses withResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
|
|
414
516
|
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
415
517
|
|
|
416
518
|
await transport.acquire({ uuid });
|
|
417
|
-
|
|
519
|
+
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
520
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
418
521
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
419
522
|
|
|
420
|
-
await transport.call(uuid, '
|
|
421
|
-
|
|
422
|
-
|
|
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 });
|
|
423
537
|
|
|
424
538
|
await transport.call(uuid, 'FileWrite', {});
|
|
425
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
539
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
426
540
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
427
541
|
await transport.release(uuid, true);
|
|
428
542
|
});
|
|
429
543
|
|
|
544
|
+
test('falls back to withoutResponse for an iOS Protocol V2 control call when required', async () => {
|
|
545
|
+
const { transport, uuid, writeCharacteristic } = createHarness({
|
|
546
|
+
isWritableWithResponse: false,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
await transport.acquire({ uuid });
|
|
550
|
+
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
551
|
+
|
|
552
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
553
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
554
|
+
await transport.release(uuid, true);
|
|
555
|
+
});
|
|
556
|
+
|
|
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
|
+
|
|
430
624
|
test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
|
|
431
625
|
const harness = createHarness();
|
|
432
626
|
const { transport, uuid, sentSeqs } = harness;
|
|
@@ -434,8 +628,10 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
434
628
|
harness.setShouldRespond(false);
|
|
435
629
|
|
|
436
630
|
const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
|
|
437
|
-
while (sentSeqs.length <
|
|
438
|
-
await Promise
|
|
631
|
+
while (sentSeqs.length < 1) {
|
|
632
|
+
await new Promise(resolve => {
|
|
633
|
+
setTimeout(resolve, 0);
|
|
634
|
+
});
|
|
439
635
|
}
|
|
440
636
|
|
|
441
637
|
const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
|
|
@@ -482,7 +678,6 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
482
678
|
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
483
679
|
|
|
484
680
|
await transport.writeProtocolV2Frame(
|
|
485
|
-
'device-uuid',
|
|
486
681
|
bleTransport,
|
|
487
682
|
new Uint8Array(30),
|
|
488
683
|
context,
|
|
@@ -514,13 +709,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
514
709
|
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
515
710
|
|
|
516
711
|
await expect(
|
|
517
|
-
transport.writeProtocolV2Frame(
|
|
518
|
-
'device-uuid',
|
|
519
|
-
bleTransport,
|
|
520
|
-
new Uint8Array(30),
|
|
521
|
-
context,
|
|
522
|
-
jest.fn()
|
|
523
|
-
)
|
|
712
|
+
transport.writeProtocolV2Frame(bleTransport, new Uint8Array(30), context, jest.fn())
|
|
524
713
|
).rejects.toMatchObject({ errorCode: 205 });
|
|
525
714
|
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
526
715
|
});
|