@onekeyfe/hd-transport-react-native 1.2.0-alpha.70 → 1.2.0-alpha.72

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.
@@ -7,6 +7,7 @@ import transportPackage, {
7
7
  import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
8
8
 
9
9
  import ReactNativeBleTransport, {
10
+ BLE_WRITE_PACKET_TIMEOUT_MS,
10
11
  configureProtocolV2BleTuning,
11
12
  getFirmwareUploadWriteRetryType,
12
13
  resetProtocolV2BleTuning,
@@ -32,6 +33,7 @@ jest.mock('react-native-ble-plx', () => ({
32
33
  CharacteristicNotFound: 404,
33
34
  },
34
35
  BleManager: jest.fn(),
36
+ ConnectionPriority: { Balanced: 0, High: 1, LowPower: 2 },
35
37
  ScanMode: { LowLatency: 2 },
36
38
  }));
37
39
 
@@ -157,6 +159,7 @@ const createHarness = ({
157
159
  id: uuid,
158
160
  name: deviceName,
159
161
  localName: deviceName,
162
+ mtu: 247,
160
163
  serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
161
164
  isConnected: jest.fn(() => Promise.resolve(true)),
162
165
  cancelConnection: jest.fn(() => Promise.resolve()),
@@ -164,7 +167,9 @@ const createHarness = ({
164
167
  disconnectCallback = callback;
165
168
  return { remove: jest.fn() };
166
169
  }),
167
- };
170
+ } as any;
171
+ device.requestMTU = jest.fn(() => Promise.resolve(device));
172
+ device.requestConnectionPriority = jest.fn(() => Promise.resolve(device));
168
173
  const bleManager = {
169
174
  devices: jest.fn(() => Promise.resolve([device])),
170
175
  connectedDevices: jest.fn(() => Promise.resolve([])),
@@ -172,18 +177,21 @@ const createHarness = ({
172
177
  };
173
178
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
174
179
  const emitter = new EventEmitter();
180
+ const logger = { debug: jest.fn(), error: jest.fn() };
175
181
  transport.blePlxManager = bleManager;
176
182
  transport.resolveCharacteristics = jest.fn(() =>
177
183
  Promise.resolve({ writeCharacteristic, notifyCharacteristic })
178
184
  );
179
- transport.init({ debug: jest.fn(), error: jest.fn() }, emitter);
185
+ transport.init(logger, emitter);
180
186
  transport.configure(protocolV1Schema);
181
187
  transport.configureProtocolV2(protocolV2Schema);
182
188
 
183
189
  return {
184
190
  transport,
185
191
  emitter,
192
+ logger,
186
193
  uuid,
194
+ device,
187
195
  sentSeqs,
188
196
  writeCharacteristic,
189
197
  setShouldRespond(value: boolean) {
@@ -247,6 +255,7 @@ const createV1Harness = ({
247
255
  id: uuid,
248
256
  name: 'OneKey Classic',
249
257
  localName: 'OneKey Classic',
258
+ mtu: 247,
250
259
  serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
251
260
  isConnected: jest.fn(() => Promise.resolve(true)),
252
261
  cancelConnection: jest.fn(() => Promise.resolve()),
@@ -255,7 +264,8 @@ const createV1Harness = ({
255
264
  disconnectSubscriptionRemovers.push(remove);
256
265
  return { remove };
257
266
  }),
258
- };
267
+ } as any;
268
+ device.requestMTU = jest.fn(() => Promise.resolve(device));
259
269
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
260
270
  const bleManager = {
261
271
  devices: jest.fn(() => Promise.resolve([device])),
@@ -392,7 +402,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
392
402
  });
393
403
 
394
404
  test('actively probes Protocol V2 on iOS when only a name-derived hint is available', async () => {
395
- const { transport, uuid, sentSeqs, writeCharacteristic } = createHarness({
405
+ const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
396
406
  deviceName: 'Pro2 6E9E',
397
407
  });
398
408
 
@@ -400,6 +410,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
400
410
  uuid,
401
411
  protocolType: 'V2',
402
412
  });
413
+ expect(device.requestMTU).toHaveBeenCalledWith(247);
403
414
  expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
404
415
 
405
416
  await expect(
@@ -429,6 +440,114 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
429
440
  await transport.release(uuid, true);
430
441
  });
431
442
 
443
+ test('continues with the current MTU when the connected snapshot refresh fails', async () => {
444
+ const { transport, uuid, device } = createHarness();
445
+ const mtuError = new Error('MTU refresh failed');
446
+ device.requestMTU.mockRejectedValueOnce(mtuError);
447
+
448
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
449
+ uuid,
450
+ protocolType: 'V2',
451
+ });
452
+ expect(device.requestMTU).toHaveBeenCalledTimes(1);
453
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
454
+ await transport.release(uuid, true);
455
+ });
456
+
457
+ test('refreshes a transient bootloader MTU after notifications are ready', async () => {
458
+ const { transport, uuid, device } = createHarness();
459
+ device.mtu = 23;
460
+ device.requestMTU
461
+ .mockResolvedValueOnce(device)
462
+ .mockResolvedValueOnce(device)
463
+ .mockImplementationOnce(() => {
464
+ device.mtu = 247;
465
+ return Promise.resolve(device);
466
+ });
467
+
468
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
469
+ uuid,
470
+ protocolType: 'V2',
471
+ });
472
+ expect(device.requestMTU).toHaveBeenCalledTimes(3);
473
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
474
+ await transport.release(uuid, true);
475
+ });
476
+
477
+ test('continues with a low bootloader MTU when the bounded retry fails', async () => {
478
+ const { transport, uuid, device } = createHarness();
479
+ device.mtu = 23;
480
+ device.requestMTU
481
+ .mockResolvedValueOnce(device)
482
+ .mockResolvedValueOnce(device)
483
+ .mockRejectedValueOnce(new Error('bootloader MTU retry failed'));
484
+
485
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
486
+ uuid,
487
+ protocolType: 'V2',
488
+ });
489
+ expect(device.requestMTU).toHaveBeenCalledTimes(3);
490
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
491
+ await transport.release(uuid, true);
492
+ });
493
+
494
+ test('accepts a stable low MTU without the delayed refresh loop', async () => {
495
+ const { transport, uuid, device } = createHarness();
496
+ device.mtu = 185;
497
+
498
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
499
+ uuid,
500
+ protocolType: 'V2',
501
+ });
502
+ expect(device.requestMTU).toHaveBeenCalledTimes(1);
503
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(185);
504
+ await transport.release(uuid, true);
505
+ });
506
+
507
+ test('continues Protocol V2 probing with a conservative packet size when MTU is unavailable', async () => {
508
+ const { transport, uuid, device, writeCharacteristic } = createHarness();
509
+ device.mtu = undefined;
510
+
511
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
512
+ uuid,
513
+ protocolType: 'V2',
514
+ });
515
+ expect(device.requestMTU).toHaveBeenCalledTimes(3);
516
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBeUndefined();
517
+ expect(writeCharacteristic.writeWithResponse).toHaveBeenCalled();
518
+ await transport.release(uuid, true);
519
+ });
520
+
521
+ test('refreshes an unavailable MTU before a Protocol V2 high-volume write', async () => {
522
+ const { transport, uuid, device, writeCharacteristic } = createHarness();
523
+ device.mtu = undefined;
524
+
525
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
526
+ device.requestMTU.mockImplementationOnce(() => {
527
+ device.mtu = 247;
528
+ return Promise.resolve(device);
529
+ });
530
+
531
+ await expect(transport.call(uuid, 'FileWrite', {})).resolves.toBeDefined();
532
+ expect(device.requestMTU).toHaveBeenCalledTimes(4);
533
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
534
+ await transport.release(uuid, true);
535
+ });
536
+
537
+ test('rejects a Protocol V2 high-volume write when MTU remains unavailable', async () => {
538
+ const { transport, uuid, device, writeCharacteristic } = createHarness();
539
+ device.mtu = undefined;
540
+
541
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
542
+
543
+ await expect(transport.call(uuid, 'FileWrite', {})).rejects.toMatchObject({
544
+ errorCode: HardwareErrorCode.BleConnectedError,
545
+ });
546
+ expect(device.requestMTU).toHaveBeenCalledTimes(4);
547
+ expect(writeCharacteristic.writeWithoutResponse).not.toHaveBeenCalled();
548
+ await transport.release(uuid, true);
549
+ });
550
+
432
551
  test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
433
552
  setPlatformOS('android');
434
553
  const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
@@ -564,16 +683,40 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
564
683
  });
565
684
 
566
685
  test('keeps iOS Protocol V2 high-volume calls on withoutResponse', async () => {
567
- const { transport, uuid, writeCharacteristic } = createHarness();
686
+ const { transport, uuid, logger, writeCharacteristic } = createHarness();
568
687
 
569
688
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
570
689
 
571
690
  await transport.call(uuid, 'FileWrite', {});
572
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
691
+ await transport.call(uuid, 'FileWrite', {});
692
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
573
693
  expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
694
+ expect(
695
+ logger.debug.mock.calls.filter(
696
+ ([message]) =>
697
+ message === '[ReactNativeBleTransport] Protocol V2 high-volume write configured'
698
+ )
699
+ ).toHaveLength(1);
574
700
  await transport.release(uuid, true);
575
701
  });
576
702
 
703
+ test('uses Android 517 MTU and high connection priority during Protocol V2 high-volume calls', async () => {
704
+ setPlatformOS('android');
705
+ const { transport, uuid, device } = createHarness();
706
+
707
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
708
+ expect(device.requestMTU).toHaveBeenCalledWith(517);
709
+
710
+ await transport.call(uuid, 'FileWrite', {});
711
+ await transport.call(uuid, 'FileWrite', {});
712
+
713
+ expect(device.requestConnectionPriority).toHaveBeenCalledTimes(1);
714
+ expect(device.requestConnectionPriority).toHaveBeenCalledWith(1);
715
+
716
+ await transport.release(uuid, true);
717
+ expect(device.requestConnectionPriority).toHaveBeenLastCalledWith(0);
718
+ });
719
+
577
720
  test('uses withResponse for an iOS Protocol V2 firmware file write when requested', async () => {
578
721
  const { transport, uuid, writeCharacteristic } = createHarness();
579
722
 
@@ -606,7 +749,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
606
749
  const context = {
607
750
  messageName: 'ProtocolInfoRequest',
608
751
  timeoutMs: 1000,
609
- highVolume: false,
752
+ highThroughput: false,
610
753
  generation: 1,
611
754
  signal: new AbortController().signal,
612
755
  };
@@ -630,7 +773,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
630
773
  expect(writeWithoutResponse).not.toHaveBeenCalled();
631
774
  });
632
775
 
633
- test('paces a one-packet Protocol V2 control write on iOS', async () => {
776
+ test('does not pace a one-packet Protocol V2 control write on iOS', async () => {
634
777
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
635
778
  const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
636
779
  const bleTransport = {
@@ -640,7 +783,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
640
783
  const context = {
641
784
  messageName: 'ProtocolInfoRequest',
642
785
  timeoutMs: 1000,
643
- highVolume: false,
786
+ highThroughput: false,
644
787
  generation: 1,
645
788
  signal: new AbortController().signal,
646
789
  };
@@ -656,11 +799,11 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
656
799
  jest.fn()
657
800
  );
658
801
 
659
- await Promise.resolve();
660
- expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5);
661
- expect(writeWithoutResponse).not.toHaveBeenCalled();
662
-
663
802
  await call;
803
+ // The only scheduled timer is the per-packet BLE write watchdog: no pacing delay.
804
+ expect(setTimeoutSpy.mock.calls.map(([, timeout]) => timeout)).toEqual([
805
+ BLE_WRITE_PACKET_TIMEOUT_MS,
806
+ ]);
664
807
  expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
665
808
  } finally {
666
809
  setTimeoutSpy.mockRestore();
@@ -716,7 +859,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
716
859
  const context = {
717
860
  messageName: 'Ping',
718
861
  timeoutMs: 1000,
719
- highVolume: false,
862
+ highThroughput: false,
720
863
  generation: 1,
721
864
  signal: new AbortController().signal,
722
865
  };
@@ -749,7 +892,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
749
892
  const context = {
750
893
  messageName: 'FileWrite',
751
894
  timeoutMs: 1000,
752
- highVolume: true,
895
+ highThroughput: true,
753
896
  generation: 1,
754
897
  signal: new AbortController().signal,
755
898
  };
@@ -766,4 +909,41 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
766
909
  ).rejects.toMatchObject({ errorCode: 205 });
767
910
  expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
768
911
  });
912
+
913
+ test('does not apply fixed burst or flush pauses to high-volume Protocol V2 writes', async () => {
914
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
915
+ const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
916
+ const bleTransport = {
917
+ mtuSize: 247,
918
+ writeCharacteristic: { writeWithoutResponse },
919
+ };
920
+ const context = {
921
+ messageName: 'FileWrite',
922
+ timeoutMs: 1000,
923
+ highThroughput: true,
924
+ generation: 1,
925
+ signal: new AbortController().signal,
926
+ };
927
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
928
+
929
+ try {
930
+ await transport.writeProtocolV2Frame(
931
+ 'test-device',
932
+ bleTransport,
933
+ new Uint8Array(600),
934
+ context,
935
+ jest.fn()
936
+ );
937
+
938
+ expect(writeWithoutResponse).toHaveBeenCalledTimes(3);
939
+ // One BLE write watchdog per packet and nothing else: no burst or flush pauses.
940
+ expect(setTimeoutSpy.mock.calls.map(([, timeout]) => timeout)).toEqual([
941
+ BLE_WRITE_PACKET_TIMEOUT_MS,
942
+ BLE_WRITE_PACKET_TIMEOUT_MS,
943
+ BLE_WRITE_PACKET_TIMEOUT_MS,
944
+ ]);
945
+ } finally {
946
+ setTimeoutSpy.mockRestore();
947
+ }
948
+ });
769
949
  });
@@ -1,4 +1,4 @@
1
- import { ANDROID_DEFAULT_MTU, ANDROID_PACKET_LENGTH, IOS_PACKET_LENGTH } from './constants';
1
+ import { ANDROID_PROTOCOL_V2_PACKET_LENGTH, IOS_PROTOCOL_V2_PACKET_LENGTH } from './constants';
2
2
 
3
3
  export type BlePlatform = 'ios' | 'android' | string;
4
4
 
@@ -13,8 +13,8 @@ export function hasWritableCapability(characteristic: BleWriteCapability) {
13
13
 
14
14
  export function resolveProtocolV2PacketCapacity({
15
15
  platform,
16
- iosPacketLength = IOS_PACKET_LENGTH,
17
- androidPacketLength = ANDROID_PACKET_LENGTH,
16
+ iosPacketLength = IOS_PROTOCOL_V2_PACKET_LENGTH,
17
+ androidPacketLength = ANDROID_PROTOCOL_V2_PACKET_LENGTH,
18
18
  mtu,
19
19
  }: {
20
20
  platform: BlePlatform;
@@ -22,14 +22,29 @@ export function resolveProtocolV2PacketCapacity({
22
22
  androidPacketLength?: number;
23
23
  mtu?: number | null;
24
24
  }) {
25
- if (platform === 'ios') {
26
- return iosPacketLength;
27
- }
25
+ const negotiatedMtu =
26
+ typeof mtu === 'number' && Number.isFinite(mtu) && mtu > 3 ? Math.floor(mtu) : 23;
27
+ const payloadLength = negotiatedMtu - 3;
28
+ const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
29
+ return Math.min(configuredPacketLength, payloadLength);
30
+ }
28
31
 
29
- if (platform === 'android') {
30
- const payloadLength = Math.max((mtu ?? ANDROID_DEFAULT_MTU) - 3, 1);
31
- return Math.min(androidPacketLength, payloadLength);
32
- }
32
+ export function shouldRefreshNegotiatedMtu(mtu?: number | null) {
33
+ return typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 23;
34
+ }
33
35
 
34
- return androidPacketLength;
36
+ export function shouldWriteProtocolV2WithResponse({
37
+ platform,
38
+ highThroughput,
39
+ requestedWithResponse,
40
+ characteristic,
41
+ }: {
42
+ platform: BlePlatform;
43
+ highThroughput: boolean;
44
+ requestedWithResponse?: boolean;
45
+ characteristic: BleWriteCapability;
46
+ }) {
47
+ if (!characteristic.isWritableWithResponse) return false;
48
+ if (!characteristic.isWritableWithoutResponse) return true;
49
+ return requestedWithResponse === true || (platform === 'ios' && !highThroughput);
35
50
  }
package/src/constants.ts CHANGED
@@ -2,7 +2,8 @@ import { createKnownBleUuidAliases, matchesKnownBleUuid } from '@onekeyfe/hd-sha
2
2
 
3
3
  export const IOS_PACKET_LENGTH = 128;
4
4
  export const ANDROID_PACKET_LENGTH = 192;
5
- export const ANDROID_DEFAULT_MTU = 23;
5
+ export const IOS_PROTOCOL_V2_PACKET_LENGTH = 244;
6
+ export const ANDROID_PROTOCOL_V2_PACKET_LENGTH = 244;
6
7
 
7
8
  type BluetoothServices = Record<
8
9
  string,