@onekeyfe/hd-transport-react-native 1.2.0-alpha.65 → 1.2.0-alpha.66
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/BleTransport.d.ts.map +1 -1
- package/dist/index.d.ts +1 -79
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -308
- package/jest.config.js +0 -5
- package/package.json +5 -5
- package/src/BleTransport.ts +6 -0
- package/src/__tests__/BleTransport.test.ts +54 -2
- package/src/__tests__/enumerate.test.ts +132 -0
- package/src/__tests__/protocolV2Link.test.ts +281 -48
- package/src/index.ts +112 -451
- package/src/__tests__/connectTimeout.test.ts +0 -282
- package/src/__tests__/protocolReprobe.test.ts +0 -133
- package/src/__tests__/staleCallTimeout.test.ts +0 -211
- package/src/__tests__/writePacketTimeout.test.ts +0 -306
|
@@ -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
|
};
|
|
@@ -264,6 +290,19 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
264
290
|
).toBeNull();
|
|
265
291
|
});
|
|
266
292
|
|
|
293
|
+
test.each(['status 143', 'status:143', 'status = 143', 'GATT_CONGESTED'])(
|
|
294
|
+
'classifies %s as transient GATT congestion',
|
|
295
|
+
message => {
|
|
296
|
+
expect(getFirmwareUploadWriteRetryType({ message })).toBe('congested');
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
test('handles long uncontrolled status messages without a backtracking regular expression', () => {
|
|
301
|
+
const message = `status${' '.repeat(100_000)}142`;
|
|
302
|
+
|
|
303
|
+
expect(getFirmwareUploadWriteRetryType({ message })).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
|
|
267
306
|
test('keeps another device reader when releasing a device with an active V1 call', async () => {
|
|
268
307
|
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
269
308
|
const activeV1Call = createDeferred<string>();
|
|
@@ -302,12 +341,101 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
302
341
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
303
342
|
});
|
|
304
343
|
|
|
344
|
+
test('uses withResponse for consecutive iOS Protocol V1 control commands without releasing', async () => {
|
|
345
|
+
const { transport, uuid, writeCharacteristic } = createV1Harness({
|
|
346
|
+
respondOnWriteCount: [1, 2],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await expect(transport.acquire({ uuid, expectedProtocol: 'V1' })).resolves.toEqual({
|
|
350
|
+
uuid,
|
|
351
|
+
protocolType: 'V1',
|
|
352
|
+
});
|
|
353
|
+
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
354
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
355
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
356
|
+
|
|
357
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
358
|
+
await expect(transport.call(uuid, 'GetFeatures', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
359
|
+
|
|
360
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
|
|
361
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
362
|
+
expect(releaseNative).not.toHaveBeenCalled();
|
|
363
|
+
await transport.release(uuid, true);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('falls back to withoutResponse for an iOS Protocol V1 control command when required', async () => {
|
|
367
|
+
const { transport, uuid, writeCharacteristic } = createV1Harness({
|
|
368
|
+
isWritableWithResponse: false,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
372
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).resolves.toBeDefined();
|
|
373
|
+
|
|
374
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
375
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
376
|
+
await transport.release(uuid, true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('does not resend a failed iOS Protocol V1 control write without response', async () => {
|
|
380
|
+
const { transport, uuid, writeCharacteristic } = createV1Harness();
|
|
381
|
+
const writeError = new Error('write with response failed');
|
|
382
|
+
|
|
383
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
384
|
+
writeCharacteristic.writeWithResponse.mockRejectedValueOnce(writeError);
|
|
385
|
+
|
|
386
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 50 })).rejects.toMatchObject({
|
|
387
|
+
errorCode: HardwareErrorCode.BleWriteCharacteristicError,
|
|
388
|
+
});
|
|
389
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
390
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
391
|
+
await transport.release(uuid, true);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test('actively probes Protocol V2 on iOS when only a name-derived hint is available', async () => {
|
|
395
|
+
const { transport, uuid, sentSeqs, writeCharacteristic } = createHarness({
|
|
396
|
+
deviceName: 'Pro2 6E9E',
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
400
|
+
uuid,
|
|
401
|
+
protocolType: 'V2',
|
|
402
|
+
});
|
|
403
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
404
|
+
|
|
405
|
+
await expect(
|
|
406
|
+
transport.call(uuid, 'Ping', { message: 'first-core-command' })
|
|
407
|
+
).resolves.toBeDefined();
|
|
408
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
409
|
+
await transport.release(uuid, true);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('falls back to the other active probe on iOS when protocol metadata is absent', async () => {
|
|
413
|
+
const { transport, uuid } = createHarness({ deviceName: 'OneKey' });
|
|
414
|
+
const probeProtocolV1 = jest
|
|
415
|
+
.spyOn(transport as any, 'probeProtocolV1')
|
|
416
|
+
.mockResolvedValue(false);
|
|
417
|
+
const probeProtocolV2 = jest.spyOn(transport as any, 'probeProtocolV2').mockResolvedValue(true);
|
|
418
|
+
|
|
419
|
+
await expect(transport.acquire({ uuid })).resolves.toEqual({
|
|
420
|
+
uuid,
|
|
421
|
+
protocolType: 'V2',
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
expect(probeProtocolV1).toHaveBeenCalledTimes(1);
|
|
425
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
426
|
+
expect(probeProtocolV1.mock.invocationCallOrder[0]).toBeLessThan(
|
|
427
|
+
probeProtocolV2.mock.invocationCallOrder[0]
|
|
428
|
+
);
|
|
429
|
+
await transport.release(uuid, true);
|
|
430
|
+
});
|
|
431
|
+
|
|
305
432
|
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
433
|
+
setPlatformOS('android');
|
|
306
434
|
const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
|
|
307
435
|
createV1Harness();
|
|
308
436
|
const probeProtocolV2 = jest
|
|
309
437
|
.spyOn(transport as any, 'probeProtocolV2')
|
|
310
|
-
.
|
|
438
|
+
.mockImplementation(async () => {
|
|
311
439
|
await (transport as any).releaseNative(uuid, true);
|
|
312
440
|
return false;
|
|
313
441
|
});
|
|
@@ -333,8 +461,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
333
461
|
});
|
|
334
462
|
|
|
335
463
|
test('cleans the rebuilt transport when Protocol V1 fallback also fails', async () => {
|
|
464
|
+
setPlatformOS('android');
|
|
336
465
|
const { transport, uuid, device, bleManager, notifySubscriptionRemovers } = createV1Harness();
|
|
337
|
-
jest.spyOn(transport as any, 'probeProtocolV2').
|
|
466
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockImplementation(async () => {
|
|
338
467
|
await (transport as any).releaseNative(uuid, true);
|
|
339
468
|
return false;
|
|
340
469
|
});
|
|
@@ -353,7 +482,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
353
482
|
});
|
|
354
483
|
|
|
355
484
|
test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
|
|
356
|
-
const { transport, uuid, device } = createV1Harness(
|
|
485
|
+
const { transport, uuid, device } = createV1Harness({
|
|
486
|
+
respondOnWriteCount: Number.POSITIVE_INFINITY,
|
|
487
|
+
});
|
|
357
488
|
|
|
358
489
|
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
359
490
|
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
|
|
@@ -365,29 +496,31 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
365
496
|
});
|
|
366
497
|
|
|
367
498
|
afterEach(() => {
|
|
499
|
+
setPlatformOS('ios');
|
|
368
500
|
resetProtocolV2BleTuning();
|
|
369
501
|
});
|
|
370
502
|
|
|
371
|
-
test('
|
|
503
|
+
test('uses the first Protocol V2 sequence for the first Core call when protocol is known', async () => {
|
|
372
504
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
373
505
|
|
|
374
|
-
await transport.acquire({ uuid });
|
|
375
|
-
await transport.call(uuid, 'Ping', { message: '
|
|
506
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
507
|
+
await transport.call(uuid, 'Ping', { message: 'first-core-command' });
|
|
376
508
|
|
|
377
|
-
expect(sentSeqs).toEqual([1
|
|
378
|
-
expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
|
|
509
|
+
expect(sentSeqs).toEqual([1]);
|
|
379
510
|
await transport.release(uuid, true);
|
|
380
511
|
});
|
|
381
512
|
|
|
382
513
|
test('rejects the active Protocol V2 reader when the current monitor errors', async () => {
|
|
383
514
|
const harness = createHarness();
|
|
384
515
|
const { transport, uuid, sentSeqs } = harness;
|
|
385
|
-
await transport.acquire({ uuid });
|
|
516
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
386
517
|
harness.setShouldRespond(false);
|
|
387
518
|
|
|
388
519
|
const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
|
|
389
|
-
while (sentSeqs.length <
|
|
390
|
-
await Promise
|
|
520
|
+
while (sentSeqs.length < 1) {
|
|
521
|
+
await new Promise(resolve => {
|
|
522
|
+
setTimeout(resolve, 0);
|
|
523
|
+
});
|
|
391
524
|
}
|
|
392
525
|
await new Promise(resolve => {
|
|
393
526
|
setTimeout(resolve, 0);
|
|
@@ -402,40 +535,147 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
402
535
|
test('retains the sequence cursor when a new monitor generation is acquired', async () => {
|
|
403
536
|
const { transport, uuid, sentSeqs } = createHarness();
|
|
404
537
|
|
|
405
|
-
await transport.acquire({ uuid });
|
|
538
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
539
|
+
await transport.call(uuid, 'Ping', { message: 'first-generation' });
|
|
406
540
|
await transport.release(uuid, true);
|
|
407
|
-
await transport.acquire({ uuid });
|
|
541
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
542
|
+
await transport.call(uuid, 'Ping', { message: 'second-generation' });
|
|
408
543
|
|
|
409
544
|
expect(sentSeqs).toEqual([1, 2]);
|
|
410
545
|
await transport.release(uuid, true);
|
|
411
546
|
});
|
|
412
547
|
|
|
413
|
-
test('uses
|
|
548
|
+
test('uses withResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
|
|
414
549
|
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
415
550
|
|
|
416
|
-
await transport.acquire({ uuid });
|
|
417
|
-
|
|
551
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
552
|
+
const releaseNative = jest.spyOn(transport as any, 'releaseNative');
|
|
553
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
418
554
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
419
555
|
|
|
420
|
-
await transport.call(uuid, '
|
|
421
|
-
|
|
422
|
-
|
|
556
|
+
await transport.call(uuid, 'DeviceInfoGet', {});
|
|
557
|
+
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
558
|
+
|
|
559
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(2);
|
|
560
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
561
|
+
expect(releaseNative).not.toHaveBeenCalled();
|
|
562
|
+
|
|
563
|
+
await transport.release(uuid, true);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test('keeps iOS Protocol V2 high-volume calls on withoutResponse', async () => {
|
|
567
|
+
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
568
|
+
|
|
569
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
423
570
|
|
|
424
571
|
await transport.call(uuid, 'FileWrite', {});
|
|
425
|
-
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(
|
|
572
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
573
|
+
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
574
|
+
await transport.release(uuid, true);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test('uses withResponse for an iOS Protocol V2 firmware file write when requested', async () => {
|
|
578
|
+
const { transport, uuid, writeCharacteristic } = createHarness();
|
|
579
|
+
|
|
580
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
581
|
+
|
|
582
|
+
await transport.call(uuid, 'FileWrite', {}, { writeWithResponse: true });
|
|
583
|
+
expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
|
|
584
|
+
expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
|
|
585
|
+
await transport.release(uuid, true);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test('falls back to withoutResponse for an iOS Protocol V2 control call when required', async () => {
|
|
589
|
+
const { transport, uuid, writeCharacteristic } = createHarness({
|
|
590
|
+
isWritableWithResponse: false,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
594
|
+
await transport.call(uuid, 'ProtocolInfoRequest', {});
|
|
595
|
+
|
|
426
596
|
expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
|
|
597
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
427
598
|
await transport.release(uuid, true);
|
|
428
599
|
});
|
|
429
600
|
|
|
601
|
+
test('does not resend a failed iOS Protocol V2 control write without response', async () => {
|
|
602
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
603
|
+
const writeError = new Error('write with response failed');
|
|
604
|
+
const writeWithResponse = jest.fn().mockRejectedValue(writeError);
|
|
605
|
+
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
606
|
+
const context = {
|
|
607
|
+
messageName: 'ProtocolInfoRequest',
|
|
608
|
+
timeoutMs: 1000,
|
|
609
|
+
highVolume: false,
|
|
610
|
+
generation: 1,
|
|
611
|
+
signal: new AbortController().signal,
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
await expect(
|
|
615
|
+
transport.writeProtocolV2Packet(
|
|
616
|
+
{
|
|
617
|
+
writeCharacteristic: {
|
|
618
|
+
isWritableWithResponse: true,
|
|
619
|
+
writeWithResponse,
|
|
620
|
+
writeWithoutResponse,
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
Buffer.from('control').toString('base64'),
|
|
624
|
+
context,
|
|
625
|
+
jest.fn()
|
|
626
|
+
)
|
|
627
|
+
).rejects.toBe(writeError);
|
|
628
|
+
expect(writeWithResponse).toHaveBeenCalledTimes(1);
|
|
629
|
+
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test('paces a one-packet Protocol V2 control write on iOS', async () => {
|
|
633
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
634
|
+
const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
|
|
635
|
+
const bleTransport = {
|
|
636
|
+
mtuSize: 23,
|
|
637
|
+
writeCharacteristic: { writeWithoutResponse },
|
|
638
|
+
};
|
|
639
|
+
const context = {
|
|
640
|
+
messageName: 'ProtocolInfoRequest',
|
|
641
|
+
timeoutMs: 1000,
|
|
642
|
+
highVolume: false,
|
|
643
|
+
generation: 1,
|
|
644
|
+
signal: new AbortController().signal,
|
|
645
|
+
};
|
|
646
|
+
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
647
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
const call = transport.writeProtocolV2Frame(
|
|
651
|
+
bleTransport,
|
|
652
|
+
new Uint8Array(10),
|
|
653
|
+
context,
|
|
654
|
+
jest.fn()
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
await Promise.resolve();
|
|
658
|
+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5);
|
|
659
|
+
expect(writeWithoutResponse).not.toHaveBeenCalled();
|
|
660
|
+
|
|
661
|
+
await call;
|
|
662
|
+
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
663
|
+
} finally {
|
|
664
|
+
setTimeoutSpy.mockRestore();
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
430
668
|
test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
|
|
431
669
|
const harness = createHarness();
|
|
432
670
|
const { transport, uuid, sentSeqs } = harness;
|
|
433
|
-
await transport.acquire({ uuid });
|
|
671
|
+
await transport.acquire({ uuid, expectedProtocol: 'V2' });
|
|
434
672
|
harness.setShouldRespond(false);
|
|
435
673
|
|
|
436
674
|
const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
|
|
437
|
-
while (sentSeqs.length <
|
|
438
|
-
await Promise
|
|
675
|
+
while (sentSeqs.length < 1) {
|
|
676
|
+
await new Promise(resolve => {
|
|
677
|
+
setTimeout(resolve, 0);
|
|
678
|
+
});
|
|
439
679
|
}
|
|
440
680
|
|
|
441
681
|
const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
|
|
@@ -448,7 +688,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
448
688
|
const disconnectListener = jest.fn();
|
|
449
689
|
harness.emitter.on(TRANSPORT_EVENT.DEVICE_DISCONNECT, disconnectListener);
|
|
450
690
|
|
|
451
|
-
await harness.transport.acquire({ uuid: harness.uuid });
|
|
691
|
+
await harness.transport.acquire({ uuid: harness.uuid, expectedProtocol: 'V2' });
|
|
452
692
|
harness.emitDisconnect();
|
|
453
693
|
await harness.transport.disconnect(harness.uuid);
|
|
454
694
|
|
|
@@ -482,7 +722,6 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
482
722
|
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
483
723
|
|
|
484
724
|
await transport.writeProtocolV2Frame(
|
|
485
|
-
'device-uuid',
|
|
486
725
|
bleTransport,
|
|
487
726
|
new Uint8Array(30),
|
|
488
727
|
context,
|
|
@@ -514,13 +753,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
|
514
753
|
configureProtocolV2BleTuning({ iosPacketLength: 20 });
|
|
515
754
|
|
|
516
755
|
await expect(
|
|
517
|
-
transport.writeProtocolV2Frame(
|
|
518
|
-
'device-uuid',
|
|
519
|
-
bleTransport,
|
|
520
|
-
new Uint8Array(30),
|
|
521
|
-
context,
|
|
522
|
-
jest.fn()
|
|
523
|
-
)
|
|
756
|
+
transport.writeProtocolV2Frame(bleTransport, new Uint8Array(30), context, jest.fn())
|
|
524
757
|
).rejects.toMatchObject({ errorCode: 205 });
|
|
525
758
|
expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
526
759
|
});
|