@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0

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 (204) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +203 -27
  3. package/__tests__/base64Data.test.ts +69 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
  5. package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
  6. package/__tests__/device-connector-protocol.test.ts +21 -0
  7. package/__tests__/device-lifecycle-events.test.ts +427 -7
  8. package/__tests__/device-pool-state.test.ts +25 -0
  9. package/__tests__/device-settings.test.ts +237 -9
  10. package/__tests__/device-state-events.test.ts +2 -2
  11. package/__tests__/device-state-mapper.test.ts +61 -5
  12. package/__tests__/device-utils.test.ts +15 -1
  13. package/__tests__/device-wallet-session-store.test.ts +147 -21
  14. package/__tests__/deviceSettings.test.ts +7 -3
  15. package/__tests__/deviceUploadNft.test.ts +36 -4
  16. package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
  17. package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
  18. package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
  19. package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
  20. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
  21. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
  22. package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
  23. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
  24. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  25. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
  26. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  27. package/__tests__/get-device-state.test.ts +60 -21
  28. package/__tests__/logBlockEvent.test.ts +13 -1
  29. package/__tests__/open-wallet-session.test.ts +127 -1
  30. package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
  31. package/__tests__/protocol-v2-resources.test.ts +185 -88
  32. package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
  33. package/__tests__/protocol-v2.test.ts +2274 -466
  34. package/__tests__/refresh-device-state.test.ts +3 -1
  35. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  36. package/__tests__/search-devices.test.ts +79 -32
  37. package/__tests__/ton-sign-message.test.ts +84 -0
  38. package/dist/api/BaseMethod.d.ts +2 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  41. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  44. package/dist/api/FirmwareUpdateV4.d.ts +22 -7
  45. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  46. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  47. package/dist/api/SearchDevices.d.ts.map +1 -1
  48. package/dist/api/UploadPortfolio.d.ts +1 -1
  49. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  50. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  51. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  52. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  53. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  54. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  55. package/dist/api/device/DeviceVerify.d.ts +1 -0
  56. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  57. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  58. package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
  59. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
  61. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  63. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  64. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
  65. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  66. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
  67. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  68. package/dist/api/firmware/getBinary.d.ts +3 -2
  69. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  70. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  71. package/dist/api/helpers/base64Data.d.ts +16 -0
  72. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  73. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
  74. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  75. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  76. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  78. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
  79. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  81. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  82. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  83. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  84. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  85. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  86. package/dist/core/index.d.ts +6 -1
  87. package/dist/core/index.d.ts.map +1 -1
  88. package/dist/data-manager/DataManager.d.ts +4 -3
  89. package/dist/data-manager/DataManager.d.ts.map +1 -1
  90. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  91. package/dist/device/Device.d.ts +16 -3
  92. package/dist/device/Device.d.ts.map +1 -1
  93. package/dist/device/DeviceCommands.d.ts +4 -4
  94. package/dist/device/DeviceCommands.d.ts.map +1 -1
  95. package/dist/device/DeviceConnector.d.ts +1 -1
  96. package/dist/device/DeviceConnector.d.ts.map +1 -1
  97. package/dist/device/DevicePool.d.ts.map +1 -1
  98. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  99. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  100. package/dist/events/logBlockEvent.d.ts.map +1 -1
  101. package/dist/events/ui-request.d.ts +3 -0
  102. package/dist/events/ui-request.d.ts.map +1 -1
  103. package/dist/index.d.ts +54 -84
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +2333 -1515
  106. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  107. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  108. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  109. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  110. package/dist/protocols/protocol-v2/resources.d.ts +12 -7
  111. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  112. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
  113. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  114. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  115. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  116. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  117. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  118. package/dist/types/api/protocolV2.d.ts +1 -1
  119. package/dist/types/api/protocolV2.d.ts.map +1 -1
  120. package/dist/types/device.d.ts.map +1 -1
  121. package/dist/types/settings.d.ts +3 -31
  122. package/dist/types/settings.d.ts.map +1 -1
  123. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  124. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  125. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  126. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  127. package/dist/utils/deviceSettings.d.ts +1 -0
  128. package/dist/utils/deviceSettings.d.ts.map +1 -1
  129. package/dist/utils/index.d.ts +1 -1
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/patch.d.ts +1 -1
  132. package/dist/utils/patch.d.ts.map +1 -1
  133. package/dist/utils/pro2Nft.d.ts +7 -0
  134. package/dist/utils/pro2Nft.d.ts.map +1 -1
  135. package/package.json +6 -4
  136. package/src/api/BaseMethod.ts +8 -1
  137. package/src/api/CheckAllFirmwareRelease.ts +8 -5
  138. package/src/api/CheckBLEFirmwareRelease.ts +2 -2
  139. package/src/api/CheckBootloaderRelease.ts +3 -2
  140. package/src/api/CheckFirmwareRelease.ts +2 -2
  141. package/src/api/FirmwareUpdateV2.ts +130 -69
  142. package/src/api/FirmwareUpdateV3.ts +29 -19
  143. package/src/api/FirmwareUpdateV4.ts +966 -743
  144. package/src/api/OpenWalletSession.ts +39 -9
  145. package/src/api/PromptWebDeviceAccess.ts +2 -2
  146. package/src/api/SearchDevices.ts +6 -3
  147. package/src/api/UploadPortfolio.ts +9 -2
  148. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  149. package/src/api/device/DeviceChangePin.ts +4 -1
  150. package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
  151. package/src/api/device/DeviceRebootToBootloader.ts +3 -1
  152. package/src/api/device/DeviceSettings.ts +34 -20
  153. package/src/api/device/DeviceVerify.ts +25 -0
  154. package/src/api/device/DeviceWipe.ts +4 -1
  155. package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
  156. package/src/api/firmware/FirmwareHostBinding.ts +10 -0
  157. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
  158. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
  159. package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
  160. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
  161. package/src/api/firmware/protocolV2Release.ts +7 -2
  162. package/src/api/helpers/base64Data.ts +85 -0
  163. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  164. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
  165. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  166. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  167. package/src/api/protocol-v2/DeviceReboot.ts +5 -0
  168. package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
  169. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
  170. package/src/api/protocol-v2/helpers.ts +8 -0
  171. package/src/api/ton/TonSignMessage.ts +5 -3
  172. package/src/core/index.ts +264 -55
  173. package/src/data/messages/messages-protocol-v2.json +140 -13
  174. package/src/data-manager/DataManager.ts +46 -39
  175. package/src/data-manager/TransportManager.ts +3 -1
  176. package/src/device/Device.ts +210 -44
  177. package/src/device/DeviceCommands.ts +27 -18
  178. package/src/device/DeviceConnector.ts +17 -3
  179. package/src/device/DevicePool.ts +9 -2
  180. package/src/device/DeviceStateMapper.ts +30 -24
  181. package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
  182. package/src/events/logBlockEvent.ts +14 -1
  183. package/src/events/ui-request.ts +3 -0
  184. package/src/index.ts +0 -10
  185. package/src/protocols/protocol-v2/features.ts +22 -5
  186. package/src/protocols/protocol-v2/index.ts +2 -0
  187. package/src/protocols/protocol-v2/resources.ts +110 -147
  188. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
  189. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  190. package/src/types/api/firmwareUpdate.ts +1 -1
  191. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  192. package/src/types/api/protocolV2.ts +1 -1
  193. package/src/types/device.ts +2 -1
  194. package/src/types/settings.ts +6 -34
  195. package/src/utils/deviceFeaturesCompat.ts +9 -4
  196. package/src/utils/deviceFeaturesUtils.ts +4 -0
  197. package/src/utils/deviceInfoUtils.ts +9 -2
  198. package/src/utils/deviceSettings.ts +11 -4
  199. package/src/utils/index.ts +1 -0
  200. package/src/utils/pro2Nft.ts +31 -8
  201. package/__tests__/firmware-memory-host.test.ts +0 -112
  202. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  203. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  204. package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
@@ -95,7 +95,8 @@ describe('AllNetworkGetAddressBase tracing', () => {
95
95
  'hidden-state',
96
96
  false,
97
97
  undefined,
98
- false
98
+ false,
99
+ undefined
99
100
  );
100
101
  expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
101
102
  expect(typedCall).not.toHaveBeenCalled();
@@ -173,7 +174,13 @@ describe('AllNetworkGetAddressBase tracing', () => {
173
174
  0
174
175
  );
175
176
 
176
- expect(checkPassphraseStateSafety).toHaveBeenCalledWith('hidden-state', false, undefined, true);
177
+ expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
178
+ 'hidden-state',
179
+ false,
180
+ undefined,
181
+ true,
182
+ undefined
183
+ );
177
184
  expect(calls).toEqual(['resume-cardano-session', 'run-cardano-method']);
178
185
  expect(typedCall).not.toHaveBeenCalled();
179
186
  });
@@ -212,7 +219,10 @@ describe('AllNetworkGetAddressBase tracing', () => {
212
219
  bundle: [],
213
220
  },
214
221
  });
215
- method.protocolV2UnlockContext = { preflightCompleted: true };
222
+ method.protocolV2UnlockContext = {
223
+ preflightCompleted: true,
224
+ preflightMainPinSelected: true,
225
+ };
216
226
  const typedCall = jest.fn();
217
227
  method.device = {
218
228
  checkPassphraseStateSafety,
@@ -250,7 +260,13 @@ describe('AllNetworkGetAddressBase tracing', () => {
250
260
  0
251
261
  );
252
262
 
253
- expect(checkPassphraseStateSafety).toHaveBeenCalledWith(undefined, true, undefined, false);
263
+ expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
264
+ undefined,
265
+ true,
266
+ undefined,
267
+ false,
268
+ true
269
+ );
254
270
  expect(calls).toEqual(['resume-standard-session', 'run-chain-method']);
255
271
  expect(typedCall).not.toHaveBeenCalled();
256
272
  });
@@ -19,12 +19,28 @@ const createCommands = () => {
19
19
  };
20
20
 
21
21
  describe('DeviceCommands failure mapping', () => {
22
- it('logs passphrase call request and response without exposing the passphrase', async () => {
22
+ it.each(['File already exists', 'NFT already exists'])(
23
+ 'maps duplicate file response "%s" to FileAlreadyExists',
24
+ async message => {
25
+ const commands = createCommands();
26
+ await expect(
27
+ commands._filterCommonTypes(
28
+ {
29
+ type: 'Failure',
30
+ message: { code: 'Failure_DataError', message },
31
+ } as any,
32
+ 'NFTUpdate'
33
+ )
34
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.FileAlreadyExists });
35
+ }
36
+ );
37
+
38
+ it('logs passphrase request and canonical response without exposing the passphrase', async () => {
23
39
  const commands = createCommands();
24
- const requestLog = getLogger(LoggerNames.DeviceCommands);
25
- const responseLog = getLogger(LoggerNames.Core);
26
- requestLog.messages.length = 0;
27
- responseLog.messages.length = 0;
40
+ const log = getLogger(LoggerNames.DeviceCommands);
41
+ const coreLog = getLogger(LoggerNames.Core);
42
+ log.messages.length = 0;
43
+ coreLog.messages.length = 0;
28
44
  commands.mainId = 'main-id';
29
45
  commands.transport = {
30
46
  call: jest.fn().mockResolvedValue({
@@ -36,13 +52,13 @@ describe('DeviceCommands failure mapping', () => {
36
52
  } as any;
37
53
 
38
54
  await expect(
39
- commands.call('DeviceSessionAskPassphrase', {
55
+ commands._commonCall('DeviceSessionAskPassphrase', {
40
56
  passphrase: 'hidden-wallet-secret',
41
57
  on_device: false,
42
58
  })
43
59
  ).resolves.toMatchObject({ type: 'Success' });
44
60
 
45
- expect(requestLog.messages.at(-1)?.message).toEqual([
61
+ expect(log.messages.at(-2)?.message).toEqual([
46
62
  '[DeviceCommands] [call] Sending',
47
63
  'DeviceSessionAskPassphrase',
48
64
  {
@@ -50,21 +66,82 @@ describe('DeviceCommands failure mapping', () => {
50
66
  on_device: false,
51
67
  },
52
68
  ]);
53
- expect(responseLog.messages.at(-1)?.message).toEqual([
54
- '[DeviceCommands] [call] Received',
55
- 'Success',
69
+ expect(log.messages.at(-1)?.message).toEqual([
70
+ '_filterCommonTypes: ',
71
+ {
72
+ request: 'DeviceSessionAskPassphrase',
73
+ response: {
74
+ type: 'Success',
75
+ message: {
76
+ message: 'Passphrase accepted',
77
+ },
78
+ },
79
+ },
80
+ ]);
81
+ expect(
82
+ coreLog.messages.some(entry => entry.message[0] === '[DeviceCommands] [call] Received')
83
+ ).toBe(false);
84
+ expect(JSON.stringify([...log.messages, ...coreLog.messages])).not.toContain(
85
+ 'hidden-wallet-secret'
86
+ );
87
+ });
88
+
89
+ it('redacts passphrases from transport errors logged through Core', async () => {
90
+ const commands = createCommands();
91
+ const log = getLogger(LoggerNames.DeviceCommands);
92
+ const coreLog = getLogger(LoggerNames.Core);
93
+ log.messages.length = 0;
94
+ coreLog.messages.length = 0;
95
+ const transportError = Object.assign(new Error('transport failed'), {
96
+ response: {
97
+ data: {
98
+ passphrase: 'hidden-wallet-secret',
99
+ },
100
+ },
101
+ });
102
+ commands.mainId = 'main-id';
103
+ commands.transport = {
104
+ call: jest.fn().mockRejectedValue(transportError),
105
+ } as any;
106
+
107
+ await expect(
108
+ commands._commonCall('DeviceSessionAskPassphrase', {
109
+ passphrase: 'hidden-wallet-secret',
110
+ on_device: false,
111
+ })
112
+ ).rejects.toBe(transportError);
113
+
114
+ expect(coreLog.messages.at(-1)?.message).toEqual([
115
+ '[DeviceCommands] [call] Received error',
56
116
  {
57
- message: 'Passphrase accepted',
117
+ request: 'DeviceSessionAskPassphrase',
118
+ errorCode: undefined,
119
+ response: {
120
+ passphrase: '[REDACTED]',
121
+ },
58
122
  },
59
123
  ]);
60
- expect(JSON.stringify([...requestLog.messages, ...responseLog.messages])).not.toContain(
124
+ expect(JSON.stringify([...log.messages, ...coreLog.messages])).not.toContain(
61
125
  'hidden-wallet-secret'
62
126
  );
63
127
  });
64
128
 
65
- it('logs DeviceStatus response fields without exposing the device ID', async () => {
129
+ it('preserves the Protocol V2 peer-removed pairing error code', async () => {
66
130
  const commands = createCommands();
67
- const log = getLogger(LoggerNames.Core);
131
+ const transportError = Object.assign(new Error('Peer removed pairing information'), {
132
+ errorCode: HardwareErrorCode.BlePeerRemovedPairingInformation,
133
+ });
134
+ commands.mainId = 'main-id';
135
+ commands.transport = {
136
+ call: jest.fn().mockRejectedValue(transportError),
137
+ } as any;
138
+
139
+ await expect(commands._commonCall('DeviceInfoGet', {})).rejects.toBe(transportError);
140
+ });
141
+
142
+ it('logs canonical DeviceStatus response fields without exposing the device ID', async () => {
143
+ const commands = createCommands();
144
+ const log = getLogger(LoggerNames.DeviceCommands);
68
145
  log.messages.length = 0;
69
146
  commands.mainId = 'main-id';
70
147
  commands.transport = {
@@ -82,22 +159,27 @@ describe('DeviceCommands failure mapping', () => {
82
159
  }),
83
160
  } as any;
84
161
 
85
- await expect(commands.call('DeviceStatusGet', {})).resolves.toMatchObject({
162
+ await expect(commands._commonCall('DeviceStatusGet', {})).resolves.toMatchObject({
86
163
  type: 'DeviceStatus',
87
164
  });
88
165
 
89
166
  const receivedLog = log.messages.at(-1)?.message;
90
167
  expect(receivedLog).toEqual([
91
- '[DeviceCommands] [call] Received',
92
- 'DeviceStatus',
168
+ '_filterCommonTypes: ',
93
169
  {
94
- device_id: '[REDACTED]',
95
- unlocked: true,
96
- init_states: true,
97
- backup_required: false,
98
- passphrase_enabled: true,
99
- attach_to_pin_enabled: false,
100
- unlocked_by_attach_to_pin: false,
170
+ request: 'DeviceStatusGet',
171
+ response: {
172
+ type: 'DeviceStatus',
173
+ message: {
174
+ device_id: '[REDACTED]',
175
+ unlocked: true,
176
+ init_states: true,
177
+ backup_required: false,
178
+ passphrase_enabled: true,
179
+ attach_to_pin_enabled: false,
180
+ unlocked_by_attach_to_pin: false,
181
+ },
182
+ },
101
183
  },
102
184
  ]);
103
185
  expect(JSON.stringify(receivedLog)).not.toContain('sensitive-device-id');
@@ -125,6 +207,59 @@ describe('DeviceCommands failure mapping', () => {
125
207
  expect(JSON.stringify(log.messages)).not.toContain('secret-wallet-address');
126
208
  });
127
209
 
210
+ it('logs the sanitized DeviceInfo response payload', async () => {
211
+ const commands = createCommands();
212
+ const log = getLogger(LoggerNames.DeviceCommands);
213
+ log.messages.length = 0;
214
+
215
+ await expect(
216
+ commands._filterCommonTypes(
217
+ {
218
+ type: 'DeviceInfo',
219
+ message: {
220
+ protocol_version: 2,
221
+ hw: {
222
+ Device_type: 7,
223
+ hardware_version: '1.0.0',
224
+ },
225
+ main_mcu: {
226
+ application: {
227
+ version: '5.0.0',
228
+ build_id: '20260811',
229
+ hash: 'firmware-hash',
230
+ },
231
+ },
232
+ },
233
+ } as any,
234
+ 'DeviceInfoGet'
235
+ )
236
+ ).resolves.toMatchObject({ type: 'DeviceInfo' });
237
+
238
+ expect(log.messages.at(-1)?.message).toEqual([
239
+ '_filterCommonTypes: ',
240
+ {
241
+ request: 'DeviceInfoGet',
242
+ response: {
243
+ type: 'DeviceInfo',
244
+ message: {
245
+ protocol_version: 2,
246
+ hw: {
247
+ Device_type: 7,
248
+ hardware_version: '1.0.0',
249
+ },
250
+ main_mcu: {
251
+ application: {
252
+ version: '5.0.0',
253
+ build_id: '20260811',
254
+ hash: 'firmware-hash',
255
+ },
256
+ },
257
+ },
258
+ },
259
+ },
260
+ ]);
261
+ });
262
+
128
263
  it('logs the sanitized DeviceFirmwareUpdateStatus response payload', async () => {
129
264
  const commands = createCommands();
130
265
  const log = getLogger(LoggerNames.DeviceCommands);
@@ -170,6 +305,32 @@ describe('DeviceCommands failure mapping', () => {
170
305
  ]);
171
306
  });
172
307
 
308
+ it.each(['Failure_ProcessError', 5])(
309
+ 'maps USB-priority link disabled to public BLE error 723 for code %s',
310
+ async code => {
311
+ const commands = createCommands();
312
+
313
+ await expect(
314
+ commands._filterCommonTypes(
315
+ {
316
+ type: 'Failure',
317
+ message: {
318
+ code,
319
+ message: ' link disabled ',
320
+ },
321
+ } as any,
322
+ 'DeviceInfo'
323
+ )
324
+ ).rejects.toMatchObject({
325
+ errorCode: HardwareErrorCode.BleUnavailableWhileUsbConnected,
326
+ params: {
327
+ failureCode: code,
328
+ firmwareMessage: ' link disabled ',
329
+ },
330
+ });
331
+ }
332
+ );
333
+
173
334
  it('maps an invalid DeviceSessionGet resume to WalletSessionInvalid', async () => {
174
335
  const commands = createCommands();
175
336
 
@@ -337,7 +498,7 @@ describe('DeviceCommands failure mapping', () => {
337
498
  });
338
499
  });
339
500
 
340
- it.each(['Cancelled on device', 'Confirm dismissed'])(
501
+ it.each(['Cancelled on device', 'Confirm dismissed', 'Update cancelled'])(
341
502
  'maps legacy Protocol V2 cancellation message "%s" without a subcode',
342
503
  async message => {
343
504
  const commands = createCommands();
@@ -561,17 +722,22 @@ describe('DeviceCommands failure mapping', () => {
561
722
  });
562
723
 
563
724
  describe('DeviceCommands Protocol V2 interactive response compatibility', () => {
564
- it('still auto-acks ButtonRequest when no hardware UI listener is registered', async () => {
725
+ it('auto-acks Pro2 ButtonRequest and registers Cancel instead of Initialize', async () => {
565
726
  const commands = createCommands();
566
727
  const emit = jest.fn();
728
+ const cancelDevice = jest.spyOn(commands, 'cancelDevice').mockResolvedValue(undefined);
729
+ const cancelDeviceOnOneKeyDevice = jest
730
+ .spyOn(commands, 'cancelDeviceOnOneKeyDevice')
731
+ .mockResolvedValue(undefined);
567
732
  const commonCall = jest.fn().mockResolvedValue({
568
733
  type: 'Success',
569
734
  message: {},
570
735
  });
736
+ const setCancelableAction = jest.fn();
571
737
  commands.device = {
572
738
  ...commands.device,
573
739
  getCurrentDeviceType: jest.fn(() => 'pro2'),
574
- setCancelableAction: jest.fn(),
740
+ setCancelableAction,
575
741
  emit,
576
742
  listenerCount: jest.fn(() => 0),
577
743
  } as any;
@@ -593,6 +759,9 @@ describe('DeviceCommands Protocol V2 interactive response compatibility', () =>
593
759
  expect.objectContaining({ code: 'ButtonRequest_PinEntry' })
594
760
  );
595
761
  expect(commonCall).toHaveBeenCalledWith('ButtonAck', {}, undefined);
762
+ await setCancelableAction.mock.calls[0][0]();
763
+ expect(cancelDevice).toHaveBeenCalledTimes(1);
764
+ expect(cancelDeviceOnOneKeyDevice).not.toHaveBeenCalled();
596
765
  });
597
766
 
598
767
  it('rejects PassphraseRequest when the Pro2 UI listener is not registered', async () => {
@@ -644,7 +813,13 @@ describe('DeviceCommands cancellation', () => {
644
813
  try {
645
814
  const commands = createCommands();
646
815
  commands.disposed = false;
816
+ commands.mainId = 'main-id';
647
817
  commands.callPromise = new Promise(() => {});
818
+ const disconnect = jest.fn().mockResolvedValue(undefined);
819
+ commands.transport = {
820
+ name: 'ReactNativeBleTransport',
821
+ disconnect,
822
+ } as any;
648
823
  const dispose = jest.fn().mockResolvedValue(undefined);
649
824
  commands.dispose = dispose;
650
825
 
@@ -654,6 +829,7 @@ describe('DeviceCommands cancellation', () => {
654
829
 
655
830
  await expect(cancellation).resolves.toBeUndefined();
656
831
  expect(dispose).toHaveBeenCalledWith(true);
832
+ expect(disconnect).toHaveBeenCalledWith('main-id');
657
833
  expect(commands.callPromise).toBeUndefined();
658
834
  } finally {
659
835
  jest.useRealTimers();
@@ -0,0 +1,69 @@
1
+ import { Buffer } from 'buffer';
2
+ import { encode as encodeJpeg } from 'jpeg-js';
3
+
4
+ import { decodeCanonicalBase64, decodeJpegBase64ToRgba } from '../src/api/helpers/base64Data';
5
+
6
+ const createJpegBase64 = (width: number, height: number) => {
7
+ const rgba = new Uint8Array(width * height * 4).fill(0xff);
8
+ return encodeJpeg({ width, height, data: rgba }, 80).data.toString('base64');
9
+ };
10
+
11
+ describe('Base64 resource data', () => {
12
+ test('decodes canonical Base64 to a detached Uint8Array', () => {
13
+ const decoded = decodeCanonicalBase64({
14
+ value: 'AQID',
15
+ parameterName: 'packageBase64',
16
+ maxBytes: 3,
17
+ });
18
+
19
+ expect(decoded).toBeInstanceOf(Uint8Array);
20
+ expect(Array.from(decoded)).toEqual([1, 2, 3]);
21
+ });
22
+
23
+ test.each(['', 'not-base64', 'AB==', 'data:image/jpeg;base64,AQID'])(
24
+ 'rejects non-canonical Base64 input %p',
25
+ value => {
26
+ expect(() =>
27
+ decodeCanonicalBase64({ value, parameterName: 'data', maxBytes: 1024 })
28
+ ).toThrow();
29
+ }
30
+ );
31
+
32
+ test('rejects oversized Base64 before decoding', () => {
33
+ const value = Buffer.alloc(64 * 1024 + 17).toString('base64');
34
+ expect(() =>
35
+ decodeCanonicalBase64({ value, parameterName: 'data', maxBytes: 64 * 1024 })
36
+ ).toThrow('maximum supported size');
37
+ });
38
+
39
+ test('decodes and validates a JPEG with the expected dimensions', () => {
40
+ const decoded = decodeJpegBase64ToRgba({
41
+ jpegBase64: createJpegBase64(2, 1),
42
+ parameterName: 'jpegBase64',
43
+ expectedWidth: 2,
44
+ expectedHeight: 1,
45
+ });
46
+
47
+ expect(decoded).toMatchObject({ width: 2, height: 1 });
48
+ expect(decoded.data).toHaveLength(8);
49
+ });
50
+
51
+ test('rejects non-JPEG bytes and unexpected dimensions', () => {
52
+ expect(() =>
53
+ decodeJpegBase64ToRgba({
54
+ jpegBase64: 'AQID',
55
+ parameterName: 'jpegBase64',
56
+ expectedWidth: 2,
57
+ expectedHeight: 1,
58
+ })
59
+ ).toThrow('JPEG image');
60
+ expect(() =>
61
+ decodeJpegBase64ToRgba({
62
+ jpegBase64: createJpegBase64(2, 1),
63
+ parameterName: 'jpegBase64',
64
+ expectedWidth: 1,
65
+ expectedHeight: 1,
66
+ })
67
+ ).toThrow('1x1');
68
+ });
69
+ });
@@ -33,8 +33,6 @@ const validComponentIntegrity = {
33
33
  const release: IFirmwareReleaseInfo = {
34
34
  required: true,
35
35
  version: [2, 0, 0],
36
- url: 'https://example.com/application-p1.bin',
37
- fingerprint: 'release-fingerprint',
38
36
  changelog: {
39
37
  'zh-CN': '更新',
40
38
  'en-US': 'Update',
@@ -328,15 +326,16 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
328
326
  status: 'required',
329
327
  resourceStatus: 'unknown',
330
328
  resourceArchive: resourceSource,
331
- targetsToUpdate: ['boot', 'app_v1'],
329
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
332
330
  firmwareUpdatePlan: {
333
331
  executor: 'v4',
334
332
  platform: 'web',
335
333
  artifacts: [
336
334
  { artifactId: 'component:boot', target: 'boot' },
337
335
  { artifactId: 'component:app_v1', target: 'app_v1' },
336
+ { artifactId: 'resource:archive', target: 'resource' },
338
337
  ],
339
- targetsToUpdate: ['boot', 'app_v1'],
338
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
340
339
  },
341
340
  firmware: {
342
341
  status: 'required',
@@ -377,7 +376,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
377
376
  jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
378
377
 
379
378
  await expect(method.run()).resolves.toMatchObject({
380
- targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
379
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
381
380
  firmwareUpdatePlan: {
382
381
  executor: 'v4',
383
382
  deviceIdentity: 'pro2-device-id',
@@ -387,8 +386,9 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
387
386
  { artifactId: 'component:app_v1', target: 'app_v1' },
388
387
  { artifactId: 'component:se02', target: 'se02' },
389
388
  { artifactId: 'component:se03', target: 'se03' },
389
+ { artifactId: 'resource:archive', target: 'resource' },
390
390
  ],
391
- targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03'],
391
+ targetsToUpdate: ['boot', 'app_v1', 'se02', 'se03', 'resource'],
392
392
  },
393
393
  });
394
394
  });
@@ -582,12 +582,12 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
582
582
  jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
583
583
 
584
584
  await expect(method.run()).resolves.toMatchObject({
585
- targetsToUpdate: ['se01', 'se02'],
585
+ targetsToUpdate: ['se01', 'se02', 'resource'],
586
586
  firmwareUpdatePlan: {
587
587
  executor: 'v4',
588
588
  deviceModel: 'neo',
589
589
  platform: 'native',
590
- targetsToUpdate: ['se01', 'se02'],
590
+ targetsToUpdate: ['se01', 'se02', 'resource'],
591
591
  },
592
592
  });
593
593
  });
@@ -618,18 +618,10 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
618
618
  });
619
619
  });
620
620
 
621
- test('adds a Protocol V2 resource target only when explicitly forced', async () => {
622
- const currentRelease: IFirmwareReleaseInfo = {
621
+ test('selects the Protocol V2 resource archive using the DeviceState identity', async () => {
622
+ const releaseWithResources: IFirmwareReleaseInfo = {
623
623
  ...release,
624
- required: false,
625
- installOrder: ['applicationP1'],
626
- components: {
627
- applicationP1: {
628
- target: 'APPLICATION_P1',
629
- url: 'https://example.com/application-p1.okpkg',
630
- version: [1, 0, 0],
631
- },
632
- },
624
+ resources: { source: resourceSource },
633
625
  };
634
626
  const method = new CheckAllFirmwareRelease({
635
627
  id: 1,
@@ -643,39 +635,98 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
643
635
  method.device = {
644
636
  isProtocolV2: () => true,
645
637
  features: {
646
- deviceType: 'pro2',
647
- serialNo: 'pro2-device-id',
648
638
  firmwareVersion: '1.0.0',
639
+ serialNo: 'device-without-model-prefix',
649
640
  },
650
641
  getDeviceState: jest.fn().mockResolvedValue({
651
642
  identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
652
643
  status: { mode: 'normal' },
653
- versions: { ...currentVersions, applicationP1: '1.0.0' },
644
+ versions: currentVersions,
654
645
  }),
655
646
  } as unknown as CheckAllFirmwareRelease['device'];
656
- jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
657
- jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
647
+ const getLatestReleaseSpy = jest
648
+ .spyOn(DataManager, 'getFirmwareLatestRelease')
649
+ .mockImplementation(features =>
650
+ features.deviceType === 'pro2' ? releaseWithResources : undefined
651
+ );
658
652
 
659
653
  await expect(method.run()).resolves.toMatchObject({
660
- status: 'valid',
661
- hasUpgrade: true,
662
- targetsToUpdate: ['resource'],
654
+ deviceType: 'pro2',
655
+ resourceArchive: resourceSource,
656
+ targetsToUpdate: expect.arrayContaining(['resource']),
663
657
  firmwareUpdatePlan: {
664
658
  executor: 'v4',
665
- platform: 'desktop',
666
- artifacts: [
667
- {
668
- artifactId: 'resource:archive',
669
- target: 'resource',
670
- role: 'resourceBundle',
671
- container: 'zip',
672
- },
673
- ],
674
- targetsToUpdate: ['resource'],
659
+ deviceModel: 'pro2',
660
+ targetsToUpdate: expect.arrayContaining(['resource']),
675
661
  },
676
662
  });
663
+ expect(getLatestReleaseSpy).toHaveBeenCalledWith(
664
+ expect.objectContaining({ deviceType: 'pro2' }),
665
+ EFirmwareType.Universal
666
+ );
677
667
  });
678
668
 
669
+ test.each(['resource', 'boot_resources'] as const)(
670
+ 'allows an explicitly forced Protocol V2 resource-only target through %s',
671
+ async forceTarget => {
672
+ const currentRelease: IFirmwareReleaseInfo = {
673
+ ...release,
674
+ required: false,
675
+ installOrder: ['applicationP1'],
676
+ components: {
677
+ applicationP1: {
678
+ target: 'APPLICATION_P1',
679
+ url: 'https://example.com/application-p1.okpkg',
680
+ version: [1, 0, 0],
681
+ },
682
+ },
683
+ };
684
+ const method = new CheckAllFirmwareRelease({
685
+ id: 1,
686
+ payload: {
687
+ method: 'checkAllFirmwareRelease',
688
+ platform: 'desktop',
689
+ protocolV2ForceUpdateTargets: [forceTarget],
690
+ },
691
+ });
692
+ method.init();
693
+ method.device = {
694
+ isProtocolV2: () => true,
695
+ features: {
696
+ deviceType: 'pro2',
697
+ serialNo: 'pro2-device-id',
698
+ firmwareVersion: '1.0.0',
699
+ },
700
+ getDeviceState: jest.fn().mockResolvedValue({
701
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
702
+ status: { mode: 'normal' },
703
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
704
+ }),
705
+ } as unknown as CheckAllFirmwareRelease['device'];
706
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(currentRelease);
707
+ jest.spyOn(DataManager, 'getProtocolV2ResourceSource').mockReturnValue(resourceSource);
708
+
709
+ await expect(method.run()).resolves.toMatchObject({
710
+ status: 'valid',
711
+ hasUpgrade: true,
712
+ targetsToUpdate: ['resource'],
713
+ firmwareUpdatePlan: {
714
+ executor: 'v4',
715
+ platform: 'desktop',
716
+ artifacts: [
717
+ {
718
+ artifactId: 'resource:archive',
719
+ target: 'resource',
720
+ role: 'resourceBundle',
721
+ container: 'zip',
722
+ },
723
+ ],
724
+ targetsToUpdate: ['resource'],
725
+ },
726
+ });
727
+ }
728
+ );
729
+
679
730
  test('requests and compares firmware hashes only when explicitly enabled', async () => {
680
731
  const packageSet: IFirmwareReleaseInfo = {
681
732
  ...release,
@@ -752,7 +803,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
752
803
  protocol: 'V2',
753
804
  resourceStatus: 'unknown',
754
805
  resourceArchive: resourceSource,
755
- targetsToUpdate: ['boot', 'app_v1'],
806
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
756
807
  });
757
808
  expect(typedCall).not.toHaveBeenCalled();
758
809
  }