@onekeyfe/hd-core 1.2.0-alpha.99 → 1.2.1-alpha.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 (200) 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 +48 -2
  5. package/__tests__/check-firmware-release-protocol-v2.test.ts +82 -13
  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 +13 -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 +63 -0
  21. package/__tests__/firmware-update/firmware-update-plan.test.ts +59 -55
  22. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
  23. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
  24. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +979 -0
  25. package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
  26. package/__tests__/get-device-state.test.ts +60 -21
  27. package/__tests__/logBlockEvent.test.ts +13 -1
  28. package/__tests__/open-wallet-session.test.ts +127 -1
  29. package/__tests__/protocol-v2-firmware-targets.test.ts +23 -0
  30. package/__tests__/protocol-v2-resources.test.ts +185 -88
  31. package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
  32. package/__tests__/protocol-v2.test.ts +2199 -486
  33. package/__tests__/refresh-device-state.test.ts +3 -1
  34. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  35. package/__tests__/search-devices.test.ts +79 -32
  36. package/__tests__/ton-sign-message.test.ts +84 -0
  37. package/dist/api/BaseMethod.d.ts +2 -1
  38. package/dist/api/BaseMethod.d.ts.map +1 -1
  39. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  40. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  41. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  42. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV4.d.ts +20 -6
  44. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  45. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  46. package/dist/api/SearchDevices.d.ts.map +1 -1
  47. package/dist/api/UploadPortfolio.d.ts +1 -1
  48. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  49. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  50. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  51. package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
  52. package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
  53. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  54. package/dist/api/device/DeviceVerify.d.ts +1 -0
  55. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  56. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  57. package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
  58. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  59. package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
  63. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  64. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
  65. package/dist/api/firmware/getBinary.d.ts +3 -2
  66. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  67. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  68. package/dist/api/helpers/base64Data.d.ts +16 -0
  69. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  70. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
  71. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  72. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  73. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  74. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  75. package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
  76. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  77. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  78. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  79. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  80. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  81. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  82. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  83. package/dist/core/index.d.ts +6 -1
  84. package/dist/core/index.d.ts.map +1 -1
  85. package/dist/data-manager/DataManager.d.ts +4 -3
  86. package/dist/data-manager/DataManager.d.ts.map +1 -1
  87. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  88. package/dist/device/Device.d.ts +16 -3
  89. package/dist/device/Device.d.ts.map +1 -1
  90. package/dist/device/DeviceCommands.d.ts +4 -4
  91. package/dist/device/DeviceCommands.d.ts.map +1 -1
  92. package/dist/device/DeviceConnector.d.ts +1 -1
  93. package/dist/device/DeviceConnector.d.ts.map +1 -1
  94. package/dist/device/DevicePool.d.ts.map +1 -1
  95. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  96. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  97. package/dist/events/logBlockEvent.d.ts.map +1 -1
  98. package/dist/events/ui-request.d.ts +3 -0
  99. package/dist/events/ui-request.d.ts.map +1 -1
  100. package/dist/index.d.ts +54 -84
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +2150 -1469
  103. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  104. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  105. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  106. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  107. package/dist/protocols/protocol-v2/resources.d.ts +12 -7
  108. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  109. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
  110. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  111. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  112. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  113. package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
  114. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  115. package/dist/types/api/protocolV2.d.ts +1 -1
  116. package/dist/types/api/protocolV2.d.ts.map +1 -1
  117. package/dist/types/device.d.ts.map +1 -1
  118. package/dist/types/settings.d.ts +3 -31
  119. package/dist/types/settings.d.ts.map +1 -1
  120. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  121. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  122. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  123. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  124. package/dist/utils/deviceSettings.d.ts +1 -0
  125. package/dist/utils/deviceSettings.d.ts.map +1 -1
  126. package/dist/utils/index.d.ts +1 -1
  127. package/dist/utils/index.d.ts.map +1 -1
  128. package/dist/utils/patch.d.ts +1 -1
  129. package/dist/utils/patch.d.ts.map +1 -1
  130. package/dist/utils/pro2Nft.d.ts +7 -0
  131. package/dist/utils/pro2Nft.d.ts.map +1 -1
  132. package/package.json +6 -4
  133. package/src/api/BaseMethod.ts +8 -1
  134. package/src/api/CheckAllFirmwareRelease.ts +2 -4
  135. package/src/api/CheckBLEFirmwareRelease.ts +2 -2
  136. package/src/api/CheckBootloaderRelease.ts +3 -2
  137. package/src/api/CheckFirmwareRelease.ts +2 -2
  138. package/src/api/FirmwareUpdateV2.ts +12 -9
  139. package/src/api/FirmwareUpdateV3.ts +29 -19
  140. package/src/api/FirmwareUpdateV4.ts +934 -739
  141. package/src/api/OpenWalletSession.ts +39 -9
  142. package/src/api/PromptWebDeviceAccess.ts +2 -2
  143. package/src/api/SearchDevices.ts +6 -3
  144. package/src/api/UploadPortfolio.ts +9 -2
  145. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  146. package/src/api/device/DeviceChangePin.ts +4 -1
  147. package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
  148. package/src/api/device/DeviceRebootToBootloader.ts +3 -1
  149. package/src/api/device/DeviceSettings.ts +34 -20
  150. package/src/api/device/DeviceVerify.ts +25 -0
  151. package/src/api/device/DeviceWipe.ts +4 -1
  152. package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
  153. package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
  154. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  155. package/src/api/firmware/FirmwareUpdatePlan.ts +37 -77
  156. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +10 -6
  157. package/src/api/firmware/protocolV2Release.ts +7 -2
  158. package/src/api/helpers/base64Data.ts +85 -0
  159. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  160. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
  161. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  162. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  163. package/src/api/protocol-v2/DeviceReboot.ts +5 -0
  164. package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
  165. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
  166. package/src/api/protocol-v2/helpers.ts +8 -0
  167. package/src/api/ton/TonSignMessage.ts +5 -3
  168. package/src/core/index.ts +264 -55
  169. package/src/data/messages/messages-protocol-v2.json +140 -13
  170. package/src/data-manager/DataManager.ts +46 -39
  171. package/src/data-manager/TransportManager.ts +3 -1
  172. package/src/device/Device.ts +210 -44
  173. package/src/device/DeviceCommands.ts +27 -18
  174. package/src/device/DeviceConnector.ts +17 -3
  175. package/src/device/DevicePool.ts +9 -2
  176. package/src/device/DeviceStateMapper.ts +30 -24
  177. package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
  178. package/src/events/logBlockEvent.ts +14 -1
  179. package/src/events/ui-request.ts +3 -0
  180. package/src/index.ts +0 -10
  181. package/src/protocols/protocol-v2/features.ts +22 -5
  182. package/src/protocols/protocol-v2/index.ts +2 -0
  183. package/src/protocols/protocol-v2/resources.ts +110 -147
  184. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
  185. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  186. package/src/types/api/firmwareUpdate.ts +1 -1
  187. package/src/types/api/firmwareUpdatePlan.ts +2 -2
  188. package/src/types/api/protocolV2.ts +1 -1
  189. package/src/types/device.ts +2 -1
  190. package/src/types/settings.ts +6 -34
  191. package/src/utils/deviceFeaturesCompat.ts +9 -4
  192. package/src/utils/deviceFeaturesUtils.ts +4 -0
  193. package/src/utils/deviceInfoUtils.ts +9 -2
  194. package/src/utils/deviceSettings.ts +11 -4
  195. package/src/utils/index.ts +1 -0
  196. package/src/utils/pro2Nft.ts +31 -8
  197. package/__tests__/firmware-memory-host.test.ts +0 -126
  198. package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
  199. package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
  200. package/src/api/firmware/FirmwareMemoryHost.ts +0 -143
@@ -2,6 +2,10 @@ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
  import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
3
3
 
4
4
  import DeviceSettings from '../src/api/device/DeviceSettings';
5
+ import { Device } from '../src/device/Device';
6
+ import { DEVICE } from '../src/events';
7
+ import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
8
+ import { DEVICE_SETTINGS_NEVER_TIMEOUT_MS } from '../src/utils/deviceSettings';
5
9
 
6
10
  import type { Features } from '../src/types';
7
11
 
@@ -20,6 +24,9 @@ function createDevice({ protocol }: { protocol: 'V1' | 'V2' }) {
20
24
  const typedCall = jest.fn().mockResolvedValue({ message: { message: 'Success' } });
21
25
  const updateState = jest.fn();
22
26
  const getDeviceState = jest.fn();
27
+ const refreshProtocolV2SettingsAfterMutation = jest.fn(() =>
28
+ getDeviceState({ refreshSections: ['status', 'settings'] })
29
+ );
23
30
  return {
24
31
  device: {
25
32
  features,
@@ -28,6 +35,7 @@ function createDevice({ protocol }: { protocol: 'V1' | 'V2' }) {
28
35
  commands: { typedCall },
29
36
  updateState,
30
37
  getDeviceState,
38
+ refreshProtocolV2SettingsAfterMutation,
31
39
  },
32
40
  typedCall,
33
41
  updateState,
@@ -115,6 +123,39 @@ describe('DeviceSettings protocol routing', () => {
115
123
  );
116
124
  });
117
125
 
126
+ it('normalizes legacy Protocol V1 never values before ApplySettings', async () => {
127
+ const { device, typedCall, updateState } = createDevice({ protocol: 'V1' });
128
+ const method = new DeviceSettings({
129
+ id: 2,
130
+ payload: {
131
+ method: 'deviceSettings',
132
+ autoLockDelayMs: 0,
133
+ autoShutdownDelayMs: 0,
134
+ },
135
+ });
136
+ method.init();
137
+ (method as any).device = device;
138
+
139
+ await expect(method.run()).resolves.toEqual({ message: 'Success' });
140
+ expect(typedCall).toHaveBeenCalledWith(
141
+ 'ApplySettings',
142
+ 'Success',
143
+ expect.objectContaining({
144
+ auto_lock_delay_ms: DEVICE_SETTINGS_NEVER_TIMEOUT_MS,
145
+ auto_shutdown_delay_ms: DEVICE_SETTINGS_NEVER_TIMEOUT_MS,
146
+ })
147
+ );
148
+ expect(updateState).toHaveBeenCalledWith(
149
+ {
150
+ settings: {
151
+ autoLockDelayMs: DEVICE_SETTINGS_NEVER_TIMEOUT_MS,
152
+ autoShutdownDelayMs: DEVICE_SETTINGS_NEVER_TIMEOUT_MS,
153
+ },
154
+ },
155
+ 'settings-write'
156
+ );
157
+ });
158
+
118
159
  it('uses DeviceSettingsSet and reloads Protocol V2 status and settings from the device', async () => {
119
160
  const { device, typedCall, updateState, getDeviceState } = createDevice({ protocol: 'V2' });
120
161
  const method = new DeviceSettings({
@@ -147,7 +188,98 @@ describe('DeviceSettings protocol routing', () => {
147
188
  expect(updateState).not.toHaveBeenCalled();
148
189
  });
149
190
 
150
- it('uses the Pro2 passphrase page as a device-side toggle and verifies the target state', async () => {
191
+ it('reconciles stale passphrase and label after an unrelated Protocol V2 setting change', async () => {
192
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
193
+ if (requestType === 'DeviceSettingsSet') {
194
+ return { message: { message: 'Success' } };
195
+ }
196
+ if (requestType === 'DeviceStatusGet') {
197
+ return {
198
+ message: {
199
+ init_states: true,
200
+ unlocked: false,
201
+ passphrase_enabled: false,
202
+ },
203
+ };
204
+ }
205
+ if (requestType === 'DeviceSettingsGet') {
206
+ return {
207
+ message: {
208
+ label: 'Current Label',
209
+ brightness: 80,
210
+ passphrase_enable: false,
211
+ },
212
+ };
213
+ }
214
+ throw new Error(`Unexpected request: ${requestType}`);
215
+ });
216
+ const device = Device.fromDescriptor({
217
+ id: 'pro2',
218
+ path: 'pro2',
219
+ protocolType: 'V2',
220
+ } as never);
221
+ (device as any).commands = { typedCall };
222
+ device.updateState(
223
+ {
224
+ protocol: 'V2',
225
+ identity: {
226
+ deviceType: EDeviceType.Pro2,
227
+ label: 'Stale Label',
228
+ },
229
+ status: {
230
+ mode: 'normal',
231
+ unlocked: false,
232
+ passphraseProtection: true,
233
+ },
234
+ settings: { brightness: 20 },
235
+ raw: {
236
+ protocolV2ProtocolInfo: {
237
+ version: 1,
238
+ build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
239
+ supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
240
+ },
241
+ },
242
+ },
243
+ 'initialize'
244
+ );
245
+ const onState = jest.fn();
246
+ device.on(DEVICE.STATE, onState);
247
+ const method = new DeviceSettings({
248
+ id: 3,
249
+ payload: {
250
+ method: 'deviceSettings',
251
+ brightness: 80,
252
+ },
253
+ });
254
+ method.init();
255
+ (method as any).device = device;
256
+
257
+ await expect(method.run()).resolves.toEqual({ message: 'Success' });
258
+
259
+ expect(typedCall.mock.calls.map(call => call[0])).toEqual([
260
+ 'DeviceSettingsSet',
261
+ 'DeviceStatusGet',
262
+ 'DeviceSettingsGet',
263
+ ]);
264
+ expect(device.state).toMatchObject({
265
+ identity: { label: 'Current Label' },
266
+ status: { passphraseProtection: false },
267
+ settings: { brightness: 80 },
268
+ });
269
+ expect(onState).toHaveBeenLastCalledWith(
270
+ device,
271
+ expect.objectContaining({
272
+ source: 'settings-read',
273
+ state: expect.objectContaining({
274
+ identity: expect.objectContaining({ label: 'Current Label' }),
275
+ status: expect.objectContaining({ passphraseProtection: false }),
276
+ settings: expect.objectContaining({ brightness: 80 }),
277
+ }),
278
+ })
279
+ );
280
+ });
281
+
282
+ it('uses the Pro2 passphrase page and refreshes device state after the toggle', async () => {
151
283
  const { device, typedCall, getDeviceState } = createDevice({ protocol: 'V2' });
152
284
  getDeviceState
153
285
  .mockResolvedValueOnce({
@@ -229,8 +361,8 @@ describe('DeviceSettings protocol routing', () => {
229
361
  expect(getDeviceState).toHaveBeenCalledWith({ refreshSections: ['settings'] });
230
362
  });
231
363
 
232
- it('rejects a successful Pro2 page response when the hardware did not reach the target', async () => {
233
- const { device, getDeviceState } = createDevice({ protocol: 'V2' });
364
+ it('persists a confirmed Pro2 passphrase enable when the refreshed state is unavailable', async () => {
365
+ const { device, getDeviceState, updateState } = createDevice({ protocol: 'V2' });
234
366
  getDeviceState.mockResolvedValue({
235
367
  status: { passphraseProtection: false },
236
368
  });
@@ -244,14 +376,15 @@ describe('DeviceSettings protocol routing', () => {
244
376
  method.init();
245
377
  (method as any).device = device;
246
378
 
247
- await expect(method.run()).rejects.toMatchObject({
248
- errorCode: HardwareErrorCode.RuntimeError,
249
- message: 'Protocol V2 passphrase setting did not reach the requested value.',
250
- });
379
+ await expect(method.run()).resolves.toEqual({ message: 'Success' });
380
+ expect(updateState).toHaveBeenCalledWith(
381
+ { status: { passphraseProtection: true } },
382
+ 'settings-write'
383
+ );
251
384
  });
252
385
 
253
- it('accepts a locked Pro2 as confirmation after disabling passphrase', async () => {
254
- const { device, getDeviceState } = createDevice({ protocol: 'V2' });
386
+ it('persists a confirmed Pro2 passphrase disable when refreshed state is unavailable after locking', async () => {
387
+ const { device, getDeviceState, updateState } = createDevice({ protocol: 'V2' });
255
388
  getDeviceState
256
389
  .mockResolvedValueOnce({
257
390
  status: { unlocked: true, passphraseProtection: true },
@@ -270,6 +403,101 @@ describe('DeviceSettings protocol routing', () => {
270
403
  (method as any).device = device;
271
404
 
272
405
  await expect(method.run()).resolves.toEqual({ message: 'Success' });
406
+ expect(updateState).toHaveBeenCalledWith(
407
+ {
408
+ status: {
409
+ passphraseProtection: false,
410
+ unlockedAttachPin: false,
411
+ },
412
+ },
413
+ 'settings-write'
414
+ );
415
+ });
416
+
417
+ it('persists a confirmed Pro2 passphrase disable when the Attach PIN session locks', async () => {
418
+ let statusReadCount = 0;
419
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
420
+ if (requestType === 'DeviceStatusGet') {
421
+ statusReadCount += 1;
422
+ return {
423
+ message:
424
+ statusReadCount === 1
425
+ ? {
426
+ init_states: true,
427
+ unlocked: true,
428
+ passphrase_enabled: true,
429
+ unlocked_by_attach_to_pin: true,
430
+ }
431
+ : { init_states: true, unlocked: false },
432
+ };
433
+ }
434
+ if (requestType === 'DeviceSettingsPageShow') {
435
+ return { message: { message: 'Success' } };
436
+ }
437
+ if (requestType === 'DeviceSettingsGet') {
438
+ return { message: { label: 'Pro 2' } };
439
+ }
440
+ throw new Error(`Unexpected request: ${requestType}`);
441
+ });
442
+ const device = Device.fromDescriptor({
443
+ id: 'pro2-passphrase',
444
+ path: 'pro2-passphrase',
445
+ protocolType: 'V2',
446
+ } as never);
447
+ (device as any).commands = { typedCall };
448
+ device.updateState(
449
+ {
450
+ protocol: 'V2',
451
+ identity: { deviceType: EDeviceType.Pro2 },
452
+ status: {
453
+ mode: 'normal',
454
+ unlocked: true,
455
+ passphraseProtection: true,
456
+ unlockedAttachPin: true,
457
+ },
458
+ raw: {
459
+ protocolV2ProtocolInfo: {
460
+ version: 1,
461
+ supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
462
+ },
463
+ },
464
+ },
465
+ 'initialize'
466
+ );
467
+ const method = new DeviceSettings({
468
+ id: 6,
469
+ payload: {
470
+ method: 'deviceSettings',
471
+ usePassphrase: false,
472
+ },
473
+ });
474
+ method.init();
475
+ (method as any).device = device;
476
+ const onState = jest.fn();
477
+ device.on(DEVICE.STATE, onState);
478
+
479
+ await expect(method.run()).resolves.toEqual({ message: 'Success' });
480
+ expect(device.state?.status).toMatchObject({
481
+ unlocked: false,
482
+ passphraseProtection: false,
483
+ unlockedAttachPin: false,
484
+ });
485
+ expect(onState).toHaveBeenCalledWith(
486
+ device,
487
+ expect.objectContaining({
488
+ source: 'settings-write',
489
+ changedKeys: expect.arrayContaining([
490
+ 'status.passphraseProtection',
491
+ 'status.unlockedAttachPin',
492
+ ]),
493
+ state: expect.objectContaining({
494
+ status: expect.objectContaining({
495
+ passphraseProtection: false,
496
+ unlockedAttachPin: false,
497
+ }),
498
+ }),
499
+ })
500
+ );
273
501
  });
274
502
 
275
503
  it('keeps Protocol V1 passphrase settings on ApplySettings', async () => {
@@ -88,9 +88,9 @@ describe('Device state events', () => {
88
88
  expect(device.getCurrentDeviceType()).toBe(EDeviceType.Pro2);
89
89
  expect(device.getCurrentDeviceId()).toBe('wallet-id');
90
90
  expect(device.getCurrentSerialNo()).toBe('SERIAL-PRO2');
91
- expect(device.getCurrentBleName()).toBe('Pro2 1234');
91
+ expect(device.getCurrentBleName()).toBe('Pro 2 1234');
92
92
  expect(device.getCurrentLabel()).toBeNull();
93
- expect((device as any).getCurrentDisplayName()).toBe('Pro2 1234');
93
+ expect((device as any).getCurrentDisplayName()).toBe('Pro 2 1234');
94
94
  expect(device.getCurrentPassphraseProtection()).toBe(false);
95
95
  expect(device.getCurrentFirmwareVersionString()).toBe('5.1.2');
96
96
  expect(device.getCurrentBLEFirmwareVersionString()).toBe('0.0.0');
@@ -115,7 +115,7 @@ describe('DeviceStateMapper', () => {
115
115
  {
116
116
  protocol_version: 2,
117
117
  hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
118
- fw: {
118
+ main_mcu: {
119
119
  application: { version: '5.0.0', build_id: 'p1-build', hash: [0x01, 0x02] },
120
120
  application_data: { version: '5.0.1', build_id: 'p2-build', hash: [0x03, 0x04] },
121
121
  bootloader: { version: '2.0.0' },
@@ -136,7 +136,7 @@ describe('DeviceStateMapper', () => {
136
136
  expect(patch.identity).toMatchObject({
137
137
  deviceType: EDeviceType.Pro2,
138
138
  serialNo: 'SERIAL-1',
139
- bleName: 'Pro2 1234',
139
+ bleName: 'Pro 2 1234',
140
140
  });
141
141
  expect(patch.versions).toMatchObject({
142
142
  firmware: '5.0.0',
@@ -172,6 +172,24 @@ describe('DeviceStateMapper', () => {
172
172
  });
173
173
  });
174
174
 
175
+ test('removes a truncated Find My suffix from the Protocol V2 BLE name', () => {
176
+ const patch = mapProtocolV2DeviceInfoToState({
177
+ hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
178
+ coprocessor: { bt_adv_name: 'Pro2 22D8 - Fin' },
179
+ });
180
+
181
+ expect(patch.identity?.bleName).toBe('Pro 2 22D8');
182
+ });
183
+
184
+ test('canonicalizes a spaced Pro2 advertisement name from DeviceInfo', () => {
185
+ const patch = mapProtocolV2DeviceInfoToState({
186
+ hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
187
+ coprocessor: { bt_adv_name: 'Pro 2 0088 - Find My' },
188
+ });
189
+
190
+ expect(patch.identity?.bleName).toBe('Pro 2 0088');
191
+ });
192
+
175
193
  test('maps the hardware model independently from Protocol V2', () => {
176
194
  const pro = mapProtocolV2DeviceInfoToState({
177
195
  protocol_version: 2,
@@ -188,7 +206,7 @@ describe('DeviceStateMapper', () => {
188
206
  {
189
207
  protocol_version: 2,
190
208
  hw: { serial_no: 'SERIAL-NORMAL' },
191
- fw: {
209
+ main_mcu: {
192
210
  application: { version: '5.0.0' },
193
211
  bootloader: { version: '2.0.0' },
194
212
  },
@@ -214,7 +232,7 @@ describe('DeviceStateMapper', () => {
214
232
  'bootloader',
215
233
  {
216
234
  hw: { serial_no: 'SERIAL-BOOT' },
217
- fw: { bootloader: { version: '2.0.0' } },
235
+ main_mcu: { bootloader: { version: '2.0.0' } },
218
236
  se1: {},
219
237
  },
220
238
  ],
@@ -222,7 +240,7 @@ describe('DeviceStateMapper', () => {
222
240
  'romloader',
223
241
  {
224
242
  hw: { serial_no: 'SERIAL-ROM' },
225
- fw: {
243
+ main_mcu: {
226
244
  romloader: { version: '1.0.0' },
227
245
  bootloader: { version: '2.0.0' },
228
246
  },
@@ -241,6 +259,8 @@ describe('DeviceStateMapper', () => {
241
259
  init_states: true,
242
260
  unlocked: false,
243
261
  passphrase_enabled: true,
262
+ attach_to_pin_enabled: true,
263
+ unlocked_by_attach_to_pin: true,
244
264
  backup_required: true,
245
265
  } as DeviceStatus);
246
266
 
@@ -252,6 +272,26 @@ describe('DeviceStateMapper', () => {
252
272
  backupRequired: true,
253
273
  });
254
274
  expect(locked.status).not.toHaveProperty('passphraseProtection');
275
+ expect(locked.status).not.toHaveProperty('attachToPinEnabled');
276
+ expect(locked.status).not.toHaveProperty('unlockedAttachPin');
277
+ });
278
+
279
+ test('omits an unavailable attach-to-PIN status after an unlocked query failure', () => {
280
+ const status = mapProtocolV2DeviceStatusToState({
281
+ init_states: true,
282
+ unlocked: true,
283
+ passphrase_enabled: true,
284
+ attach_to_pin_enabled: null,
285
+ unlocked_by_attach_to_pin: false,
286
+ backup_required: false,
287
+ } as DeviceStatus);
288
+
289
+ expect(status.status).toMatchObject({
290
+ unlocked: true,
291
+ passphraseProtection: true,
292
+ unlockedAttachPin: false,
293
+ });
294
+ expect(status.status).not.toHaveProperty('attachToPinEnabled');
255
295
  });
256
296
 
257
297
  test('maps common settings into identity, status and settings sections', () => {
@@ -279,10 +319,26 @@ describe('DeviceStateMapper', () => {
279
319
  language: 'en-Latn-US',
280
320
  brightness: 80,
281
321
  autolock_delay_ms: 30_000,
322
+ passphrase_enable: null,
282
323
  } as DeviceSettings)
283
324
  ).toEqual({
284
325
  identity: { label: 'Pro2' },
285
326
  settings: { language: 'en', brightness: 80, autoLockDelayMs: 30_000 },
286
327
  });
287
328
  });
329
+
330
+ test('omits private Protocol V2 settings that are unavailable while locked', () => {
331
+ expect(
332
+ mapDeviceSettingsToState({
333
+ brightness: 80,
334
+ passphrase_enable: null,
335
+ fido_enabled: null,
336
+ autolock_delay_ms: null,
337
+ autoshutdown_delay_ms: null,
338
+ label: null,
339
+ } as DeviceSettings)
340
+ ).toEqual({
341
+ settings: { brightness: 80 },
342
+ });
343
+ });
288
344
  });
@@ -89,7 +89,7 @@ describe('device feature selectors', () => {
89
89
  const features = buildProtocolV2FeaturesPayload({
90
90
  deviceInfo: {
91
91
  hw: { Device_type: DeviceType.PRO2, serial_no: 'P2-001' },
92
- fw: {
92
+ main_mcu: {
93
93
  application: { version: '5.0.0' },
94
94
  bootloader: { version: '2.0.0' },
95
95
  romloader: { version: '1.0.0' },
@@ -123,6 +123,20 @@ describe('device feature selectors', () => {
123
123
  test('recognizes Neo discovery names before initialization', () => {
124
124
  expect(getDeviceTypeByBleName('OneKey Neo 1234')).toBe(EDeviceType.Neo);
125
125
  expect(getDeviceTypeByBleName('Neo 1234')).toBe(EDeviceType.Neo);
126
+ expect(getDeviceTypeByBleName('Neo22D8')).toBe(EDeviceType.Neo);
127
+ expect(getDeviceTypeByBleName('OneKeyNeo22D8')).toBe(EDeviceType.Neo);
128
+ });
129
+
130
+ test('recognizes compact Pro2 discovery names before initialization', () => {
131
+ expect(getDeviceTypeByBleName('Pro2 A1B2')).toBe(EDeviceType.Pro2);
132
+ expect(getDeviceTypeByBleName('Pro2A1B2')).toBe(EDeviceType.Pro2);
133
+ expect(getDeviceTypeByBleName('OneKeyPro2A1B2')).toBe(EDeviceType.Pro2);
134
+ expect(getDeviceTypeByBleName('Pro 2 A1B2')).toBe(EDeviceType.Pro2);
135
+ expect(getDeviceTypeByBleName('Pro 2 0088')).toBe(EDeviceType.Pro2);
136
+ expect(getDeviceTypeByBleName('OneKey Pro 2 A1B2')).toBe(EDeviceType.Pro2);
137
+ expect(getDeviceTypeByBleName('Pro A1B2')).toBe(EDeviceType.Pro);
138
+ expect(getDeviceTypeByBleName('Pro 22D8')).toBe(EDeviceType.Pro);
139
+ expect(getDeviceTypeByBleName('Pro 2D8F')).toBe(EDeviceType.Pro);
126
140
  });
127
141
 
128
142
  test.each([EDeviceType.Pro2, EDeviceType.Neo])(