@onekeyfe/hd-transport-react-native 1.2.0-alpha.68 → 1.2.0-alpha.69

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/jest.config.js CHANGED
@@ -2,9 +2,4 @@ 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
- },
10
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-react-native",
3
- "version": "1.2.0-alpha.68",
3
+ "version": "1.2.0-alpha.69",
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.68",
24
- "@onekeyfe/hd-shared": "1.2.0-alpha.68",
25
- "@onekeyfe/hd-transport": "1.2.0-alpha.68",
23
+ "@onekeyfe/hd-core": "1.2.0-alpha.69",
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.69",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.69",
26
26
  "@onekeyfe/react-native-ble-utils": "^0.1.6",
27
27
  "react-native-ble-plx": "3.5.1"
28
28
  },
29
- "gitHead": "6e8a931d08768980cfe7fa918bba447ed73e2724"
29
+ "gitHead": "c6baed25917e3c3f027f83dfc23cde85400de558"
30
30
  }
@@ -9,7 +9,7 @@ export default class BleTransport {
9
9
 
10
10
  device: Device;
11
11
 
12
- mtuSize = 23;
12
+ mtuSize: number | undefined;
13
13
 
14
14
  writeCharacteristic: Characteristic;
15
15
 
@@ -1,4 +1,8 @@
1
- import { hasWritableCapability, resolveProtocolV2PacketCapacity } from '../bleStrategy';
1
+ import {
2
+ hasWritableCapability,
3
+ resolveProtocolV2PacketCapacity,
4
+ shouldWriteProtocolV2WithResponse,
5
+ } from '../bleStrategy';
2
6
 
3
7
  describe('React Native BLE strategy', () => {
4
8
  test('accepts writeWithoutResponse-only characteristics', () => {
@@ -10,27 +14,35 @@ describe('React Native BLE strategy', () => {
10
14
  expect(hasWritableCapability(characteristic)).toBe(true);
11
15
  });
12
16
 
13
- test('falls back to Android default ATT payload when MTU is unavailable', () => {
14
- expect(
17
+ test('rejects Protocol V2 packet sizing when MTU is unavailable', () => {
18
+ expect(() =>
15
19
  resolveProtocolV2PacketCapacity({
16
20
  platform: 'android',
17
- androidPacketLength: 192,
18
21
  mtu: null,
19
22
  })
20
- ).toBe(20);
23
+ ).toThrow('Protocol V2 BLE requires a negotiated MTU');
21
24
  });
22
25
 
23
26
  test('caps Android packet length by negotiated MTU payload', () => {
24
27
  expect(
25
28
  resolveProtocolV2PacketCapacity({
26
29
  platform: 'android',
27
- androidPacketLength: 192,
30
+ androidPacketLength: 244,
28
31
  mtu: 100,
29
32
  })
30
33
  ).toBe(97);
31
34
  });
32
35
 
33
- test('keeps iOS packet length controlled by tuning profile', () => {
36
+ test('uses the Android 517 MTU payload when fully negotiated', () => {
37
+ expect(
38
+ resolveProtocolV2PacketCapacity({
39
+ platform: 'android',
40
+ mtu: 517,
41
+ })
42
+ ).toBe(514);
43
+ });
44
+
45
+ test('caps iOS packet length by the system-negotiated write payload', () => {
34
46
  expect(
35
47
  resolveProtocolV2PacketCapacity({
36
48
  platform: 'ios',
@@ -39,4 +51,62 @@ describe('React Native BLE strategy', () => {
39
51
  })
40
52
  ).toBe(244);
41
53
  });
54
+
55
+ test('uses the reported iOS MTU without a compatibility fallback', () => {
56
+ expect(
57
+ resolveProtocolV2PacketCapacity({
58
+ platform: 'ios',
59
+ iosPacketLength: 128,
60
+ mtu: 23,
61
+ })
62
+ ).toBe(20);
63
+ });
64
+
65
+ test('uses a smaller system-negotiated iOS write payload', () => {
66
+ expect(
67
+ resolveProtocolV2PacketCapacity({
68
+ platform: 'ios',
69
+ iosPacketLength: 244,
70
+ mtu: 185,
71
+ })
72
+ ).toBe(182);
73
+ });
74
+
75
+ test('uses withoutResponse for a high-volume write unless explicitly overridden', () => {
76
+ const characteristic = {
77
+ isWritableWithResponse: true,
78
+ isWritableWithoutResponse: true,
79
+ };
80
+
81
+ expect(
82
+ shouldWriteProtocolV2WithResponse({
83
+ platform: 'ios',
84
+ highVolume: true,
85
+ requestedWithResponse: false,
86
+ characteristic,
87
+ })
88
+ ).toBe(false);
89
+ expect(
90
+ shouldWriteProtocolV2WithResponse({
91
+ platform: 'ios',
92
+ highVolume: true,
93
+ requestedWithResponse: true,
94
+ characteristic,
95
+ })
96
+ ).toBe(true);
97
+ });
98
+
99
+ test('falls back to withResponse when withoutResponse is unavailable', () => {
100
+ expect(
101
+ shouldWriteProtocolV2WithResponse({
102
+ platform: 'ios',
103
+ highVolume: true,
104
+ requestedWithResponse: false,
105
+ characteristic: {
106
+ isWritableWithResponse: true,
107
+ isWritableWithoutResponse: false,
108
+ },
109
+ })
110
+ ).toBe(true);
111
+ });
42
112
  });
@@ -32,6 +32,7 @@ jest.mock('react-native-ble-plx', () => ({
32
32
  CharacteristicNotFound: 404,
33
33
  },
34
34
  BleManager: jest.fn(),
35
+ ConnectionPriority: { Balanced: 0, High: 1, LowPower: 2 },
35
36
  ScanMode: { LowLatency: 2 },
36
37
  }));
37
38
 
@@ -157,6 +158,7 @@ const createHarness = ({
157
158
  id: uuid,
158
159
  name: deviceName,
159
160
  localName: deviceName,
161
+ mtu: 247,
160
162
  serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
161
163
  isConnected: jest.fn(() => Promise.resolve(true)),
162
164
  cancelConnection: jest.fn(() => Promise.resolve()),
@@ -164,7 +166,9 @@ const createHarness = ({
164
166
  disconnectCallback = callback;
165
167
  return { remove: jest.fn() };
166
168
  }),
167
- };
169
+ } as any;
170
+ device.requestMTU = jest.fn(() => Promise.resolve(device));
171
+ device.requestConnectionPriority = jest.fn(() => Promise.resolve(device));
168
172
  const bleManager = {
169
173
  devices: jest.fn(() => Promise.resolve([device])),
170
174
  connectedDevices: jest.fn(() => Promise.resolve([])),
@@ -172,18 +176,21 @@ const createHarness = ({
172
176
  };
173
177
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
174
178
  const emitter = new EventEmitter();
179
+ const logger = { debug: jest.fn(), error: jest.fn() };
175
180
  transport.blePlxManager = bleManager;
176
181
  transport.resolveCharacteristics = jest.fn(() =>
177
182
  Promise.resolve({ writeCharacteristic, notifyCharacteristic })
178
183
  );
179
- transport.init({ debug: jest.fn(), error: jest.fn() }, emitter);
184
+ transport.init(logger, emitter);
180
185
  transport.configure(protocolV1Schema);
181
186
  transport.configureProtocolV2(protocolV2Schema);
182
187
 
183
188
  return {
184
189
  transport,
185
190
  emitter,
191
+ logger,
186
192
  uuid,
193
+ device,
187
194
  sentSeqs,
188
195
  writeCharacteristic,
189
196
  setShouldRespond(value: boolean) {
@@ -247,6 +254,7 @@ const createV1Harness = ({
247
254
  id: uuid,
248
255
  name: 'OneKey Classic',
249
256
  localName: 'OneKey Classic',
257
+ mtu: 247,
250
258
  serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
251
259
  isConnected: jest.fn(() => Promise.resolve(true)),
252
260
  cancelConnection: jest.fn(() => Promise.resolve()),
@@ -255,7 +263,8 @@ const createV1Harness = ({
255
263
  disconnectSubscriptionRemovers.push(remove);
256
264
  return { remove };
257
265
  }),
258
- };
266
+ } as any;
267
+ device.requestMTU = jest.fn(() => Promise.resolve(device));
259
268
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
260
269
  const bleManager = {
261
270
  devices: jest.fn(() => Promise.resolve([device])),
@@ -392,7 +401,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
392
401
  });
393
402
 
394
403
  test('actively probes Protocol V2 on iOS when only a name-derived hint is available', async () => {
395
- const { transport, uuid, sentSeqs, writeCharacteristic } = createHarness({
404
+ const { transport, uuid, device, sentSeqs, writeCharacteristic } = createHarness({
396
405
  deviceName: 'Pro2 6E9E',
397
406
  });
398
407
 
@@ -400,6 +409,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
400
409
  uuid,
401
410
  protocolType: 'V2',
402
411
  });
412
+ expect(device.requestMTU).toHaveBeenCalledWith(247);
403
413
  expect(writeCharacteristic.writeWithResponse).toHaveBeenCalledTimes(1);
404
414
 
405
415
  await expect(
@@ -429,6 +439,57 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
429
439
  await transport.release(uuid, true);
430
440
  });
431
441
 
442
+ test('continues with the current MTU when the connected snapshot refresh fails', async () => {
443
+ const { transport, uuid, device } = createHarness();
444
+ const mtuError = new Error('MTU refresh failed');
445
+ device.requestMTU.mockRejectedValueOnce(mtuError);
446
+
447
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
448
+ uuid,
449
+ protocolType: 'V2',
450
+ });
451
+ expect(device.requestMTU).toHaveBeenCalledTimes(1);
452
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
453
+ await transport.release(uuid, true);
454
+ });
455
+
456
+ test('refreshes a transient bootloader MTU after notifications are ready', async () => {
457
+ const { transport, uuid, device } = createHarness();
458
+ device.mtu = 23;
459
+ device.requestMTU
460
+ .mockResolvedValueOnce(device)
461
+ .mockResolvedValueOnce(device)
462
+ .mockImplementationOnce(() => {
463
+ device.mtu = 247;
464
+ return Promise.resolve(device);
465
+ });
466
+
467
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
468
+ uuid,
469
+ protocolType: 'V2',
470
+ });
471
+ expect(device.requestMTU).toHaveBeenCalledTimes(3);
472
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(247);
473
+ await transport.release(uuid, true);
474
+ });
475
+
476
+ test('continues with a low bootloader MTU when the bounded retry fails', async () => {
477
+ const { transport, uuid, device } = createHarness();
478
+ device.mtu = 23;
479
+ device.requestMTU
480
+ .mockResolvedValueOnce(device)
481
+ .mockResolvedValueOnce(device)
482
+ .mockRejectedValueOnce(new Error('bootloader MTU retry failed'));
483
+
484
+ await expect(transport.acquire({ uuid })).resolves.toEqual({
485
+ uuid,
486
+ protocolType: 'V2',
487
+ });
488
+ expect(device.requestMTU).toHaveBeenCalledTimes(3);
489
+ expect((transport as any).getCachedTransport(uuid).mtuSize).toBe(23);
490
+ await transport.release(uuid, true);
491
+ });
492
+
432
493
  test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
433
494
  setPlatformOS('android');
434
495
  const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
@@ -564,14 +625,38 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
564
625
  });
565
626
 
566
627
  test('keeps iOS Protocol V2 high-volume calls on withoutResponse', async () => {
567
- const { transport, uuid, writeCharacteristic } = createHarness();
628
+ const { transport, uuid, logger, writeCharacteristic } = createHarness();
568
629
 
569
630
  await transport.acquire({ uuid, expectedProtocol: 'V2' });
570
631
 
571
632
  await transport.call(uuid, 'FileWrite', {});
572
- expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
633
+ await transport.call(uuid, 'FileWrite', {});
634
+ expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(2);
573
635
  expect(writeCharacteristic.writeWithResponse).not.toHaveBeenCalled();
636
+ expect(
637
+ logger.debug.mock.calls.filter(
638
+ ([message]) =>
639
+ message === '[ReactNativeBleTransport] Protocol V2 high-volume write configured'
640
+ )
641
+ ).toHaveLength(1);
642
+ await transport.release(uuid, true);
643
+ });
644
+
645
+ test('uses Android 517 MTU and high connection priority during Protocol V2 high-volume calls', async () => {
646
+ setPlatformOS('android');
647
+ const { transport, uuid, device } = createHarness();
648
+
649
+ await transport.acquire({ uuid, expectedProtocol: 'V2' });
650
+ expect(device.requestMTU).toHaveBeenCalledWith(517);
651
+
652
+ await transport.call(uuid, 'FileWrite', {});
653
+ await transport.call(uuid, 'FileWrite', {});
654
+
655
+ expect(device.requestConnectionPriority).toHaveBeenCalledTimes(1);
656
+ expect(device.requestConnectionPriority).toHaveBeenCalledWith(1);
657
+
574
658
  await transport.release(uuid, true);
659
+ expect(device.requestConnectionPriority).toHaveBeenLastCalledWith(0);
575
660
  });
576
661
 
577
662
  test('uses withResponse for an iOS Protocol V2 firmware file write when requested', async () => {
@@ -613,7 +698,6 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
613
698
 
614
699
  await expect(
615
700
  transport.writeProtocolV2Packet(
616
- 'test-device',
617
701
  {
618
702
  writeCharacteristic: {
619
703
  isWritableWithResponse: true,
@@ -630,7 +714,7 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
630
714
  expect(writeWithoutResponse).not.toHaveBeenCalled();
631
715
  });
632
716
 
633
- test('paces a one-packet Protocol V2 control write on iOS', async () => {
717
+ test('does not pace a one-packet Protocol V2 control write on iOS', async () => {
634
718
  const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
635
719
  const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
636
720
  const bleTransport = {
@@ -648,19 +732,9 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
648
732
  const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
649
733
 
650
734
  try {
651
- const call = transport.writeProtocolV2Frame(
652
- 'test-device',
653
- bleTransport,
654
- new Uint8Array(10),
655
- context,
656
- jest.fn()
657
- );
658
-
659
- await Promise.resolve();
660
- expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5);
661
- expect(writeWithoutResponse).not.toHaveBeenCalled();
735
+ await transport.writeProtocolV2Frame(bleTransport, new Uint8Array(10), context, jest.fn());
662
736
 
663
- await call;
737
+ expect(setTimeoutSpy).not.toHaveBeenCalled();
664
738
  expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
665
739
  } finally {
666
740
  setTimeoutSpy.mockRestore();
@@ -724,7 +798,6 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
724
798
  configureProtocolV2BleTuning({ iosPacketLength: 20 });
725
799
 
726
800
  await transport.writeProtocolV2Frame(
727
- 'device-uuid',
728
801
  bleTransport,
729
802
  new Uint8Array(30),
730
803
  context,
@@ -756,14 +829,34 @@ describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
756
829
  configureProtocolV2BleTuning({ iosPacketLength: 20 });
757
830
 
758
831
  await expect(
759
- transport.writeProtocolV2Frame(
760
- 'device-uuid',
761
- bleTransport,
762
- new Uint8Array(30),
763
- context,
764
- jest.fn()
765
- )
832
+ transport.writeProtocolV2Frame(bleTransport, new Uint8Array(30), context, jest.fn())
766
833
  ).rejects.toMatchObject({ errorCode: 205 });
767
834
  expect(writeWithoutResponse).toHaveBeenCalledTimes(1);
768
835
  });
836
+
837
+ test('does not apply fixed burst or flush pauses to high-volume Protocol V2 writes', async () => {
838
+ const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
839
+ const writeWithoutResponse = jest.fn().mockResolvedValue(undefined);
840
+ const bleTransport = {
841
+ mtuSize: 247,
842
+ writeCharacteristic: { writeWithoutResponse },
843
+ };
844
+ const context = {
845
+ messageName: 'FileWrite',
846
+ timeoutMs: 1000,
847
+ highVolume: true,
848
+ generation: 1,
849
+ signal: new AbortController().signal,
850
+ };
851
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
852
+
853
+ try {
854
+ await transport.writeProtocolV2Frame(bleTransport, new Uint8Array(600), context, jest.fn());
855
+
856
+ expect(writeWithoutResponse).toHaveBeenCalledTimes(3);
857
+ expect(setTimeoutSpy).not.toHaveBeenCalled();
858
+ } finally {
859
+ setTimeoutSpy.mockRestore();
860
+ }
861
+ });
769
862
  });
@@ -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,27 @@ export function resolveProtocolV2PacketCapacity({
22
22
  androidPacketLength?: number;
23
23
  mtu?: number | null;
24
24
  }) {
25
- if (platform === 'ios') {
26
- return iosPacketLength;
25
+ if (typeof mtu !== 'number' || !Number.isFinite(mtu) || mtu <= 3) {
26
+ throw new Error(`Protocol V2 BLE requires a negotiated MTU, received: ${String(mtu)}`);
27
27
  }
28
28
 
29
- if (platform === 'android') {
30
- const payloadLength = Math.max((mtu ?? ANDROID_DEFAULT_MTU) - 3, 1);
31
- return Math.min(androidPacketLength, payloadLength);
32
- }
29
+ const payloadLength = Math.floor(mtu) - 3;
30
+ const configuredPacketLength = platform === 'ios' ? iosPacketLength : androidPacketLength;
31
+ return Math.min(configuredPacketLength, payloadLength);
32
+ }
33
33
 
34
- return androidPacketLength;
34
+ export function shouldWriteProtocolV2WithResponse({
35
+ platform,
36
+ highVolume,
37
+ requestedWithResponse,
38
+ characteristic,
39
+ }: {
40
+ platform: BlePlatform;
41
+ highVolume: boolean;
42
+ requestedWithResponse?: boolean;
43
+ characteristic: BleWriteCapability;
44
+ }) {
45
+ if (!characteristic.isWritableWithResponse) return false;
46
+ if (!characteristic.isWritableWithoutResponse) return true;
47
+ return requestedWithResponse === true || (platform === 'ios' && !highVolume);
35
48
  }
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 = 514;
6
7
 
7
8
  type BluetoothServices = Record<
8
9
  string,