@onekeyfe/hd-transport-react-native 1.2.0-alpha.66 → 1.2.0-alpha.67

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.
@@ -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: deviceName,
159
- localName: deviceName,
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
- writeWithResponse: jest.fn(handleWrite),
244
- writeWithoutResponse: jest.fn(handleWrite),
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
  };
@@ -290,19 +264,6 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
290
264
  ).toBeNull();
291
265
  });
292
266
 
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
-
306
267
  test('keeps another device reader when releasing a device with an active V1 call', async () => {
307
268
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
308
269
  const activeV1Call = createDeferred<string>();
@@ -341,101 +302,12 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
341
302
  expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
342
303
  });
343
304
 
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
-
432
305
  test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
433
- setPlatformOS('android');
434
306
  const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
435
307
  createV1Harness();
436
308
  const probeProtocolV2 = jest
437
309
  .spyOn(transport as any, 'probeProtocolV2')
438
- .mockImplementation(async () => {
310
+ .mockImplementationOnce(async () => {
439
311
  await (transport as any).releaseNative(uuid, true);
440
312
  return false;
441
313
  });
@@ -461,9 +333,8 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
461
333
  });
462
334
 
463
335
  test('cleans the rebuilt transport when Protocol V1 fallback also fails', async () => {
464
- setPlatformOS('android');
465
336
  const { transport, uuid, device, bleManager, notifySubscriptionRemovers } = createV1Harness();
466
- jest.spyOn(transport as any, 'probeProtocolV2').mockImplementation(async () => {
337
+ jest.spyOn(transport as any, 'probeProtocolV2').mockImplementationOnce(async () => {
467
338
  await (transport as any).releaseNative(uuid, true);
468
339
  return false;
469
340
  });
@@ -482,9 +353,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
482
353
  });
483
354
 
484
355
  test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
485
- const { transport, uuid, device } = createV1Harness({
486
- respondOnWriteCount: Number.POSITIVE_INFINITY,
487
- });
356
+ const { transport, uuid, device } = createV1Harness();
488
357
 
489
358
  await transport.acquire({ uuid, expectedProtocol: 'V1' });
490
359
  await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
@@ -496,31 +365,29 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
496
365
  });
497
366
 
498
367
  afterEach(() => {
499
- setPlatformOS('ios');
500
368
  resetProtocolV2BleTuning();
501
369
  });
502
370
 
503
- test('uses the first Protocol V2 sequence for the first Core call when protocol is known', async () => {
371
+ test('keeps the Protocol V2 sequence across probe and the next call', async () => {
504
372
  const { transport, uuid, sentSeqs } = createHarness();
505
373
 
506
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
507
- await transport.call(uuid, 'Ping', { message: 'first-core-command' });
374
+ await transport.acquire({ uuid });
375
+ await transport.call(uuid, 'Ping', { message: 'after-probe' });
508
376
 
509
- expect(sentSeqs).toEqual([1]);
377
+ expect(sentSeqs).toEqual([1, 2]);
378
+ expect(bytesToHex(new Uint8Array([sentSeqs[0], sentSeqs[1]]))).toBe('0102');
510
379
  await transport.release(uuid, true);
511
380
  });
512
381
 
513
382
  test('rejects the active Protocol V2 reader when the current monitor errors', async () => {
514
383
  const harness = createHarness();
515
384
  const { transport, uuid, sentSeqs } = harness;
516
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
385
+ await transport.acquire({ uuid });
517
386
  harness.setShouldRespond(false);
518
387
 
519
388
  const call = transport.call(uuid, 'Ping', { message: 'wait-for-monitor' }, { timeoutMs: 50 });
520
- while (sentSeqs.length < 1) {
521
- await new Promise(resolve => {
522
- setTimeout(resolve, 0);
523
- });
389
+ while (sentSeqs.length < 2) {
390
+ await Promise.resolve();
524
391
  }
525
392
  await new Promise(resolve => {
526
393
  setTimeout(resolve, 0);
@@ -535,147 +402,40 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
535
402
  test('retains the sequence cursor when a new monitor generation is acquired', async () => {
536
403
  const { transport, uuid, sentSeqs } = createHarness();
537
404
 
538
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
539
- await transport.call(uuid, 'Ping', { message: 'first-generation' });
405
+ await transport.acquire({ uuid });
540
406
  await transport.release(uuid, true);
541
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
542
- await transport.call(uuid, 'Ping', { message: 'second-generation' });
407
+ await transport.acquire({ uuid });
543
408
 
544
409
  expect(sentSeqs).toEqual([1, 2]);
545
410
  await transport.release(uuid, true);
546
411
  });
547
412
 
548
- test('uses withResponse for consecutive iOS Protocol V2 control calls without releasing', async () => {
413
+ test('uses withoutResponse for normal and high-volume calls', async () => {
549
414
  const { transport, uuid, writeCharacteristic } = createHarness();
550
415
 
551
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
552
- const releaseNative = jest.spyOn(transport as any, 'releaseNative');
553
- expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
554
- expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
555
-
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' });
570
-
571
- await transport.call(uuid, 'FileWrite', {});
416
+ await transport.acquire({ uuid });
572
417
  expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
573
418
  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
419
 
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', {});
420
+ await transport.call(uuid, 'Ping', { message: 'normal' });
421
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
422
+ expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
595
423
 
424
+ await transport.call(uuid, 'FileWrite', {});
425
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(3);
596
426
  expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
597
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
598
427
  await transport.release(uuid, true);
599
428
  });
600
429
 
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
-
668
430
  test('rejects an active Protocol V2 reader when disconnect resets the link', async () => {
669
431
  const harness = createHarness();
670
432
  const { transport, uuid, sentSeqs } = harness;
671
- await transport.acquire({ uuid, expectedProtocol: 'V2' });
433
+ await transport.acquire({ uuid });
672
434
  harness.setShouldRespond(false);
673
435
 
674
436
  const call = transport.call(uuid, 'Ping', { message: 'disconnect' }, { timeoutMs: 50 });
675
- while (sentSeqs.length < 1) {
676
- await new Promise(resolve => {
677
- setTimeout(resolve, 0);
678
- });
437
+ while (sentSeqs.length < 2) {
438
+ await Promise.resolve();
679
439
  }
680
440
 
681
441
  const rejection = expect(call).rejects.toThrow('React Native BLE transport disconnected');
@@ -688,7 +448,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
688
448
  const disconnectListener = jest.fn();
689
449
  harness.emitter.on(TRANSPORT_EVENT.DEVICE_DISCONNECT, disconnectListener);
690
450
 
691
- await harness.transport.acquire({ uuid: harness.uuid, expectedProtocol: 'V2' });
451
+ await harness.transport.acquire({ uuid: harness.uuid });
692
452
  harness.emitDisconnect();
693
453
  await harness.transport.disconnect(harness.uuid);
694
454
 
@@ -722,6 +482,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
722
482
  configureProtocolV2BleTuning({ iosPacketLength: 20 });
723
483
 
724
484
  await transport.writeProtocolV2Frame(
485
+ 'device-uuid',
725
486
  bleTransport,
726
487
  new Uint8Array(30),
727
488
  context,
@@ -753,7 +514,13 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
753
514
  configureProtocolV2BleTuning({ iosPacketLength: 20 });
754
515
 
755
516
  await expect(
756
- transport.writeProtocolV2Frame(bleTransport, new Uint8Array(30), context, jest.fn())
517
+ transport.writeProtocolV2Frame(
518
+ 'device-uuid',
519
+ bleTransport,
520
+ new Uint8Array(30),
521
+ context,
522
+ jest.fn()
523
+ )
757
524
  ).rejects.toMatchObject({ errorCode: 205 });
758
525
  expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
759
526
  });