@onekeyfe/hd-core 1.1.26 → 1.1.27-alpha.31

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.
Files changed (186) hide show
  1. package/__tests__/evmSignTransaction.test.ts +1 -1
  2. package/__tests__/evmSignTypedData.test.ts +1 -1
  3. package/__tests__/protocol-v2.test.ts +1096 -0
  4. package/dist/api/BaseMethod.d.ts +1 -3
  5. package/dist/api/BaseMethod.d.ts.map +1 -1
  6. package/dist/api/DirList.d.ts +10 -0
  7. package/dist/api/DirList.d.ts.map +1 -0
  8. package/dist/api/DirMake.d.ts +9 -0
  9. package/dist/api/DirMake.d.ts.map +1 -0
  10. package/dist/api/DirRemove.d.ts +9 -0
  11. package/dist/api/DirRemove.d.ts.map +1 -0
  12. package/dist/api/FileDelete.d.ts +9 -0
  13. package/dist/api/FileDelete.d.ts.map +1 -0
  14. package/dist/api/FileRead.d.ts +19 -0
  15. package/dist/api/FileRead.d.ts.map +1 -0
  16. package/dist/api/FileWrite.d.ts +23 -0
  17. package/dist/api/FileWrite.d.ts.map +1 -0
  18. package/dist/api/FirmwareUpdateV3.d.ts +1 -0
  19. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  20. package/dist/api/FirmwareUpdateV4.d.ts +32 -0
  21. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -0
  22. package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
  23. package/dist/api/GetPassphraseState.d.ts +6 -1
  24. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  25. package/dist/api/PathInfo.d.ts +9 -0
  26. package/dist/api/PathInfo.d.ts.map +1 -0
  27. package/dist/api/SearchDevices.d.ts +2 -1
  28. package/dist/api/SearchDevices.d.ts.map +1 -1
  29. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  30. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  31. package/dist/api/device/DeviceRebootToBoardloader.d.ts +1 -1
  32. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  33. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  34. package/dist/api/dynex/DnxGetAddress.d.ts.map +1 -1
  35. package/dist/api/dynex/DnxSignTransaction.d.ts.map +1 -1
  36. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +10 -2
  37. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  38. package/dist/api/helpers/batchGetPublickeys.d.ts.map +1 -1
  39. package/dist/api/index.d.ts +26 -0
  40. package/dist/api/index.d.ts.map +1 -1
  41. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +7 -0
  42. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -0
  43. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts +7 -0
  44. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +1 -0
  45. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +6 -0
  46. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +6 -0
  48. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -0
  49. package/dist/api/protocol-v2/DeviceReboot.d.ts +7 -0
  50. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -0
  51. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +7 -0
  52. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +1 -0
  53. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +6 -0
  54. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +1 -0
  55. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts +6 -0
  56. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +1 -0
  57. package/dist/api/protocol-v2/FilesystemFormat.d.ts +6 -0
  58. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -0
  59. package/dist/api/protocol-v2/GetProtoVersion.d.ts +6 -0
  60. package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +1 -0
  61. package/dist/api/protocol-v2/Ping.d.ts +8 -0
  62. package/dist/api/protocol-v2/Ping.d.ts.map +1 -0
  63. package/dist/api/protocol-v2/helpers.d.ts +49 -0
  64. package/dist/api/protocol-v2/helpers.d.ts.map +1 -0
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/data-manager/DataManager.d.ts +7 -4
  67. package/dist/data-manager/DataManager.d.ts.map +1 -1
  68. package/dist/data-manager/MessagesConfig.d.ts +2 -2
  69. package/dist/data-manager/MessagesConfig.d.ts.map +1 -1
  70. package/dist/data-manager/TransportManager.d.ts +5 -4
  71. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  72. package/dist/device/Device.d.ts +5 -3
  73. package/dist/device/Device.d.ts.map +1 -1
  74. package/dist/device/DeviceCommands.d.ts +8 -8
  75. package/dist/device/DeviceCommands.d.ts.map +1 -1
  76. package/dist/device/DeviceConnector.d.ts +2 -1
  77. package/dist/device/DeviceConnector.d.ts.map +1 -1
  78. package/dist/events/ui-request.d.ts +8 -0
  79. package/dist/events/ui-request.d.ts.map +1 -1
  80. package/dist/index.d.ts +198 -23
  81. package/dist/index.js +15726 -806
  82. package/dist/inject.d.ts.map +1 -1
  83. package/dist/protocols/protocol-v2/features.d.ts +57 -0
  84. package/dist/protocols/protocol-v2/features.d.ts.map +1 -0
  85. package/dist/protocols/protocol-v2/firmware.d.ts +12 -0
  86. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -0
  87. package/dist/protocols/protocol-v2/index.d.ts +3 -0
  88. package/dist/protocols/protocol-v2/index.d.ts.map +1 -0
  89. package/dist/types/api/export.d.ts +1 -1
  90. package/dist/types/api/export.d.ts.map +1 -1
  91. package/dist/types/api/firmwareUpdate.d.ts +7 -0
  92. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  93. package/dist/types/api/getPassphraseState.d.ts +7 -1
  94. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  95. package/dist/types/api/index.d.ts +29 -1
  96. package/dist/types/api/index.d.ts.map +1 -1
  97. package/dist/types/api/protocolV2.d.ts +123 -0
  98. package/dist/types/api/protocolV2.d.ts.map +1 -0
  99. package/dist/types/api/searchDevices.d.ts +2 -2
  100. package/dist/types/api/searchDevices.d.ts.map +1 -1
  101. package/dist/types/device.d.ts +1 -1
  102. package/dist/types/device.d.ts.map +1 -1
  103. package/dist/types/params.d.ts +2 -0
  104. package/dist/types/params.d.ts.map +1 -1
  105. package/dist/types/settings.d.ts +1 -1
  106. package/dist/types/settings.d.ts.map +1 -1
  107. package/dist/utils/deviceFeaturesUtils.d.ts +3 -3
  108. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  109. package/dist/utils/deviceInfoUtils.d.ts +1 -0
  110. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  111. package/dist/utils/index.d.ts +1 -1
  112. package/dist/utils/index.d.ts.map +1 -1
  113. package/dist/utils/patch.d.ts +1 -1
  114. package/dist/utils/patch.d.ts.map +1 -1
  115. package/dist/utils/versionUtils.d.ts +1 -1
  116. package/package.json +4 -4
  117. package/src/api/BaseMethod.ts +12 -60
  118. package/src/api/DirList.ts +25 -0
  119. package/src/api/DirMake.ts +20 -0
  120. package/src/api/DirRemove.ts +20 -0
  121. package/src/api/FileDelete.ts +20 -0
  122. package/src/api/FileRead.ts +158 -0
  123. package/src/api/FileWrite.ts +191 -0
  124. package/src/api/FirmwareUpdateV3.ts +21 -4
  125. package/src/api/FirmwareUpdateV4.ts +810 -0
  126. package/src/api/GetOnekeyFeatures.ts +75 -3
  127. package/src/api/GetPassphraseState.ts +14 -2
  128. package/src/api/PathInfo.ts +24 -0
  129. package/src/api/SearchDevices.ts +7 -2
  130. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +25 -9
  131. package/src/api/conflux/ConfluxSignTransaction.ts +5 -2
  132. package/src/api/device/DeviceRebootToBoardloader.ts +10 -1
  133. package/src/api/device/DeviceRebootToBootloader.ts +10 -1
  134. package/src/api/dynex/DnxGetAddress.ts +7 -0
  135. package/src/api/dynex/DnxSignTransaction.ts +7 -0
  136. package/src/api/evm/EVMGetAddress.ts +1 -1
  137. package/src/api/evm/EVMGetPublicKey.ts +1 -1
  138. package/src/api/evm/EVMSignMessage.ts +1 -1
  139. package/src/api/evm/EVMSignTransaction.ts +1 -1
  140. package/src/api/evm/EVMSignTypedData.ts +6 -6
  141. package/src/api/evm/EVMVerifyMessage.ts +1 -1
  142. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -4
  143. package/src/api/helpers/batchGetPublickeys.ts +4 -2
  144. package/src/api/index.ts +28 -0
  145. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +33 -0
  146. package/src/api/protocol-v2/DeviceGetDeviceInfo.ts +35 -0
  147. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +18 -0
  148. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +18 -0
  149. package/src/api/protocol-v2/DeviceReboot.ts +22 -0
  150. package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +27 -0
  151. package/src/api/protocol-v2/FactoryGetDeviceInfo.ts +18 -0
  152. package/src/api/protocol-v2/FilesystemFixPermission.ts +14 -0
  153. package/src/api/protocol-v2/FilesystemFormat.ts +14 -0
  154. package/src/api/protocol-v2/GetProtoVersion.ts +14 -0
  155. package/src/api/protocol-v2/Ping.ts +16 -0
  156. package/src/api/protocol-v2/helpers.ts +138 -0
  157. package/src/api/tron/TronSignMessage.ts +1 -1
  158. package/src/api/xrp/XrpSignTransaction.ts +1 -1
  159. package/src/core/index.ts +31 -5
  160. package/src/data/messages/messages-pro2.json +13106 -0
  161. package/src/data-manager/DataManager.ts +12 -7
  162. package/src/data-manager/MessagesConfig.ts +14 -14
  163. package/src/data-manager/TransportManager.ts +38 -12
  164. package/src/device/Device.ts +73 -9
  165. package/src/device/DeviceCommands.ts +162 -26
  166. package/src/device/DeviceConnector.ts +29 -4
  167. package/src/device/DevicePool.ts +1 -1
  168. package/src/events/ui-request.ts +8 -0
  169. package/src/inject.ts +42 -1
  170. package/src/protocols/protocol-v2/features.ts +267 -0
  171. package/src/protocols/protocol-v2/firmware.ts +26 -0
  172. package/src/protocols/protocol-v2/index.ts +2 -0
  173. package/src/types/api/export.ts +1 -0
  174. package/src/types/api/firmwareUpdate.ts +12 -0
  175. package/src/types/api/getPassphraseState.ts +10 -1
  176. package/src/types/api/index.ts +64 -1
  177. package/src/types/api/protocolV2.ts +221 -0
  178. package/src/types/api/searchDevices.ts +2 -2
  179. package/src/types/device.ts +3 -1
  180. package/src/types/params.ts +7 -0
  181. package/src/types/settings.ts +1 -1
  182. package/src/utils/deviceFeaturesUtils.ts +33 -15
  183. package/src/utils/deviceInfoUtils.ts +14 -5
  184. package/src/utils/index.ts +1 -0
  185. package/__tests__/DeviceCommands.test.ts +0 -99
  186. package/__tests__/evmLedgerLegacySafety.test.ts +0 -261
@@ -0,0 +1,1096 @@
1
+ import JSZip from 'jszip';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
3
+ import { DeviceRebootType } from '@onekeyfe/hd-transport';
4
+
5
+ import ConfluxSignTransaction from '../src/api/conflux/ConfluxSignTransaction';
6
+ import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
7
+ import DnxSignTransaction from '../src/api/dynex/DnxSignTransaction';
8
+ import FileRead from '../src/api/FileRead';
9
+ import FileWrite from '../src/api/FileWrite';
10
+ import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
11
+ import DeviceGetOnboardingStatus from '../src/api/protocol-v2/DeviceGetOnboardingStatus';
12
+ import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
13
+ import FirmwareUpdateV4 from '../src/api/FirmwareUpdateV4';
14
+ import GetOnekeyFeatures from '../src/api/GetOnekeyFeatures';
15
+ import { batchGetPublickeys } from '../src/api/helpers/batchGetPublickeys';
16
+ import KaspaGetAddress from '../src/api/kaspa/KaspaGetAddress';
17
+ import TronSignMessage from '../src/api/tron/TronSignMessage';
18
+ import XrpSignTransaction from '../src/api/xrp/XrpSignTransaction';
19
+ import { DataManager } from '../src/data-manager';
20
+ import { Device } from '../src/device/Device';
21
+ import { UI_REQUEST } from '../src/events/ui-request';
22
+ import { getProtocolV2Features, normalizeProtocolV2Features } from '../src/protocols/protocol-v2';
23
+ import { shouldSkipMethodSupportCheck } from '../src/utils';
24
+
25
+ import type { DeviceCommands } from '../src/device/DeviceCommands';
26
+
27
+ jest.mock('../src/data/config', () => ({
28
+ getSDKVersion: jest.fn(() => '1.0.0'),
29
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
30
+ }));
31
+
32
+ const descriptor = {
33
+ id: 'ble-id',
34
+ path: 'usb-path',
35
+ };
36
+
37
+ describe('Protocol V2 feature adapter', () => {
38
+ test('normalizes Protocol V2 DeviceInfo into existing Features fields', () => {
39
+ const features = normalizeProtocolV2Features(descriptor as any, {
40
+ hw: {
41
+ serial_no: 'PR2SERIAL',
42
+ },
43
+ fw: {
44
+ board: {
45
+ version: '0.1.0',
46
+ hash: [1, 2, 255],
47
+ },
48
+ boot: {
49
+ version: '0.2.0',
50
+ build_id: 'boot-build',
51
+ hash: new Uint8Array([10, 11]),
52
+ },
53
+ app: {
54
+ version: '1.2.3',
55
+ build_id: 'app-build',
56
+ hash: 'abc123',
57
+ },
58
+ },
59
+ bt: {
60
+ app: {
61
+ version: '4.5.6',
62
+ build_id: 'bt-build',
63
+ hash: [12, 13],
64
+ },
65
+ adv_name: 'Pro2 BLE',
66
+ },
67
+ se1: {
68
+ app: {
69
+ version: '7.8.9',
70
+ build_id: 'se-build',
71
+ hash: [14, 15],
72
+ },
73
+ state: 85,
74
+ },
75
+ se2: {
76
+ app: {
77
+ version: '8.0.0',
78
+ },
79
+ state: 0,
80
+ },
81
+ status: {
82
+ label: 'My Pro2',
83
+ language: 'en-US',
84
+ bt_enable: true,
85
+ init_states: false,
86
+ backup_required: true,
87
+ passphrase_protection: true,
88
+ },
89
+ });
90
+
91
+ expect(features.device_id).toBe('PR2SERIAL');
92
+ expect(features.serial_no).toBe('PR2SERIAL');
93
+ expect(features.onekey_serial_no).toBe('PR2SERIAL');
94
+ expect(features.onekey_device_type).toBe('pro2');
95
+ expect(features.major_version).toBe(1);
96
+ expect(features.minor_version).toBe(2);
97
+ expect(features.patch_version).toBe(3);
98
+ expect(features.onekey_firmware_version).toBe('1.2.3');
99
+ expect(features.onekey_firmware_build_id).toBe('app-build');
100
+ expect(features.onekey_firmware_hash).toBe('abc123');
101
+ expect(features.bootloader_version).toBe('0.2.0');
102
+ expect(features.onekey_boot_build_id).toBe('boot-build');
103
+ expect(features.onekey_boot_hash).toBe('0a0b');
104
+ expect(features.onekey_board_hash).toBe('0102ff');
105
+ expect(features.ble_name).toBe('Pro2 BLE');
106
+ expect(features.onekey_ble_version).toBe('4.5.6');
107
+ expect(features.onekey_ble_hash).toBe('0c0d');
108
+ expect(features.onekey_se01_version).toBe('7.8.9');
109
+ expect(features.onekey_se01_hash).toBe('0e0f');
110
+ expect(features.onekey_se01_state).toBe('APP');
111
+ expect(features.onekey_se02_state).toBe('BOOT');
112
+ expect(features.label).toBe('My Pro2');
113
+ expect(features.language).toBe('en-US');
114
+ expect(features.initialized).toBe(false);
115
+ expect(features.needs_backup).toBe(true);
116
+ expect(features.passphrase_protection).toBe(true);
117
+ expect(features.ble_enable).toBe(true);
118
+ });
119
+
120
+ test('marks fallback features as unavailable when DeviceInfo is missing', () => {
121
+ const features = normalizeProtocolV2Features(descriptor as any);
122
+
123
+ expect(features.device_id).toBe('usb-path');
124
+ expect(features.serial_no).toBe('usb-path');
125
+ expect(features.onekey_serial_no).toBe('usb-path');
126
+ expect(features.initialized).toBe(false);
127
+ expect(features.unlocked).toBe(false);
128
+ expect(features.firmware_present).toBe(false);
129
+ });
130
+
131
+ test('bypasses legacy method support checks for Protocol V2 fallback features', () => {
132
+ const features = normalizeProtocolV2Features({
133
+ ...descriptor,
134
+ protocolType: 'V2',
135
+ } as any);
136
+
137
+ expect(shouldSkipMethodSupportCheck(features, 'V2')).toBe(true);
138
+ expect(shouldSkipMethodSupportCheck(features)).toBe(true);
139
+ });
140
+
141
+ test('initializes Protocol V2 features with Ping only while DeviceGetDeviceInfo is disabled', async () => {
142
+ const commands = {
143
+ typedCall: jest
144
+ .fn()
145
+ .mockResolvedValueOnce({ type: 'Success', message: { message: 'pong' } }),
146
+ };
147
+
148
+ const features = await getProtocolV2Features({
149
+ commands: commands as unknown as DeviceCommands,
150
+ descriptor: descriptor as any,
151
+ });
152
+
153
+ expect(features.device_id).toBe('usb-path');
154
+ expect(commands.typedCall).toHaveBeenNthCalledWith(1, 'Ping', 'Success', { message: 'init' });
155
+ expect(commands.typedCall).toHaveBeenCalledTimes(1);
156
+ });
157
+
158
+ test('does not block method-level legacy version checks on Protocol V2', async () => {
159
+ const method = new KaspaGetAddress({
160
+ id: 1,
161
+ payload: {
162
+ method: 'kaspaGetAddress',
163
+ path: "m/44'/111111'/0'/0/0",
164
+ prefix: 'kaspa',
165
+ showOnOneKey: false,
166
+ useTweak: false,
167
+ },
168
+ });
169
+ const typedCall = jest.fn().mockResolvedValue({
170
+ type: 'KaspaAddress',
171
+ message: {
172
+ address: 'kaspa:test-address',
173
+ },
174
+ });
175
+
176
+ method.init();
177
+ method.postMessage = jest.fn();
178
+ (method as any).device = {
179
+ originalDescriptor: { protocolType: 'V2' },
180
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
181
+ commands: { typedCall },
182
+ toMessageObject: jest.fn(() => ({})),
183
+ };
184
+
185
+ await expect(method.run()).resolves.toMatchObject({
186
+ address: 'kaspa:test-address',
187
+ });
188
+ expect(typedCall).toHaveBeenCalledWith('KaspaGetAddress', 'KaspaAddress', expect.any(Object));
189
+ });
190
+
191
+ test('does not block legacy batch public key support checks on Protocol V2', async () => {
192
+ const paths = [{ address_n: [0x8000002c, 0x80000000, 0x80000000] }] as any;
193
+ const typedCall = jest.fn().mockResolvedValue({
194
+ type: 'EcdsaPublicKeys',
195
+ message: {
196
+ root_fingerprint: 123,
197
+ public_keys: [],
198
+ hd_nodes: [{}],
199
+ },
200
+ });
201
+ const device = {
202
+ originalDescriptor: { protocolType: 'V2' },
203
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
204
+ commands: { typedCall },
205
+ };
206
+
207
+ await expect(
208
+ batchGetPublickeys(device as any, paths, 'secp256k1', 0, { includeNode: true })
209
+ ).resolves.toMatchObject({
210
+ root_fingerprint: 123,
211
+ hd_nodes: [{}],
212
+ });
213
+ expect(typedCall).toHaveBeenCalledWith('BatchGetPublickeys', 'EcdsaPublicKeys', {
214
+ paths,
215
+ ecdsa_curve_name: 'secp256k1',
216
+ include_node: true,
217
+ });
218
+ });
219
+
220
+ test('returns Protocol V2 oneKey fields without calling legacy OnekeyGetFeatures', async () => {
221
+ const method = new GetOnekeyFeatures({
222
+ id: 1,
223
+ payload: {
224
+ method: 'getOnekeyFeatures',
225
+ },
226
+ });
227
+ const typedCall = jest.fn();
228
+
229
+ (method as any).device = {
230
+ originalDescriptor: { protocolType: 'V2' },
231
+ commands: { typedCall },
232
+ features: {
233
+ label: 'ignored label',
234
+ onekey_device_type: 'pro2',
235
+ onekey_firmware_version: '1.2.3',
236
+ onekey_firmware_build_id: 'app-build',
237
+ onekey_serial_no: 'PR2SERIAL',
238
+ onekey_ble_name: 'Pro2 BLE',
239
+ },
240
+ };
241
+
242
+ const message = await method.run();
243
+
244
+ expect(typedCall).not.toHaveBeenCalled();
245
+ expect(message).toMatchObject({
246
+ onekey_device_type: 'pro2',
247
+ onekey_firmware_version: '1.2.3',
248
+ onekey_firmware_build_id: 'app-build',
249
+ onekey_serial_no: 'PR2SERIAL',
250
+ onekey_ble_name: 'Pro2 BLE',
251
+ });
252
+ expect(message).not.toHaveProperty('label');
253
+ });
254
+
255
+ test('reuses cached Protocol V2 features after the first initialization', async () => {
256
+ const device = Device.fromDescriptor({
257
+ path: 'usb-path',
258
+ protocolType: 'V2',
259
+ } as any);
260
+ const typedCall = jest
261
+ .fn()
262
+ .mockResolvedValueOnce({ type: 'Success', message: { message: 'init' } });
263
+
264
+ (device as any).commands = { typedCall };
265
+
266
+ await device.initialize();
267
+ await device.initialize();
268
+
269
+ expect(device.features?.device_id).toBe('usb-path');
270
+ expect(typedCall).toHaveBeenCalledTimes(1);
271
+ expect(typedCall).toHaveBeenNthCalledWith(
272
+ 1,
273
+ 'Ping',
274
+ 'Success',
275
+ { message: 'init' },
276
+ {
277
+ timeoutMs: 10000,
278
+ }
279
+ );
280
+ });
281
+ });
282
+
283
+ describe('API compatibility handling', () => {
284
+ test('returns a typed unsupported error for Tron sign message V1 before device binding', () => {
285
+ const method = new TronSignMessage({
286
+ id: 1,
287
+ payload: {
288
+ method: 'tronSignMessage',
289
+ path: "m/44'/195'/0'/0/0",
290
+ messageHex: '0x1234',
291
+ messageType: 'V1',
292
+ },
293
+ });
294
+
295
+ expect(() => method.init()).toThrow(
296
+ expect.objectContaining({
297
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
298
+ })
299
+ );
300
+ });
301
+
302
+ test('accepts string XRP payment amount values', () => {
303
+ const method = new XrpSignTransaction({
304
+ id: 1,
305
+ payload: {
306
+ method: 'xrpSignTransaction',
307
+ path: "m/44'/144'/0'/0/0",
308
+ transaction: {
309
+ fee: '100000',
310
+ flags: 2147483648,
311
+ sequence: 25,
312
+ maxLedgerVersion: 8820051,
313
+ payment: {
314
+ amount: '100000000',
315
+ destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws',
316
+ },
317
+ },
318
+ },
319
+ });
320
+
321
+ expect(() => method.init()).not.toThrow();
322
+ expect(method.params.payment?.amount).toBe('100000000');
323
+ });
324
+
325
+ test('accepts Conflux base32 recipient addresses without hex formatting them', () => {
326
+ const to = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91';
327
+ const method = new ConfluxSignTransaction({
328
+ id: 1,
329
+ payload: {
330
+ method: 'confluxSignTransaction',
331
+ path: "m/44'/503'/0'/0/0",
332
+ transaction: {
333
+ to,
334
+ value: '0x0',
335
+ data: '0x',
336
+ chainId: 1,
337
+ nonce: '0x00',
338
+ epochHeight: '0x00',
339
+ gasLimit: '0x5208',
340
+ storageLimit: '0x5208',
341
+ gasPrice: '0xbebc200',
342
+ },
343
+ },
344
+ });
345
+
346
+ expect(() => method.init()).not.toThrow();
347
+ expect(method.formattedTx?.to).toBe(to);
348
+ });
349
+
350
+ test('returns a typed unsupported error for Dynex signing on Protocol V2', async () => {
351
+ const method = new DnxSignTransaction({
352
+ id: 1,
353
+ payload: {
354
+ method: 'dnxSignTransaction',
355
+ path: "m/44'/29538'/0'/0/0",
356
+ inputs: [
357
+ {
358
+ prevIndex: 1,
359
+ globalIndex: 1,
360
+ txPubkey: '00',
361
+ prevOutPubkey: '00',
362
+ amount: '1',
363
+ },
364
+ ],
365
+ toAddress: 'dnx-address',
366
+ amount: '1',
367
+ fee: '1',
368
+ },
369
+ });
370
+
371
+ method.init();
372
+ (method as any).device = {
373
+ originalDescriptor: { protocolType: 'V2' },
374
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
375
+ };
376
+
377
+ await expect(method.run()).rejects.toMatchObject({
378
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
379
+ });
380
+ });
381
+
382
+ test('returns a typed unsupported error for Dynex address on Protocol V2', async () => {
383
+ const method = new DnxGetAddress({
384
+ id: 1,
385
+ payload: {
386
+ method: 'dnxGetAddress',
387
+ path: "m/44'/29538'/0'/0/0",
388
+ showOnOneKey: false,
389
+ },
390
+ });
391
+
392
+ method.init();
393
+ (method as any).device = {
394
+ originalDescriptor: { protocolType: 'V2' },
395
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
396
+ };
397
+
398
+ await expect(method.run()).rejects.toMatchObject({
399
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
400
+ });
401
+ });
402
+ });
403
+
404
+ describe('Protocol V2 firmware update targets', () => {
405
+ test('keeps Protocol V2 firmware updates off the legacy firmwareUpdateV3 path', async () => {
406
+ const method = new FirmwareUpdateV3({
407
+ id: 1,
408
+ payload: {
409
+ method: 'firmwareUpdateV3',
410
+ },
411
+ });
412
+ (method as any).device = {
413
+ originalDescriptor: { protocolType: 'V2' },
414
+ };
415
+
416
+ await expect(method.run()).rejects.toThrow('firmwareUpdateV4');
417
+ });
418
+
419
+ test('uses Protocol V2 features after BLE final reconnect without legacy Initialize', async () => {
420
+ const method = new FirmwareUpdateV4({
421
+ id: 1,
422
+ payload: {
423
+ method: 'firmwareUpdateV4',
424
+ },
425
+ });
426
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-session' });
427
+ const typedCall = jest.fn().mockImplementation((name: string) => {
428
+ if (name === 'Ping') {
429
+ return Promise.resolve({ type: 'Success', message: { message: 'init' } });
430
+ }
431
+ return Promise.reject(new Error(`unexpected call ${name}`));
432
+ });
433
+ const commands = { typedCall };
434
+
435
+ (method as any).isBleReconnect = jest.fn(() => true);
436
+ (method as any).device = {
437
+ originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
438
+ deviceConnector: { acquire },
439
+ getCommands: () => commands,
440
+ _updateFeatures: jest.fn(),
441
+ };
442
+
443
+ const versions = await (method as any).waitForProtocolV2FinalFeatures();
444
+
445
+ expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2');
446
+ expect(typedCall).toHaveBeenNthCalledWith(
447
+ 1,
448
+ 'Ping',
449
+ 'Success',
450
+ { message: 'init' },
451
+ { timeoutMs: 5000 }
452
+ );
453
+ expect(typedCall).toHaveBeenCalledTimes(1);
454
+ expect(typedCall).not.toHaveBeenCalledWith('Initialize', 'Features', {});
455
+ expect(versions).toEqual({
456
+ bootloaderVersion: '0.0.0',
457
+ bleVersion: '0.0.0',
458
+ firmwareVersion: '0.0.0',
459
+ });
460
+ });
461
+
462
+ test('runs Protocol V2 upload and install without rebooting to bootloader first', async () => {
463
+ const method = new FirmwareUpdateV4({
464
+ id: 1,
465
+ payload: {
466
+ method: 'firmwareUpdateV4',
467
+ },
468
+ });
469
+ method.init();
470
+
471
+ (method as any).device = {
472
+ originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
473
+ features: { capabilities: [] },
474
+ };
475
+ (method as any).prepareResourceBinary = jest.fn().mockResolvedValue(null);
476
+ (method as any).prepareFirmwareAndBleBinary = jest.fn().mockResolvedValue([
477
+ {
478
+ fileName: 'ble-firmware.bin',
479
+ binary: new Uint8Array([1, 2, 3]).buffer,
480
+ },
481
+ ]);
482
+ (method as any).prepareBootloaderBinary = jest.fn().mockResolvedValue(null);
483
+ (method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
484
+ (method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
485
+ (method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
486
+ bootloaderVersion: '0.2.0',
487
+ bleVersion: '4.5.6',
488
+ firmwareVersion: '1.2.3',
489
+ });
490
+ (method as any).protocolV2Reboot = jest.fn();
491
+ method.postTipMessage = jest.fn();
492
+
493
+ await method.run();
494
+
495
+ expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
496
+ resourceBinary: null,
497
+ fwBinaryMap: [
498
+ {
499
+ fileName: 'ble-firmware.bin',
500
+ binary: expect.any(ArrayBuffer),
501
+ },
502
+ ],
503
+ bootloaderBinary: null,
504
+ });
505
+ expect((method as any).protocolV2Reboot).not.toHaveBeenCalledWith(DeviceRebootType.Bootloader);
506
+ expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
507
+ });
508
+
509
+ test('reboots Protocol V2 firmware flow back to normal before final feature polling', async () => {
510
+ const method = new FirmwareUpdateV4({
511
+ id: 1,
512
+ payload: {
513
+ method: 'firmwareUpdateV4',
514
+ },
515
+ });
516
+ method.postTipMessage = jest.fn();
517
+ (method as any).protocolV2Reboot = jest.fn().mockResolvedValue({
518
+ message: 'Device rebooted successfully',
519
+ });
520
+
521
+ await (method as any).exitProtocolV2BootloaderToNormal();
522
+
523
+ expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
524
+ expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
525
+ });
526
+
527
+ test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
528
+ const method = new FirmwareUpdateV4({
529
+ id: 1,
530
+ payload: {
531
+ method: 'firmwareUpdateV4',
532
+ },
533
+ });
534
+ const typedCall = jest
535
+ .fn()
536
+ .mockRejectedValue(
537
+ new Error("The operation couldn't be completed. (MultiplatformBleAdapter.RxError error 6.)")
538
+ );
539
+
540
+ (method as any).device = {
541
+ getCommands: () => ({ typedCall }),
542
+ };
543
+
544
+ await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
545
+ message: 'Device rebooted successfully',
546
+ });
547
+ });
548
+
549
+ test('treats direct disconnect during Protocol V2 normal reboot as expected', async () => {
550
+ const method = new FirmwareUpdateV4({
551
+ id: 1,
552
+ payload: {
553
+ method: 'firmwareUpdateV4',
554
+ },
555
+ });
556
+ const typedCall = jest
557
+ .fn()
558
+ .mockRejectedValue(new Error('Connection error has occured: Device disconnected'));
559
+
560
+ (method as any).device = {
561
+ getCommands: () => ({ typedCall }),
562
+ };
563
+
564
+ await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
565
+ message: 'Device rebooted successfully',
566
+ });
567
+ });
568
+
569
+ test('continues Protocol V2 install polling through temporary expected V2 probe failures', async () => {
570
+ const method = new FirmwareUpdateV4({
571
+ id: 1,
572
+ payload: {
573
+ method: 'firmwareUpdateV4',
574
+ },
575
+ });
576
+ const typedCall = jest
577
+ .fn()
578
+ .mockRejectedValueOnce(
579
+ new Error(
580
+ 'Device protocol mismatch: expected V2, but device did not respond to expected protocol'
581
+ )
582
+ )
583
+ .mockResolvedValueOnce({
584
+ type: 'DeviceFirmwareUpdateStatus',
585
+ message: {
586
+ targets: [{ target_id: 2, status: 0 }],
587
+ },
588
+ });
589
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
590
+
591
+ (method as any).device = {
592
+ getCommands: () => ({ typedCall }),
593
+ };
594
+ (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
595
+ method.postProgressMessage = jest.fn();
596
+
597
+ await (method as any).waitForProtocolV2FirmwareUpdateComplete([
598
+ { target_id: 2, path: 'vol1:ble-firmware.bin' },
599
+ ]);
600
+
601
+ expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
602
+ expect(typedCall).toHaveBeenCalledTimes(2);
603
+ });
604
+
605
+ test('passes resource, bootloader, BLE, SE and app files to DeviceFirmwareUpdate targets', async () => {
606
+ const resourceZip = new JSZip();
607
+ resourceZip.file('icons/home.png', new Uint8Array([1, 2, 3]));
608
+ const resourceBinary = await resourceZip.generateAsync({ type: 'arraybuffer' });
609
+ const method = new FirmwareUpdateV4({
610
+ id: 1,
611
+ payload: {
612
+ method: 'firmwareUpdateV4',
613
+ },
614
+ });
615
+
616
+ const writtenPaths: string[] = [];
617
+ method.postTipMessage = jest.fn();
618
+ (method as any).protocolV2CreateFolder = jest.fn().mockResolvedValue(undefined);
619
+ (method as any).protocolV2CommonUpdateProcess = jest.fn().mockImplementation(params => {
620
+ writtenPaths.push(params.filePath);
621
+ return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
622
+ });
623
+ (method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
624
+ (method as any).waitForProtocolV2FirmwareUpdateComplete = jest
625
+ .fn()
626
+ .mockResolvedValue(undefined);
627
+
628
+ await (method as any).executeProtocolV2Update({
629
+ resourceBinary,
630
+ bootloaderBinary: new Uint8Array([4, 5]).buffer,
631
+ fwBinaryMap: [
632
+ {
633
+ fileName: 'ble-firmware.bin',
634
+ binary: new Uint8Array([6]).buffer,
635
+ },
636
+ {
637
+ fileName: 'se1-firmware.bin',
638
+ binary: new Uint8Array([7]).buffer,
639
+ },
640
+ {
641
+ fileName: 'firmware.bin',
642
+ binary: new Uint8Array([8]).buffer,
643
+ },
644
+ ],
645
+ });
646
+
647
+ expect((method as any).protocolV2CreateFolder).toHaveBeenCalledWith('vol1:res/');
648
+ expect(writtenPaths).toEqual([
649
+ 'vol1:res/home.png',
650
+ 'vol1:bootloader.bin',
651
+ 'vol1:ble-firmware.bin',
652
+ 'vol1:se1-firmware.bin',
653
+ 'vol1:firmware.bin',
654
+ ]);
655
+ expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
656
+ targets: [
657
+ { target_id: 10, path: 'vol1:res/' },
658
+ { target_id: 2, path: 'vol1:bootloader.bin' },
659
+ { target_id: 5, path: 'vol1:ble-firmware.bin' },
660
+ { target_id: 6, path: 'vol1:se1-firmware.bin' },
661
+ { target_id: 3, path: 'vol1:firmware.bin' },
662
+ ],
663
+ });
664
+ expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
665
+ });
666
+
667
+ test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
668
+ const method = new FirmwareUpdateV4({
669
+ id: 1,
670
+ payload: {
671
+ method: 'firmwareUpdateV4',
672
+ },
673
+ });
674
+ const typedCall = jest.fn(
675
+ (
676
+ _name: string,
677
+ _resType: string,
678
+ params: { file: { offset: number; data: { byteLength: number } } }
679
+ ) =>
680
+ Promise.resolve({
681
+ type: 'FilesystemFile',
682
+ message: {
683
+ processed_byte: params.file.offset + params.file.data.byteLength,
684
+ },
685
+ })
686
+ );
687
+
688
+ (method as any).device = {
689
+ getCommands: () => ({ typedCall }),
690
+ };
691
+ method.postProgressMessage = jest.fn();
692
+
693
+ await (method as any).protocolV2CommonUpdateProcess({
694
+ payload: new Uint8Array(4097).buffer,
695
+ filePath: 'vol1:firmware.bin',
696
+ processedSize: 0,
697
+ totalSize: 4097,
698
+ transferStartTime: Date.now() - 1000,
699
+ });
700
+
701
+ const writePayloads = typedCall.mock.calls.map(call => call[2]);
702
+ expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4096]);
703
+ expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([4096, 1]);
704
+ expect(writePayloads.map(payload => payload.overwrite)).toEqual([true, false]);
705
+ expect(writePayloads.every(payload => payload.append === false)).toBe(true);
706
+ expect(method.postProgressMessage).toHaveBeenLastCalledWith(
707
+ 99,
708
+ 'transferData',
709
+ expect.objectContaining({
710
+ transferredBytes: 4097,
711
+ totalBytes: 4097,
712
+ rateBytesPerSecond: expect.any(Number),
713
+ elapsedMs: expect.any(Number),
714
+ })
715
+ );
716
+ });
717
+
718
+ test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
719
+ const method = new FirmwareUpdateV4({
720
+ id: 1,
721
+ payload: {
722
+ method: 'firmwareUpdateV4',
723
+ },
724
+ });
725
+ const typedCall = jest.fn(
726
+ (
727
+ _name: string,
728
+ _resType: string,
729
+ params: { file: { offset: number; data: { byteLength: number } } }
730
+ ) =>
731
+ Promise.resolve({
732
+ type: 'FilesystemFile',
733
+ message: {
734
+ processed_byte: params.file.offset + params.file.data.byteLength,
735
+ },
736
+ })
737
+ );
738
+
739
+ (method as any).params = {
740
+ platform: 'native',
741
+ chunkSize: 4096,
742
+ };
743
+ (method as any).device = {
744
+ getCommands: () => ({ typedCall }),
745
+ };
746
+ method.postProgressMessage = jest.fn();
747
+
748
+ await (method as any).protocolV2CommonUpdateProcess({
749
+ payload: new Uint8Array(1801).buffer,
750
+ filePath: 'vol1:ble-firmware.bin',
751
+ processedSize: 0,
752
+ totalSize: 1801,
753
+ });
754
+
755
+ const writePayloads = typedCall.mock.calls.map(call => call[2]);
756
+ expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
757
+ expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1800, 1]);
758
+ });
759
+
760
+ test('consumes Protocol V2 install progress before final update success', async () => {
761
+ const method = new FirmwareUpdateV4({
762
+ id: 1,
763
+ payload: {
764
+ method: 'firmwareUpdateV4',
765
+ },
766
+ });
767
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: { message: 'ok' } });
768
+
769
+ (method as any).device = {
770
+ getCommands: () => ({ typedCall }),
771
+ };
772
+ method.postProgressMessage = jest.fn();
773
+ method.postTipMessage = jest.fn();
774
+
775
+ await (method as any).protocolV2StartFirmwareUpdate({
776
+ targets: [{ target_id: 0, path: 'vol1:firmware.bin' }],
777
+ });
778
+
779
+ const callOptions = typedCall.mock.calls[0][3];
780
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
781
+ expect(callOptions.intermediateTypes).toEqual(['DeviceFirmwareInstallProgress']);
782
+ callOptions.onIntermediateResponse({
783
+ type: 'DeviceFirmwareInstallProgress',
784
+ message: { target_id: 0, progress: 42 },
785
+ });
786
+
787
+ expect(method.postProgressMessage).toHaveBeenCalledWith(42, 'installingFirmware');
788
+ });
789
+
790
+ test('accepts Protocol V2 firmware update status as start response', async () => {
791
+ const method = new FirmwareUpdateV4({
792
+ id: 1,
793
+ payload: {
794
+ method: 'firmwareUpdateV4',
795
+ },
796
+ });
797
+ const typedCall = jest.fn().mockResolvedValue({
798
+ type: 'DeviceFirmwareUpdateStatus',
799
+ message: { targets: [{ target_id: 0, status: 1 }] },
800
+ });
801
+
802
+ (method as any).device = {
803
+ getCommands: () => ({ typedCall }),
804
+ };
805
+ method.postProgressMessage = jest.fn();
806
+ method.postTipMessage = jest.fn();
807
+
808
+ await (method as any).protocolV2StartFirmwareUpdate({
809
+ targets: [{ target_id: 0, path: 'vol1:firmware.bin' }],
810
+ });
811
+
812
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
813
+ expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
814
+ });
815
+ });
816
+
817
+ describe('Protocol V2 firmware update method', () => {
818
+ test('returns DeviceFirmwareUpdateStatus from low-level update trigger', async () => {
819
+ const method = new DeviceFirmwareUpdate({
820
+ id: 1,
821
+ payload: {
822
+ method: 'deviceFirmwareUpdate',
823
+ path: 'vol0:firmware.bin',
824
+ },
825
+ });
826
+ method.init();
827
+
828
+ const typedCall = jest.fn().mockResolvedValue({
829
+ type: 'DeviceFirmwareUpdateStatus',
830
+ message: { targets: [{ target_id: 0, status: 1 }] },
831
+ });
832
+
833
+ (method as any).device = {
834
+ commands: { typedCall },
835
+ };
836
+
837
+ await expect(method.run()).resolves.toEqual({
838
+ targets: [{ target_id: 0, status: 1 }],
839
+ });
840
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
841
+ });
842
+ });
843
+
844
+ describe('Protocol V2 onboarding status method', () => {
845
+ test('returns DeviceOnboardingStatus from low-level status query', async () => {
846
+ const method = new DeviceGetOnboardingStatus({
847
+ id: 1,
848
+ payload: {
849
+ method: 'deviceGetOnboardingStatus',
850
+ },
851
+ });
852
+ method.init();
853
+
854
+ const typedCall = jest.fn().mockResolvedValue({
855
+ type: 'DeviceOnboardingStatus',
856
+ message: {
857
+ page_index: 2,
858
+ page_count: 5,
859
+ page_name: 'backup',
860
+ },
861
+ });
862
+
863
+ (method as any).device = {
864
+ commands: { typedCall },
865
+ };
866
+
867
+ await expect(method.run()).resolves.toEqual({
868
+ page_index: 2,
869
+ page_count: 5,
870
+ page_name: 'backup',
871
+ });
872
+ expect(typedCall).toHaveBeenCalledWith('DeviceGetOnboardingStatus', 'DeviceOnboardingStatus', {});
873
+ });
874
+ });
875
+
876
+ describe('Protocol V2 file write method', () => {
877
+ test('uses demo-aligned overwrite and append defaults', async () => {
878
+ const typedCall = jest.fn().mockResolvedValue({ message: { processed_byte: 1 } });
879
+ const method = new FileWrite({
880
+ id: 1,
881
+ payload: {
882
+ method: 'fileWrite',
883
+ path: 'vol1:test.bin',
884
+ offset: 1,
885
+ totalSize: 2,
886
+ data: new Uint8Array([1]),
887
+ },
888
+ });
889
+ (method as any).device = { commands: { typedCall } };
890
+ method.postMessage = jest.fn();
891
+
892
+ method.init();
893
+ await method.run();
894
+
895
+ expect(typedCall).toHaveBeenCalledWith('FilesystemFileWrite', 'FilesystemFile', {
896
+ file: {
897
+ path: 'vol1:test.bin',
898
+ offset: 1,
899
+ total_size: 2,
900
+ data: new Uint8Array([1]),
901
+ },
902
+ overwrite: false,
903
+ append: false,
904
+ ui_percentage: 99,
905
+ });
906
+ expect(method.postMessage).toHaveBeenCalledWith({
907
+ event: 'UI_EVENT',
908
+ type: UI_REQUEST.DEVICE_PROGRESS,
909
+ payload: expect.objectContaining({
910
+ progress: 100,
911
+ transferredBytes: 1,
912
+ totalBytes: 1,
913
+ elapsedMs: expect.any(Number),
914
+ }),
915
+ });
916
+ });
917
+
918
+ test('splits data larger than the Protocol V2 file payload limit', async () => {
919
+ const data = new Uint8Array(4097);
920
+ const typedCall = jest.fn().mockResolvedValue({ message: {} });
921
+ const method = new FileWrite({
922
+ id: 1,
923
+ payload: {
924
+ method: 'fileWrite',
925
+ path: 'vol1:test.bin',
926
+ offset: 0,
927
+ totalSize: 4097,
928
+ data,
929
+ },
930
+ });
931
+ (method as any).device = { commands: { typedCall } };
932
+ method.postMessage = jest.fn();
933
+
934
+ method.init();
935
+ const result = await method.run();
936
+
937
+ expect(typedCall).toHaveBeenCalledTimes(2);
938
+ expect(typedCall).toHaveBeenNthCalledWith(1, 'FilesystemFileWrite', 'FilesystemFile', {
939
+ file: {
940
+ path: 'vol1:test.bin',
941
+ offset: 0,
942
+ total_size: 4097,
943
+ data: data.slice(0, 4096),
944
+ },
945
+ overwrite: true,
946
+ append: false,
947
+ ui_percentage: 99,
948
+ });
949
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'FilesystemFileWrite', 'FilesystemFile', {
950
+ file: {
951
+ path: 'vol1:test.bin',
952
+ offset: 4096,
953
+ total_size: 4097,
954
+ data: data.slice(4096),
955
+ },
956
+ overwrite: false,
957
+ append: false,
958
+ ui_percentage: 99,
959
+ });
960
+ expect(result).toMatchObject({
961
+ path: 'vol1:test.bin',
962
+ processed_byte: 4097,
963
+ chunks: 2,
964
+ });
965
+ expect(method.postMessage).toHaveBeenNthCalledWith(1, {
966
+ event: 'UI_EVENT',
967
+ type: UI_REQUEST.DEVICE_PROGRESS,
968
+ payload: expect.objectContaining({
969
+ progress: 99,
970
+ transferredBytes: 4096,
971
+ totalBytes: 4097,
972
+ elapsedMs: expect.any(Number),
973
+ }),
974
+ });
975
+ expect(method.postMessage).toHaveBeenNthCalledWith(2, {
976
+ event: 'UI_EVENT',
977
+ type: UI_REQUEST.DEVICE_PROGRESS,
978
+ payload: expect.objectContaining({
979
+ progress: 100,
980
+ transferredBytes: 4097,
981
+ totalBytes: 4097,
982
+ elapsedMs: expect.any(Number),
983
+ }),
984
+ });
985
+ });
986
+
987
+ test('uses the BLE chunk limit by default in BLE environments', async () => {
988
+ const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native');
989
+ const data = new Uint8Array(1801);
990
+ const typedCall = jest.fn().mockResolvedValue({ message: {} });
991
+ const method = new FileWrite({
992
+ id: 1,
993
+ payload: {
994
+ method: 'fileWrite',
995
+ path: 'vol1:test.bin',
996
+ offset: 0,
997
+ totalSize: 1801,
998
+ data,
999
+ },
1000
+ });
1001
+ (method as any).device = { commands: { typedCall } };
1002
+ method.postMessage = jest.fn();
1003
+
1004
+ try {
1005
+ method.init();
1006
+ await method.run();
1007
+ } finally {
1008
+ getSettingsSpy.mockRestore();
1009
+ }
1010
+
1011
+ expect(typedCall).toHaveBeenCalledTimes(2);
1012
+ expect(typedCall.mock.calls[0][2].file.data.byteLength).toBe(1800);
1013
+ expect(typedCall.mock.calls[1][2].file.offset).toBe(1800);
1014
+ expect(typedCall.mock.calls[1][2].file.data.byteLength).toBe(1);
1015
+ });
1016
+ });
1017
+
1018
+ describe('Protocol V2 file read method', () => {
1019
+ test('reads full file in chunks when read length is 0', async () => {
1020
+ const firstChunk = new Uint8Array(64).fill(1);
1021
+ const typedCall = jest
1022
+ .fn()
1023
+ .mockResolvedValueOnce({ message: { exist: true, size: 65, directory: false } })
1024
+ .mockResolvedValueOnce({ message: { data: firstChunk } })
1025
+ .mockResolvedValueOnce({ message: { data: new Uint8Array([2]) } });
1026
+ const method = new FileRead({
1027
+ id: 1,
1028
+ payload: {
1029
+ method: 'fileRead',
1030
+ path: 'vol1:test.bin',
1031
+ offset: 0,
1032
+ totalSize: 0,
1033
+ chunkLen: 64,
1034
+ },
1035
+ });
1036
+ (method as any).device = { commands: { typedCall } };
1037
+
1038
+ method.init();
1039
+ const result = await method.run();
1040
+
1041
+ expect(typedCall).toHaveBeenNthCalledWith(1, 'FilesystemPathInfoQuery', 'FilesystemPathInfo', {
1042
+ path: 'vol1:test.bin',
1043
+ });
1044
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'FilesystemFileRead', 'FilesystemFile', {
1045
+ file: {
1046
+ path: 'vol1:test.bin',
1047
+ offset: 0,
1048
+ total_size: 0,
1049
+ },
1050
+ chunk_len: 64,
1051
+ ui_percentage: 99,
1052
+ });
1053
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'FilesystemFileRead', 'FilesystemFile', {
1054
+ file: {
1055
+ path: 'vol1:test.bin',
1056
+ offset: 64,
1057
+ total_size: 0,
1058
+ },
1059
+ chunk_len: 1,
1060
+ ui_percentage: 99,
1061
+ });
1062
+ expect(result.data.byteLength).toBe(65);
1063
+ expect(result.data[0]).toBe(1);
1064
+ expect(result.data[64]).toBe(2);
1065
+ expect(result).toMatchObject({
1066
+ path: 'vol1:test.bin',
1067
+ offset: 0,
1068
+ total_size: 65,
1069
+ chunks: 2,
1070
+ });
1071
+ });
1072
+
1073
+ test('decodes protobuf bytes hex string returned by transport', async () => {
1074
+ const typedCall = jest.fn().mockResolvedValue({
1075
+ message: {
1076
+ data: '0102ff',
1077
+ },
1078
+ });
1079
+ const method = new FileRead({
1080
+ id: 1,
1081
+ payload: {
1082
+ method: 'fileRead',
1083
+ path: 'vol0:test.bin',
1084
+ offset: 0,
1085
+ totalSize: 3,
1086
+ chunkLen: 512,
1087
+ },
1088
+ });
1089
+ (method as any).device = { commands: { typedCall } };
1090
+
1091
+ method.init();
1092
+ const result = await method.run();
1093
+
1094
+ expect(result.data).toEqual(new Uint8Array([1, 2, 255]));
1095
+ });
1096
+ });