@onekeyfe/hd-core 1.2.0-alpha.107 → 1.2.0-alpha.109

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 (74) hide show
  1. package/__tests__/DeviceCommands.test.ts +140 -26
  2. package/__tests__/base64Data.test.ts +70 -0
  3. package/__tests__/device-lifecycle-events.test.ts +113 -2
  4. package/__tests__/device-pool-state.test.ts +25 -0
  5. package/__tests__/device-state-mapper.test.ts +4 -4
  6. package/__tests__/device-utils.test.ts +1 -1
  7. package/__tests__/deviceSettings.test.ts +0 -1
  8. package/__tests__/deviceUploadNft.test.ts +33 -4
  9. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +125 -0
  10. package/__tests__/get-device-state.test.ts +60 -21
  11. package/__tests__/logBlockEvent.test.ts +13 -1
  12. package/__tests__/protocol-v2-resources.test.ts +8 -0
  13. package/__tests__/protocol-v2.test.ts +821 -227
  14. package/__tests__/refresh-device-state.test.ts +3 -1
  15. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  16. package/__tests__/ton-sign-message.test.ts +84 -0
  17. package/dist/api/FirmwareUpdateV4.d.ts +7 -3
  18. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  19. package/dist/api/UploadPortfolio.d.ts +1 -1
  20. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  21. package/dist/api/helpers/base64Data.d.ts +16 -0
  22. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  23. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  24. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  25. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  26. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  27. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  28. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  29. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/device/Device.d.ts +7 -2
  32. package/dist/device/Device.d.ts.map +1 -1
  33. package/dist/device/DeviceCommands.d.ts.map +1 -1
  34. package/dist/device/DevicePool.d.ts.map +1 -1
  35. package/dist/events/logBlockEvent.d.ts.map +1 -1
  36. package/dist/index.d.ts +16 -34
  37. package/dist/index.js +506 -329
  38. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  39. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  40. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  41. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  42. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  43. package/dist/types/api/protocolV2.d.ts +1 -1
  44. package/dist/types/api/protocolV2.d.ts.map +1 -1
  45. package/dist/types/settings.d.ts +4 -19
  46. package/dist/types/settings.d.ts.map +1 -1
  47. package/dist/utils/patch.d.ts +1 -1
  48. package/dist/utils/patch.d.ts.map +1 -1
  49. package/dist/utils/pro2Nft.d.ts +7 -0
  50. package/dist/utils/pro2Nft.d.ts.map +1 -1
  51. package/package.json +6 -4
  52. package/src/api/FirmwareUpdateV4.ts +307 -245
  53. package/src/api/UploadPortfolio.ts +9 -2
  54. package/src/api/helpers/base64Data.ts +85 -0
  55. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  56. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  57. package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
  58. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
  59. package/src/api/ton/TonSignMessage.ts +5 -3
  60. package/src/core/index.ts +6 -2
  61. package/src/data/messages/messages-protocol-v2.json +31 -14
  62. package/src/device/Device.ts +53 -31
  63. package/src/device/DeviceCommands.ts +4 -14
  64. package/src/device/DevicePool.ts +6 -2
  65. package/src/device/DeviceStateMapper.ts +15 -15
  66. package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
  67. package/src/events/logBlockEvent.ts +14 -1
  68. package/src/protocols/protocol-v2/features.ts +22 -5
  69. package/src/protocols/protocol-v2/index.ts +2 -0
  70. package/src/protocols/protocol-v2/resources.ts +9 -46
  71. package/src/types/api/protocolV2.ts +1 -1
  72. package/src/types/settings.ts +4 -19
  73. package/src/utils/deviceSettings.ts +1 -1
  74. package/src/utils/pro2Nft.ts +31 -8
@@ -0,0 +1,125 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import FirmwareUpdateV4 from '../../src/api/FirmwareUpdateV4';
4
+
5
+ import type { Device } from '../../src/device/Device';
6
+
7
+ jest.mock('../../src/data/config', () => ({
8
+ DEFAULT_DOMAIN: 'https://example.com/',
9
+ getSDKVersion: () => '0.0.0-test',
10
+ }));
11
+
12
+ describe('FirmwareUpdateV4 install polling', () => {
13
+ afterEach(() => {
14
+ jest.restoreAllMocks();
15
+ });
16
+
17
+ test('uses Stage, an empty write-only Request, then status polling', async () => {
18
+ const method = new FirmwareUpdateV4({
19
+ id: 1,
20
+ payload: {
21
+ method: 'firmwareUpdateV4',
22
+ connectId: 'pro2-ble',
23
+ },
24
+ });
25
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
26
+ const typedCall = jest
27
+ .fn()
28
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
29
+ .mockResolvedValueOnce({
30
+ type: 'DeviceFirmwareUpdateStatus',
31
+ message: {
32
+ records: [
33
+ {
34
+ target_id: 4,
35
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
36
+ path: 'vol0:/application_p1.bin',
37
+ },
38
+ ],
39
+ },
40
+ });
41
+ const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
42
+
43
+ method.device = {
44
+ getCommands: () => ({ typedCall, call }),
45
+ } as unknown as Device;
46
+ method.postTipMessage = jest.fn();
47
+ method.postProgressMessage = jest.fn();
48
+
49
+ const firmwareUpdate = method as unknown as {
50
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
51
+ waitForProtocolV2FirmwareUpdateComplete: (value: typeof targets) => Promise<void>;
52
+ reconnectProtocolV2Device: () => Promise<void>;
53
+ verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
54
+ };
55
+ firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
56
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue({});
57
+
58
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
59
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets);
60
+
61
+ expect(typedCall.mock.calls[0]).toEqual(['DeviceFirmwareUpdateStage', 'Success', { targets }]);
62
+ expect(call).toHaveBeenCalledWith(
63
+ 'DeviceFirmwareUpdateRequest',
64
+ {},
65
+ { returnAfterWrite: true }
66
+ );
67
+ expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
68
+ expect(call.mock.invocationCallOrder[0]).toBeLessThan(typedCall.mock.invocationCallOrder[1]);
69
+ });
70
+
71
+ test('does not send Request when Stage is rejected', async () => {
72
+ const method = new FirmwareUpdateV4({
73
+ id: 1,
74
+ payload: {
75
+ method: 'firmwareUpdateV4',
76
+ connectId: 'pro2-ble',
77
+ },
78
+ });
79
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
80
+ const typedCall = jest.fn().mockRejectedValue(new Error('stage rejected'));
81
+ const call = jest.fn();
82
+
83
+ method.device = {
84
+ getCommands: () => ({ typedCall, call }),
85
+ } as unknown as Device;
86
+
87
+ await expect(
88
+ (
89
+ method as unknown as {
90
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
91
+ }
92
+ ).protocolV2StartFirmwareUpdate({ targets })
93
+ ).rejects.toThrow('stage rejected');
94
+
95
+ expect(call).not.toHaveBeenCalled();
96
+ });
97
+
98
+ test('does not hide an explicit workflow cancellation during status polling', async () => {
99
+ const method = new FirmwareUpdateV4({
100
+ id: 1,
101
+ payload: {
102
+ method: 'firmwareUpdateV4',
103
+ connectId: 'pro2-ble',
104
+ },
105
+ });
106
+ const abortController = new AbortController();
107
+ abortController.abort();
108
+
109
+ method.abortSignal = abortController.signal;
110
+
111
+ await expect(
112
+ (
113
+ method as unknown as {
114
+ waitForProtocolV2FirmwareUpdateComplete: (
115
+ targets: Array<{ target_id: number; path: string }>
116
+ ) => Promise<void>;
117
+ }
118
+ ).waitForProtocolV2FirmwareUpdateComplete([
119
+ { target_id: 4, path: 'vol0:/application_p1.bin' },
120
+ ])
121
+ ).rejects.toMatchObject({
122
+ errorCode: HardwareErrorCode.CallQueueActionCancelled,
123
+ });
124
+ });
125
+ });
@@ -123,7 +123,7 @@ describe('getDeviceState', () => {
123
123
  message: {
124
124
  protocol_version: 2,
125
125
  hw: { serial_no: 'SERIAL-1' },
126
- fw: { application: { version: '5.0.0' } },
126
+ main_mcu: { application: { version: '5.0.0' } },
127
127
  },
128
128
  };
129
129
  }
@@ -150,7 +150,7 @@ describe('getDeviceState', () => {
150
150
  message: {
151
151
  protocol_version: 2,
152
152
  hw: { serial_no: 'SERIAL-1' },
153
- fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
153
+ main_mcu: { application: { version: '5.0.0', hash: 'HASH-1' } },
154
154
  },
155
155
  };
156
156
  }
@@ -182,7 +182,7 @@ describe('getDeviceState', () => {
182
182
  message: {
183
183
  protocol_version: 2,
184
184
  hw: { serial_no: 'SERIAL-1' },
185
- fw: { application: { version: '5.0.0' } },
185
+ main_mcu: { application: { version: '5.0.0' } },
186
186
  se1: { application: { version: '1.0.0' } },
187
187
  },
188
188
  });
@@ -202,25 +202,61 @@ describe('getDeviceState', () => {
202
202
  expect(state.versions.se01).toBe('1.0.0');
203
203
  });
204
204
 
205
- test.each(['bootloader', 'romloader'] as const)(
206
- 'uses ProtocolInfo to preserve %s mode without DeviceStatusGet',
207
- async mode => {
205
+ test.each([
206
+ ['bootloader', EDeviceType.Pro2, DeviceType.PRO2],
207
+ ['romloader', EDeviceType.Pro2, DeviceType.PRO2],
208
+ ['bootloader', EDeviceType.Neo, DeviceType.NEO],
209
+ ['romloader', EDeviceType.Neo, DeviceType.NEO],
210
+ ] as const)(
211
+ 'refreshes cached %s state after %s reboots into the application',
212
+ async (mode, deviceType, protocolV2DeviceType) => {
208
213
  const typedCall = jest.fn().mockImplementation((requestType: string) => {
209
- if (requestType === 'DeviceInfoGet') {
214
+ if (requestType === 'ProtocolInfoRequest') {
215
+ return { message: protocolV2ApplicationInfo };
216
+ }
217
+ if (requestType === 'DeviceStatusGet') {
210
218
  return {
211
- message: {
212
- hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
213
- fw:
219
+ message: { init_states: true, unlocked: true, device_id: 'wallet-1' },
220
+ };
221
+ }
222
+ throw new Error(`Unexpected request: ${requestType}`);
223
+ });
224
+ const device = createV2Device(typedCall);
225
+ device.updateState(
226
+ {
227
+ protocol: 'V2',
228
+ identity: { deviceType },
229
+ status: { mode },
230
+ raw: {
231
+ protocolV2DeviceInfo: {
232
+ hw: { Device_type: protocolV2DeviceType, serial_no: 'SERIAL-1' },
233
+ main_mcu:
214
234
  mode === 'romloader'
215
235
  ? { romloader: { version: '1.0.0' } }
216
236
  : { bootloader: { version: '1.0.0' } },
217
237
  },
218
- };
219
- }
220
- if (requestType === 'ProtocolInfoRequest') {
221
- return { message: getProtocolV2LoaderInfo(mode) };
222
- }
223
- throw new Error(`Unexpected request: ${requestType}`);
238
+ protocolV2ProtocolInfo: getProtocolV2LoaderInfo(mode),
239
+ },
240
+ },
241
+ 'initialize'
242
+ );
243
+
244
+ const state = await device.getDeviceState({ refreshSections: ['status'] });
245
+
246
+ expect(state.status.mode).toBe('normal');
247
+ expect(state.identity.deviceId).toBe('wallet-1');
248
+ expect(typedCall.mock.calls.map(call => call[0])).toEqual([
249
+ 'ProtocolInfoRequest',
250
+ 'DeviceStatusGet',
251
+ ]);
252
+ }
253
+ );
254
+
255
+ test.each(['bootloader', 'romloader'] as const)(
256
+ 'keeps live %s mode after refreshing cached loader state',
257
+ async mode => {
258
+ const typedCall = jest.fn().mockResolvedValue({
259
+ message: getProtocolV2LoaderInfo(mode),
224
260
  });
225
261
  const device = createV2Device(typedCall);
226
262
  device.updateState(
@@ -236,7 +272,10 @@ describe('getDeviceState', () => {
236
272
  const state = await device.getDeviceState({ refreshSections: ['status'] });
237
273
 
238
274
  expect(state.status.mode).toBe(mode);
239
- expect(typedCall).not.toHaveBeenCalled();
275
+ expect(typedCall).toHaveBeenCalledTimes(1);
276
+ expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
277
+ eventless_wallet_session: true,
278
+ });
240
279
  }
241
280
  );
242
281
 
@@ -246,7 +285,7 @@ describe('getDeviceState', () => {
246
285
  return {
247
286
  message: {
248
287
  hw: { serial_no: 'SERIAL-1' },
249
- fw: { application: { version: '5.0.0' } },
288
+ main_mcu: { application: { version: '5.0.0' } },
250
289
  },
251
290
  };
252
291
  }
@@ -315,7 +354,7 @@ describe('getDeviceState', () => {
315
354
  return {
316
355
  message: {
317
356
  hw: { serial_no: 'SERIAL-1' },
318
- fw: { application: { version: '5.0.0' } },
357
+ main_mcu: { application: { version: '5.0.0' } },
319
358
  },
320
359
  };
321
360
  }
@@ -358,7 +397,7 @@ describe('getDeviceState', () => {
358
397
  return {
359
398
  message: {
360
399
  hw: { serial_no: 'SERIAL-1' },
361
- fw: { application: { version: '5.0.0' } },
400
+ main_mcu: { application: { version: '5.0.0' } },
362
401
  },
363
402
  };
364
403
  }
@@ -543,7 +582,7 @@ describe('getDeviceState', () => {
543
582
  message: {
544
583
  protocol_version: 1,
545
584
  hw: { serial_no: 'SERIAL-1' },
546
- fw: { bootloader: { version: '1.0.0' } },
585
+ main_mcu: { bootloader: { version: '1.0.0' } },
547
586
  se1: {},
548
587
  },
549
588
  };
@@ -104,7 +104,19 @@ describe('getLogBlockLabel', () => {
104
104
  });
105
105
  });
106
106
 
107
- it.each(['deviceUploadNft', 'deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
107
+ it.each(['deviceUploadNft', 'deviceUploadWallpaper', 'uploadPortfolio'])(
108
+ 'skips large Base64 resource payload logging for %s',
109
+ method => {
110
+ const request = { method, path: 'resource.bin', data: 'A'.repeat(1024 * 1024) };
111
+ expect(getLogBlockLabel(request)).toBe(method);
112
+ expect(getSafeLogPayload(request, method)).toEqual({
113
+ method,
114
+ payload: '[REDACTED]',
115
+ });
116
+ }
117
+ );
118
+
119
+ it.each(['fileWrite', 'fileRead'])(
108
120
  'keeps metadata and replaces binary payloads with their size for %s',
109
121
  method => {
110
122
  const request = { method, path: 'resource.bin', data: new Uint8Array(1024) };
@@ -126,6 +126,14 @@ describe('Pro2 resource configuration', () => {
126
126
  expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
127
127
  });
128
128
 
129
+ test('uses only the resource file list from a manifest', () => {
130
+ expect(
131
+ parseProtocolV2ResourceManifest({
132
+ files: resourceManifest.files,
133
+ })
134
+ ).toEqual({ files: resourceManifest.files });
135
+ });
136
+
129
137
  test('rejects missing source and malformed manifest paths', () => {
130
138
  expect(() => parseProtocolV2Resources({})).toThrow('source is required');
131
139
  expect(() =>