@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.36

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 (91) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +74 -0
  2. package/__tests__/DeviceCommands.test.ts +84 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/device-settings.test.ts +3 -0
  5. package/__tests__/device-wallet-session-store.test.ts +80 -7
  6. package/__tests__/evmSignTransaction.test.ts +69 -0
  7. package/__tests__/get-device-state.test.ts +180 -40
  8. package/__tests__/open-wallet-session.test.ts +314 -55
  9. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  10. package/__tests__/protocol-v2.test.ts +768 -139
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  14. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  15. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  16. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  17. package/dist/api/evm/protocol.d.ts +3 -0
  18. package/dist/api/evm/protocol.d.ts.map +1 -0
  19. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  20. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  21. package/dist/core/deviceEventRegistration.d.ts +2 -0
  22. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/device/Device.d.ts +25 -6
  25. package/dist/device/Device.d.ts.map +1 -1
  26. package/dist/device/DeviceCommands.d.ts.map +1 -1
  27. package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
  28. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  29. package/dist/events/device.d.ts +4 -0
  30. package/dist/events/device.d.ts.map +1 -1
  31. package/dist/events/ui-request.d.ts +27 -2
  32. package/dist/events/ui-request.d.ts.map +1 -1
  33. package/dist/index.d.ts +63 -12
  34. package/dist/index.js +1164 -904
  35. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  36. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  37. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  38. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  40. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  41. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  42. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  43. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  45. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  46. package/dist/types/api/openWalletSession.d.ts +1 -1
  47. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  48. package/dist/types/api/protocolV2.d.ts +2 -2
  49. package/dist/types/api/protocolV2.d.ts.map +1 -1
  50. package/dist/types/device.d.ts +2 -1
  51. package/dist/types/device.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/patch.d.ts +1 -1
  55. package/dist/utils/patch.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/BaseMethod.ts +1 -1
  58. package/src/api/FirmwareUpdateV4.ts +39 -14
  59. package/src/api/GetPassphraseState.ts +17 -0
  60. package/src/api/OpenWalletSession.ts +31 -8
  61. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
  62. package/src/api/device/DeviceUnlock.ts +1 -1
  63. package/src/api/evm/EVMGetAddress.ts +2 -2
  64. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  65. package/src/api/evm/EVMSignMessage.ts +2 -2
  66. package/src/api/evm/EVMSignTransaction.ts +2 -2
  67. package/src/api/evm/EVMSignTypedData.ts +35 -45
  68. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  69. package/src/api/evm/protocol.ts +6 -0
  70. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  71. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  72. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  73. package/src/core/deviceEventRegistration.ts +4 -0
  74. package/src/core/index.ts +46 -4
  75. package/src/data/messages/messages-protocol-v2.json +408 -485
  76. package/src/device/Device.ts +266 -43
  77. package/src/device/DeviceCommands.ts +12 -2
  78. package/src/device/DeviceWalletSessionStore.ts +0 -14
  79. package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
  80. package/src/events/device.ts +4 -0
  81. package/src/events/ui-request.ts +32 -2
  82. package/src/protocols/protocol-v2/features.ts +65 -16
  83. package/src/protocols/protocol-v2/index.ts +5 -0
  84. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  85. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  86. package/src/protocols/protocol-v2/unlockRetry.ts +51 -18
  87. package/src/protocols/protocol-v2/walletSession.ts +172 -64
  88. package/src/types/api/openWalletSession.ts +1 -1
  89. package/src/types/api/protocolV2.ts +2 -2
  90. package/src/types/device.ts +2 -0
  91. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -1,7 +1,9 @@
1
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { DeviceType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { Device } from '../src/device/Device';
4
5
  import { DEVICE } from '../src/events';
6
+ import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
5
7
 
6
8
  jest.mock('../src/data/config', () => ({
7
9
  getSDKVersion: jest.fn(() => '1.0.0'),
@@ -28,7 +30,83 @@ const createV1Device = (typedCall: jest.Mock) => {
28
30
  return device;
29
31
  };
30
32
 
33
+ const protocolV2ApplicationInfo = {
34
+ version: 1,
35
+ build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
36
+ supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
37
+ };
38
+
39
+ const getProtocolV2LoaderInfo = (mode: 'bootloader' | 'romloader') => ({
40
+ version: 1,
41
+ build_fingerprint: `${mode}__1.0.0__abcdef0__PROD__RELEASE`,
42
+ supported_messages: [],
43
+ });
44
+
31
45
  describe('getDeviceState', () => {
46
+ test('coalesces concurrent Protocol V2 runtime-context negotiation', async () => {
47
+ let resolveProtocolInfo:
48
+ | ((value: { message: typeof protocolV2ApplicationInfo }) => void)
49
+ | undefined;
50
+ const typedCall = jest.fn(
51
+ () =>
52
+ new Promise<{ message: typeof protocolV2ApplicationInfo }>(resolve => {
53
+ resolveProtocolInfo = resolve;
54
+ })
55
+ );
56
+ const device = createV2Device(typedCall);
57
+
58
+ const first = device.ensureProtocolV2RuntimeContext();
59
+ const second = device.ensureProtocolV2RuntimeContext();
60
+ resolveProtocolInfo?.({ message: protocolV2ApplicationInfo });
61
+
62
+ await expect(Promise.all([first, second])).resolves.toEqual([
63
+ protocolV2ApplicationInfo,
64
+ protocolV2ApplicationInfo,
65
+ ]);
66
+ expect(typedCall).toHaveBeenCalledTimes(1);
67
+ expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
68
+ eventless_wallet_session: true,
69
+ });
70
+ });
71
+
72
+ test('renegotiates Protocol V2 runtime context after transport disconnect', async () => {
73
+ const typedCall = jest.fn().mockResolvedValue({ message: protocolV2ApplicationInfo });
74
+ const device = createV2Device(typedCall);
75
+
76
+ await device.ensureProtocolV2RuntimeContext();
77
+ await device.ensureProtocolV2RuntimeContext();
78
+ device.markTransportDisconnected();
79
+ await device.ensureProtocolV2RuntimeContext();
80
+
81
+ expect(typedCall).toHaveBeenCalledTimes(2);
82
+ });
83
+
84
+ test('rejects every waiter when runtime-context negotiation is invalidated', async () => {
85
+ let resolveProtocolInfo:
86
+ | ((value: { message: typeof protocolV2ApplicationInfo }) => void)
87
+ | undefined;
88
+ const typedCall = jest.fn(
89
+ () =>
90
+ new Promise<{ message: typeof protocolV2ApplicationInfo }>(resolve => {
91
+ resolveProtocolInfo = resolve;
92
+ })
93
+ );
94
+ const device = createV2Device(typedCall);
95
+
96
+ const first = device.ensureProtocolV2RuntimeContext();
97
+ const second = device.ensureProtocolV2RuntimeContext();
98
+ const firstExpectation = expect(first).rejects.toMatchObject({
99
+ errorCode: HardwareErrorCode.DeviceInitializeFailed,
100
+ });
101
+ const secondExpectation = expect(second).rejects.toMatchObject({
102
+ errorCode: HardwareErrorCode.DeviceInitializeFailed,
103
+ });
104
+ device.markTransportDisconnected();
105
+ resolveProtocolInfo?.({ message: protocolV2ApplicationInfo });
106
+
107
+ await Promise.all([firstExpectation, secondExpectation]);
108
+ });
109
+
32
110
  test('does not expose the internal wallet session', async () => {
33
111
  const device = createV2Device(jest.fn());
34
112
  device.updateState({ protocol: 'V2' }, 'initialize');
@@ -39,45 +117,55 @@ describe('getDeviceState', () => {
39
117
  });
40
118
 
41
119
  test('hydrates Protocol V2 with separate DeviceInfoGet and DeviceStatusGet', async () => {
42
- const typedCall = jest.fn().mockImplementation((requestType: string) => ({
43
- message:
44
- requestType === 'DeviceInfoGet'
45
- ? {
46
- protocol_version: 2,
47
- hw: { serial_no: 'SERIAL-1' },
48
- fw: { application: { version: '5.0.0' } },
49
- }
50
- : { init_states: true, unlocked: true },
51
- }));
120
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
121
+ if (requestType === 'DeviceInfoGet') {
122
+ return {
123
+ message: {
124
+ protocol_version: 2,
125
+ hw: { serial_no: 'SERIAL-1' },
126
+ fw: { application: { version: '5.0.0' } },
127
+ },
128
+ };
129
+ }
130
+ if (requestType === 'ProtocolInfoRequest') {
131
+ return { message: protocolV2ApplicationInfo };
132
+ }
133
+ return { message: { init_states: true, unlocked: true } };
134
+ });
52
135
  const device = createV2Device(typedCall);
53
136
 
54
137
  const state = await device.getDeviceState();
55
138
 
56
139
  expect(state.identity.serialNo).toBe('SERIAL-1');
57
- expect(typedCall).toHaveBeenCalledTimes(2);
140
+ expect(typedCall).toHaveBeenCalledTimes(3);
58
141
  expect(typedCall.mock.calls[0][0]).toBe('DeviceInfoGet');
59
142
  expect(typedCall.mock.calls[0][2]?.targets?.status).not.toBe(true);
60
143
  expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
61
144
  });
62
145
 
63
146
  test('uses one full DeviceInfoGet plus DeviceStatusGet when firmware refresh initializes state', async () => {
64
- const typedCall = jest.fn().mockImplementation((requestType: string) => ({
65
- message:
66
- requestType === 'DeviceInfoGet'
67
- ? {
68
- protocol_version: 2,
69
- hw: { serial_no: 'SERIAL-1' },
70
- fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
71
- }
72
- : { init_states: true, unlocked: true },
73
- }));
147
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
148
+ if (requestType === 'DeviceInfoGet') {
149
+ return {
150
+ message: {
151
+ protocol_version: 2,
152
+ hw: { serial_no: 'SERIAL-1' },
153
+ fw: { application: { version: '5.0.0', hash: 'HASH-1' } },
154
+ },
155
+ };
156
+ }
157
+ if (requestType === 'ProtocolInfoRequest') {
158
+ return { message: protocolV2ApplicationInfo };
159
+ }
160
+ return { message: { init_states: true, unlocked: true } };
161
+ });
74
162
  const device = createV2Device(typedCall);
75
163
 
76
164
  await device.getDeviceState({
77
165
  refreshSections: ['identity', 'versions', 'verification'],
78
166
  });
79
167
 
80
- expect(typedCall).toHaveBeenCalledTimes(2);
168
+ expect(typedCall).toHaveBeenCalledTimes(3);
81
169
  expect(typedCall).toHaveBeenNthCalledWith(
82
170
  1,
83
171
  'DeviceInfoGet',
@@ -115,13 +203,13 @@ describe('getDeviceState', () => {
115
203
  });
116
204
 
117
205
  test.each(['bootloader', 'romloader'] as const)(
118
- 'temporarily treats failed DeviceStatusGet with %s metadata as bootloader',
206
+ 'uses ProtocolInfo to preserve %s mode without DeviceStatusGet',
119
207
  async mode => {
120
208
  const typedCall = jest.fn().mockImplementation((requestType: string) => {
121
209
  if (requestType === 'DeviceInfoGet') {
122
210
  return {
123
211
  message: {
124
- hw: { serial_no: 'SERIAL-1' },
212
+ hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
125
213
  fw:
126
214
  mode === 'romloader'
127
215
  ? { romloader: { version: '1.0.0' } }
@@ -129,27 +217,53 @@ describe('getDeviceState', () => {
129
217
  },
130
218
  };
131
219
  }
132
- throw new Error('DeviceStatusGet unavailable');
220
+ if (requestType === 'ProtocolInfoRequest') {
221
+ return { message: getProtocolV2LoaderInfo(mode) };
222
+ }
223
+ throw new Error(`Unexpected request: ${requestType}`);
133
224
  });
134
225
  const device = createV2Device(typedCall);
135
- device.updateState({ protocol: 'V2', status: { mode } }, 'initialize');
226
+ device.updateState(
227
+ {
228
+ protocol: 'V2',
229
+ identity: { deviceType: EDeviceType.Pro2 },
230
+ status: { mode },
231
+ raw: { protocolV2ProtocolInfo: getProtocolV2LoaderInfo(mode) },
232
+ },
233
+ 'initialize'
234
+ );
136
235
 
137
236
  const state = await device.getDeviceState({ refreshSections: ['status'] });
138
237
 
139
- expect(state.status.mode).toBe('bootloader');
140
- expect(typedCall.mock.calls.map(call => call[0])).toEqual([
141
- 'DeviceInfoGet',
142
- 'DeviceStatusGet',
143
- ]);
238
+ expect(state.status.mode).toBe(mode);
239
+ expect(typedCall).not.toHaveBeenCalled();
144
240
  }
145
241
  );
146
242
 
147
243
  test('refreshes Protocol V2 status only when explicitly requested in normal mode', async () => {
148
- const typedCall = jest.fn().mockResolvedValue({
149
- message: { init_states: true, unlocked: true, device_id: 'device-1' },
244
+ const typedCall = jest.fn().mockImplementation((requestType: string) => {
245
+ if (requestType === 'DeviceInfoGet') {
246
+ return {
247
+ message: {
248
+ hw: { serial_no: 'SERIAL-1' },
249
+ fw: { application: { version: '5.0.0' } },
250
+ },
251
+ };
252
+ }
253
+ if (requestType === 'ProtocolInfoRequest') {
254
+ return { message: protocolV2ApplicationInfo };
255
+ }
256
+ return { message: { init_states: true, unlocked: true, device_id: 'device-1' } };
150
257
  });
151
258
  const device = createV2Device(typedCall);
152
- device.updateState({ protocol: 'V2', status: { mode: 'normal' } }, 'initialize');
259
+ device.updateState(
260
+ {
261
+ protocol: 'V2',
262
+ status: { mode: 'normal' },
263
+ raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
264
+ },
265
+ 'initialize'
266
+ );
153
267
 
154
268
  const state = await device.getDeviceState({ refreshSections: ['status'] });
155
269
 
@@ -167,7 +281,14 @@ describe('getDeviceState', () => {
167
281
  },
168
282
  });
169
283
  const device = createV2Device(typedCall);
170
- device.updateState({ protocol: 'V2', status: { mode: 'normal' } }, 'initialize');
284
+ device.updateState(
285
+ {
286
+ protocol: 'V2',
287
+ status: { mode: 'normal' },
288
+ raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
289
+ },
290
+ 'initialize'
291
+ );
171
292
  const onState = jest.fn();
172
293
  device.on(DEVICE.STATE, onState);
173
294
 
@@ -203,18 +324,27 @@ describe('getDeviceState', () => {
203
324
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
204
325
  };
205
326
  }
327
+ if (requestType === 'ProtocolInfoRequest') {
328
+ return { message: protocolV2ApplicationInfo };
329
+ }
206
330
  if (requestType === 'DeviceSettingsGet') {
207
331
  return { message: { label: 'Renamed Pro 2' } };
208
332
  }
209
333
  throw new Error(`Unexpected request: ${requestType}`);
210
334
  });
211
335
  const device = createV2Device(typedCall);
212
- device.updateState({ protocol: 'V2', status: { mode: 'normal' } }, 'initialize');
336
+ device.updateState(
337
+ {
338
+ protocol: 'V2',
339
+ status: { mode: 'normal' },
340
+ raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
341
+ },
342
+ 'initialize'
343
+ );
213
344
 
214
345
  const state = await device.getDeviceState({ refreshSections: ['status', 'settings'] });
215
346
 
216
347
  expect(typedCall.mock.calls.map(call => call[0])).toEqual([
217
- 'DeviceInfoGet',
218
348
  'DeviceStatusGet',
219
349
  'DeviceSettingsGet',
220
350
  ]);
@@ -237,13 +367,20 @@ describe('getDeviceState', () => {
237
367
  message: { init_states: true, unlocked: false, device_id: 'device-1' },
238
368
  };
239
369
  }
370
+ if (requestType === 'ProtocolInfoRequest') {
371
+ return { message: protocolV2ApplicationInfo };
372
+ }
240
373
  throw Object.assign(new Error('Device locked'), {
241
374
  errorCode: HardwareErrorCode.DeviceLocked,
242
375
  });
243
376
  });
244
377
  const device = createV2Device(typedCall);
245
378
  device.updateState(
246
- { protocol: 'V2', status: { mode: 'normal', unlocked: true } },
379
+ {
380
+ protocol: 'V2',
381
+ status: { mode: 'normal', unlocked: true },
382
+ raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
383
+ },
247
384
  'initialize'
248
385
  );
249
386
 
@@ -383,7 +520,10 @@ describe('getDeviceState', () => {
383
520
  },
384
521
  };
385
522
  }
386
- throw new Error('DeviceStatusGet unavailable in bootloader');
523
+ if (requestType === 'ProtocolInfoRequest') {
524
+ return { message: getProtocolV2LoaderInfo('bootloader') };
525
+ }
526
+ throw new Error(`Unexpected request: ${requestType}`);
387
527
  });
388
528
  const device = createV2Device(typedCall);
389
529
  device.updateState(