@onekeyfe/hd-core 1.2.0-alpha.30 → 1.2.0-alpha.32

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 (123) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +251 -0
  2. package/__tests__/connectSettings.test.ts +5 -47
  3. package/__tests__/device-state-mapper.test.ts +10 -1
  4. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  5. package/__tests__/networkUtils.test.ts +20 -0
  6. package/__tests__/open-wallet-session.test.ts +19 -5
  7. package/__tests__/protocol-v2-firmware-targets.test.ts +18 -0
  8. package/__tests__/protocol-v2.test.ts +58 -240
  9. package/__tests__/public-device-state-api.test.ts +10 -1
  10. package/dist/api/CheckAllFirmwareRelease.d.ts +24 -1
  11. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  12. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  13. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  14. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  15. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  16. package/dist/api/FirmwareUpdateV4.d.ts +6 -22
  17. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  18. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  19. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  20. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  21. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  22. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -7
  23. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  24. package/dist/api/firmware/getBinary.d.ts +0 -7
  25. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  26. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  27. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  28. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  29. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  30. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  31. package/dist/data-manager/DataManager.d.ts +0 -1
  32. package/dist/data-manager/DataManager.d.ts.map +1 -1
  33. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  34. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  35. package/dist/index.d.ts +43 -179
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1048 -2606
  38. package/dist/inject.d.ts.map +1 -1
  39. package/dist/lowLevelInject.d.ts.map +1 -1
  40. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  41. package/dist/topLevelInject.d.ts.map +1 -1
  42. package/dist/types/api/checkAllFirmwareRelease.d.ts +25 -7
  43. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  44. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  45. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/types/api/export.d.ts +2 -4
  47. package/dist/types/api/export.d.ts.map +1 -1
  48. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  49. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  50. package/dist/types/api/index.d.ts +3 -10
  51. package/dist/types/api/index.d.ts.map +1 -1
  52. package/dist/types/api/openWalletSession.d.ts +9 -3
  53. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  54. package/dist/types/device.d.ts +6 -0
  55. package/dist/types/device.d.ts.map +1 -1
  56. package/dist/types/settings.d.ts +1 -13
  57. package/dist/types/settings.d.ts.map +1 -1
  58. package/dist/utils/networkUtils.d.ts +1 -0
  59. package/dist/utils/networkUtils.d.ts.map +1 -1
  60. package/package.json +4 -4
  61. package/src/api/CheckAllFirmwareRelease.ts +368 -50
  62. package/src/api/FirmwareUpdateV2.ts +120 -298
  63. package/src/api/FirmwareUpdateV3.ts +56 -258
  64. package/src/api/FirmwareUpdateV4.ts +328 -765
  65. package/src/api/OpenWalletSession.ts +12 -8
  66. package/src/api/PromptWebDeviceAccess.ts +2 -6
  67. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  68. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -50
  69. package/src/api/firmware/updateBootloader.ts +4 -19
  70. package/src/api/firmware/uploadFirmware.ts +22 -140
  71. package/src/data/messages/messages-protocol-v2.json +4 -0
  72. package/src/data-manager/DataManager.ts +33 -33
  73. package/src/data-manager/connectSettings.ts +0 -11
  74. package/src/device/DeviceStateMapper.ts +6 -0
  75. package/src/index.ts +0 -5
  76. package/src/inject.ts +2 -22
  77. package/src/lowLevelInject.ts +1 -5
  78. package/src/protocols/protocol-v2/walletSession.ts +2 -1
  79. package/src/topLevelInject.ts +1 -5
  80. package/src/types/api/checkAllFirmwareRelease.ts +46 -7
  81. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  82. package/src/types/api/export.ts +5 -18
  83. package/src/types/api/firmwareUpdate.ts +0 -76
  84. package/src/types/api/index.ts +6 -15
  85. package/src/types/api/openWalletSession.ts +12 -3
  86. package/src/types/device.ts +6 -0
  87. package/src/types/settings.ts +3 -13
  88. package/src/utils/networkUtils.ts +5 -3
  89. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -174
  90. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  91. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  92. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  93. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  94. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  95. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  96. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  97. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  98. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  99. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  100. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  101. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  102. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  103. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  104. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  105. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  106. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  107. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  108. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  109. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  110. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  111. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  112. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  113. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  114. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  115. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  116. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  117. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  118. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  119. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  120. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  121. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  122. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  123. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -0,0 +1,251 @@
1
+ import { EFirmwareType } from '@onekeyfe/hd-shared';
2
+
3
+ import CheckAllFirmwareRelease, {
4
+ buildProtocolV2FirmwareRelease,
5
+ } from '../src/api/CheckAllFirmwareRelease';
6
+ import { DataManager } from '../src/data-manager';
7
+ import { createCoreApi } from '../src/inject';
8
+
9
+ import type { CoreApi } from '../src/types/api';
10
+ import type { DeviceStateVersions, IFirmwareReleaseInfo } from '../src/types';
11
+
12
+ jest.mock('../src/data/config', () => ({
13
+ DEFAULT_DOMAIN: 'https://example.com/',
14
+ getSDKVersion: () => '0.0.0-test',
15
+ }));
16
+
17
+ const currentVersions: DeviceStateVersions = {
18
+ firmware: '1.0.0',
19
+ bootloader: '1.0.0',
20
+ board: '1.0.0',
21
+ ble: '1.0.0',
22
+ se01: '1.0.0',
23
+ se02: '2.0.0',
24
+ se03: null,
25
+ se04: '1.0.0',
26
+ };
27
+
28
+ const release: IFirmwareReleaseInfo = {
29
+ required: true,
30
+ version: [2, 0, 0],
31
+ url: 'https://example.com/application-p1.bin',
32
+ fingerprint: 'release-fingerprint',
33
+ changelog: {
34
+ 'zh-CN': '更新',
35
+ 'en-US': 'Update',
36
+ },
37
+ installOrder: ['bootloader', 'applicationP1', 'se02', 'se03', 'romloader'],
38
+ components: {
39
+ bootloader: {
40
+ target: 'BOOTLOADER',
41
+ url: 'https://example.com/bootloader.bin',
42
+ version: [1, 1, 0],
43
+ },
44
+ applicationP1: {
45
+ target: 'APPLICATION_P1',
46
+ url: 'https://example.com/application-p1.bin',
47
+ version: [2, 0, 0],
48
+ },
49
+ se02: {
50
+ target: 'SE02',
51
+ url: 'https://example.com/se02.bin',
52
+ version: [2, 0, 0],
53
+ },
54
+ se03: {
55
+ target: 'SE03',
56
+ url: 'https://example.com/se03.bin',
57
+ version: [1, 0, 0],
58
+ },
59
+ romloader: {
60
+ target: 'ROMLOADER',
61
+ url: 'https://example.com/romloader.bin',
62
+ version: [2, 0, 0],
63
+ },
64
+ },
65
+ resourceBundles: [
66
+ {
67
+ name: 'images',
68
+ url: 'https://example.com/images.okpkg',
69
+ devicePath: 'vol0:/bundles/images/images.okpkg',
70
+ version: [1, 0, 0],
71
+ },
72
+ ],
73
+ };
74
+
75
+ describe('checkAllFirmwareRelease Protocol V2 support', () => {
76
+ test('builds ordered firmwareUpdateV4 targets from recommended component versions', () => {
77
+ const result = buildProtocolV2FirmwareRelease({
78
+ currentVersions,
79
+ firmwareType: EFirmwareType.Universal,
80
+ release,
81
+ });
82
+
83
+ expect(result).toMatchObject({
84
+ status: 'required',
85
+ hasUpgrade: true,
86
+ required: true,
87
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
88
+ components: [
89
+ { configKey: 'bootloader', status: 'outdated', updateTarget: 'boot' },
90
+ { configKey: 'applicationP1', status: 'outdated', updateTarget: 'app_v1' },
91
+ { configKey: 'se02', status: 'valid', updateTarget: 'se02' },
92
+ { configKey: 'se03', status: 'unknown', updateTarget: 'se03' },
93
+ { configKey: 'romloader', status: 'unsupported', updateTarget: null },
94
+ ],
95
+ });
96
+ });
97
+
98
+ test('returns an unavailable plan when no Pro2 release is configured', () => {
99
+ expect(
100
+ buildProtocolV2FirmwareRelease({
101
+ currentVersions,
102
+ firmwareType: EFirmwareType.Universal,
103
+ release: undefined,
104
+ })
105
+ ).toMatchObject({
106
+ status: 'unavailable',
107
+ hasUpgrade: false,
108
+ required: false,
109
+ components: [],
110
+ targetsToUpdate: [],
111
+ });
112
+ });
113
+
114
+ test('detects same-version package hotfixes by P1 and P2 payload hash', () => {
115
+ const packageSet: IFirmwareReleaseInfo = {
116
+ ...release,
117
+ resourceBundles: undefined,
118
+ installOrder: ['applicationP1', 'applicationP2'],
119
+ components: {
120
+ applicationP1: {
121
+ target: 'APPLICATION_P1',
122
+ version: [1, 0, 0],
123
+ url: 'https://example.com/application-p1.okpkg',
124
+ fingerprint: 'file-sha256-p1',
125
+ },
126
+ applicationP2: {
127
+ target: 'APPLICATION_P2',
128
+ version: [1, 0, 0],
129
+ url: 'https://example.com/application-p2.okpkg',
130
+ fingerprint: 'file-sha256-p2',
131
+ },
132
+ },
133
+ };
134
+
135
+ const result = buildProtocolV2FirmwareRelease({
136
+ currentVersions: {
137
+ ...currentVersions,
138
+ applicationP1: '1.0.0',
139
+ applicationP2: '1.0.0',
140
+ },
141
+ currentVerification: {
142
+ applicationP1Hash: 'aa'.repeat(32),
143
+ applicationP2Hash: 'bb'.repeat(32),
144
+ },
145
+ remotePayloadHashes: {
146
+ applicationP1: 'aa'.repeat(64),
147
+ applicationP2: 'cc'.repeat(64),
148
+ },
149
+ firmwareType: EFirmwareType.Universal,
150
+ release: packageSet,
151
+ });
152
+
153
+ expect(result).toMatchObject({
154
+ status: 'required',
155
+ targetsToUpdate: ['app_v2'],
156
+ components: [
157
+ { configKey: 'applicationP1', status: 'valid' },
158
+ { configKey: 'applicationP2', status: 'outdated' },
159
+ ],
160
+ });
161
+ });
162
+
163
+ test('updates both application packages when normal mode only reports P1', () => {
164
+ const packageSet: IFirmwareReleaseInfo = {
165
+ ...release,
166
+ resourceBundles: undefined,
167
+ installOrder: ['applicationP1', 'applicationP2'],
168
+ components: {
169
+ applicationP1: {
170
+ target: 'APPLICATION_P1',
171
+ version: [1, 0, 0],
172
+ url: 'https://example.com/application-p1.okpkg',
173
+ fingerprint: 'file-sha256-p1',
174
+ },
175
+ applicationP2: {
176
+ target: 'APPLICATION_P2',
177
+ version: [1, 0, 0],
178
+ url: 'https://example.com/application-p2.okpkg',
179
+ fingerprint: 'file-sha256-p2',
180
+ },
181
+ },
182
+ };
183
+
184
+ const result = buildProtocolV2FirmwareRelease({
185
+ currentVersions,
186
+ currentVerification: { applicationP1Hash: 'aa'.repeat(32) },
187
+ remotePayloadHashes: { applicationP1: 'cc'.repeat(64) },
188
+ firmwareType: EFirmwareType.Universal,
189
+ release: packageSet,
190
+ });
191
+
192
+ expect(result.targetsToUpdate).toEqual(['app_v1', 'app_v2']);
193
+ });
194
+
195
+ test('uses the existing method on Protocol V2 and keeps its public API name', async () => {
196
+ const method = new CheckAllFirmwareRelease({
197
+ id: 1,
198
+ payload: {
199
+ method: 'checkAllFirmwareRelease',
200
+ firmwareType: EFirmwareType.Universal,
201
+ },
202
+ });
203
+ method.init();
204
+ const getDeviceState = jest.fn().mockResolvedValue({
205
+ identity: {
206
+ deviceType: 'pro2',
207
+ firmwareType: EFirmwareType.Universal,
208
+ },
209
+ status: { mode: 'normal' },
210
+ versions: currentVersions,
211
+ });
212
+ method.device = {
213
+ isProtocolV2: () => true,
214
+ features: {
215
+ deviceType: 'pro2',
216
+ firmwareVersion: '1.0.0',
217
+ },
218
+ getDeviceState,
219
+ } as unknown as CheckAllFirmwareRelease['device'];
220
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
221
+
222
+ await expect(method.run()).resolves.toMatchObject({
223
+ protocol: 'V2',
224
+ deviceType: 'pro2',
225
+ status: 'required',
226
+ targetsToUpdate: ['boot', 'app_v1', 'resource'],
227
+ firmware: {
228
+ status: 'required',
229
+ },
230
+ });
231
+ expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
232
+ });
233
+
234
+ test('continues forwarding the existing public method', async () => {
235
+ const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
236
+ const api = createCoreApi(call as CoreApi['call']) as CoreApi;
237
+
238
+ await api.checkAllFirmwareRelease('pro2-connect-id', {
239
+ firmwareType: EFirmwareType.Universal,
240
+ retryCount: 0,
241
+ });
242
+
243
+ expect(call).toHaveBeenCalledWith({
244
+ connectId: 'pro2-connect-id',
245
+ firmwareType: EFirmwareType.Universal,
246
+ retryCount: 0,
247
+ method: 'checkAllFirmwareRelease',
248
+ });
249
+ expect(api).not.toHaveProperty('checkAllFirmwareReleaseV4');
250
+ });
251
+ });
@@ -1,61 +1,19 @@
1
- import DataManager from '../src/data-manager/DataManager';
2
1
  import { parseConnectSettings } from '../src/data-manager/connectSettings';
3
2
 
4
- import type { RemoteConfigResponse } from '../src/types';
5
-
6
- jest.mock('axios', () => ({
7
- get: jest.fn(),
8
- }));
9
-
10
3
  jest.mock('../src/data/config', () => ({
11
4
  getSDKVersion: jest.fn(() => '1.0.0'),
12
5
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
13
6
  }));
14
7
 
15
- const mockAxiosGet: jest.Mock = jest.requireMock('axios').get;
16
-
17
8
  describe('parseConnectSettings', () => {
18
- const preloadedConfig = {
19
- bridge: {},
20
- classic: { firmware: [], ble: [] },
21
- classic1s: { firmware: [], ble: [] },
22
- classicpure: { firmware: [], ble: [] },
23
- mini: { firmware: [], ble: [] },
24
- touch: { firmware: [], ble: [] },
25
- pro: { firmware: [], ble: [] },
26
- pro2: { firmware: [], ble: [] },
27
- } as unknown as RemoteConfigResponse;
28
-
29
- it('keeps a serializable preloaded config in parsed settings', () => {
30
- const settings = parseConnectSettings({
31
- firmwareManifestMode: 'sdk-managed',
32
- preloadedConfig,
33
- });
34
-
35
- expect(settings.firmwareManifestMode).toBe('sdk-managed');
36
- expect(settings.preloadedConfig).toBe(preloadedConfig);
37
- });
38
-
39
- it('uses the preloaded config before the SDK network client', async () => {
40
- const settings = parseConnectSettings({
41
- firmwareManifestMode: 'sdk-managed',
42
- preloadedConfig,
43
- });
44
-
45
- await DataManager.load(settings);
46
-
47
- expect(mockAxiosGet).not.toHaveBeenCalled();
48
- expect(DataManager.deviceMap.classic).toEqual({ firmware: [], ble: [] });
49
- });
9
+ it('keeps custom configFetcher in parsed settings', () => {
10
+ const configFetcher = jest.fn();
50
11
 
51
- it('fails closed without a preloaded config in external-only mode', async () => {
52
12
  const settings = parseConnectSettings({
53
- firmwareManifestMode: 'external-only',
13
+ fetchConfig: true,
14
+ configFetcher,
54
15
  });
55
16
 
56
- await expect(DataManager.load(settings)).rejects.toThrow(
57
- 'External firmware config snapshot is required'
58
- );
59
- expect(mockAxiosGet).not.toHaveBeenCalled();
17
+ expect(settings.configFetcher).toBe(configFetcher);
60
18
  });
61
19
  });
@@ -111,7 +111,8 @@ describe('DeviceStateMapper', () => {
111
111
  protocol_version: 2,
112
112
  hw: { serial_no: 'SERIAL-1' },
113
113
  fw: {
114
- application: { version: '5.0.0' },
114
+ application: { version: '5.0.0', build_id: 'p1-build', hash: [0x01, 0x02] },
115
+ application_data: { version: '5.0.1', build_id: 'p2-build', hash: [0x03, 0x04] },
115
116
  bootloader: { version: '2.0.0' },
116
117
  },
117
118
  coprocessor: {
@@ -129,9 +130,17 @@ describe('DeviceStateMapper', () => {
129
130
  });
130
131
  expect(patch.versions).toMatchObject({
131
132
  firmware: '5.0.0',
133
+ applicationP1: '5.0.0',
134
+ applicationP2: '5.0.1',
132
135
  bootloader: '2.0.0',
133
136
  ble: '1.2.3',
134
137
  });
138
+ expect(patch.verification).toMatchObject({
139
+ applicationP1BuildId: 'p1-build',
140
+ applicationP1Hash: '0102',
141
+ applicationP2BuildId: 'p2-build',
142
+ applicationP2Hash: '0304',
143
+ });
135
144
  expect(patch).toMatchObject({ protocolVersion: 2 });
136
145
  expect(patch.status?.unlocked).toBeUndefined();
137
146
  });
@@ -1,8 +1,8 @@
1
- import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import FirmwareUpdateV2 from '../../src/api/FirmwareUpdateV2';
4
4
  import { getBinary } from '../../src/api/firmware/getBinary';
5
- import { uploadFirmwareFromSource } from '../../src/api/firmware/uploadFirmware';
5
+ import { uploadFirmware } from '../../src/api/firmware/uploadFirmware';
6
6
  import * as utils from '../../src/utils';
7
7
 
8
8
  jest.mock('../../src/data/config', () => ({
@@ -18,8 +18,7 @@ jest.mock('../../src/api/firmware/getBinary', () => ({
18
18
 
19
19
  jest.mock('../../src/api/firmware/uploadFirmware', () => ({
20
20
  updateResources: jest.fn(),
21
- updateResourcesFromSources: jest.fn(),
22
- uploadFirmwareFromSource: jest.fn(),
21
+ uploadFirmware: jest.fn(),
23
22
  }));
24
23
 
25
24
  jest.mock('../../src/device/DevicePool', () => ({
@@ -30,9 +29,7 @@ jest.mock('../../src/device/DevicePool', () => ({
30
29
  }));
31
30
 
32
31
  const mockGetBinary = getBinary as jest.MockedFunction<typeof getBinary>;
33
- const mockUploadFirmwareFromSource = uploadFirmwareFromSource as jest.MockedFunction<
34
- typeof uploadFirmwareFromSource
35
- >;
32
+ const mockUploadFirmware = uploadFirmware as jest.MockedFunction<typeof uploadFirmware>;
36
33
 
37
34
  type DeviceType = 'CLASSIC1S' | 'PURE';
38
35
 
@@ -178,26 +175,7 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
178
175
  expect(mockGetBinary).toHaveBeenCalledTimes(1);
179
176
  expectNoFirmwareMutationCalls(typedCall);
180
177
  expect(acquire).not.toHaveBeenCalled();
181
- expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
182
- });
183
-
184
- it('preserves structured preparation errors before any firmware mutation', async () => {
185
- const preparationError = ERRORS.TypedError(
186
- HardwareErrorCode.RuntimeError,
187
- 'Firmware must be prepared by the external firmware host',
188
- {
189
- firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
190
- artifactName: 'firmware',
191
- }
192
- );
193
- mockGetBinary.mockRejectedValue(preparationError);
194
- const { method, typedCall, acquire } = createMethod();
195
-
196
- await expect(method.run()).rejects.toBe(preparationError);
197
-
198
- expectNoFirmwareMutationCalls(typedCall);
199
- expect(acquire).not.toHaveBeenCalled();
200
- expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
178
+ expect(mockUploadFirmware).not.toHaveBeenCalled();
201
179
  });
202
180
 
203
181
  it.each([
@@ -238,11 +216,9 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
238
216
  binary: firmwareBinary,
239
217
  } as Awaited<ReturnType<typeof getBinary>>);
240
218
  });
241
- mockUploadFirmwareFromSource.mockImplementation(() => {
219
+ mockUploadFirmware.mockImplementation(() => {
242
220
  callOrder.push('upload');
243
- return Promise.resolve(
244
- expectedResponse as Awaited<ReturnType<typeof uploadFirmwareFromSource>>
245
- );
221
+ return Promise.resolve(expectedResponse as Awaited<ReturnType<typeof uploadFirmware>>);
246
222
  });
247
223
  const { method, typedCall } = createMethod({
248
224
  updateType,
@@ -272,15 +248,15 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
272
248
  },
273
249
  })
274
250
  );
275
- expect(mockUploadFirmwareFromSource).toHaveBeenCalledWith(
251
+ expect(mockUploadFirmware).toHaveBeenCalledWith(
276
252
  updateType,
277
253
  expect.any(Function),
278
254
  expect.any(Function),
279
255
  method.device,
280
- expect.objectContaining({
281
- size: firmwareBinary.byteLength,
282
- }),
283
- true,
256
+ {
257
+ payload: firmwareBinary,
258
+ rebootOnSuccess: true,
259
+ },
284
260
  isUpdateBootloader
285
261
  );
286
262
  }
@@ -295,11 +271,11 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
295
271
  binary: firmwareBinary,
296
272
  } as Awaited<ReturnType<typeof getBinary>>);
297
273
  });
298
- mockUploadFirmwareFromSource.mockImplementation(() => {
274
+ mockUploadFirmware.mockImplementation(() => {
299
275
  callOrder.push('upload');
300
276
  return Promise.resolve({
301
277
  success: true,
302
- } as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
278
+ } as Awaited<ReturnType<typeof uploadFirmware>>);
303
279
  });
304
280
  const { method, typedCall, acquire } = createMethod({
305
281
  bootloaderMode: true,
@@ -324,9 +300,9 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
324
300
  resolveDownload = resolve;
325
301
  })
326
302
  );
327
- mockUploadFirmwareFromSource.mockResolvedValue({
303
+ mockUploadFirmware.mockResolvedValue({
328
304
  success: true,
329
- } as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
305
+ } as Awaited<ReturnType<typeof uploadFirmware>>);
330
306
  const { method, typedCall } = createMethod();
331
307
 
332
308
  const runPromise = method.run();
@@ -357,7 +333,7 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
357
333
 
358
334
  expectNoFirmwareMutationCalls(typedCall);
359
335
  expect(acquire).not.toHaveBeenCalled();
360
- expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
336
+ expect(mockUploadFirmware).not.toHaveBeenCalled();
361
337
  });
362
338
 
363
339
  it.each([
@@ -366,23 +342,23 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
366
342
  ])(
367
343
  'keeps the non-empty %s binary overload and never downloads it again',
368
344
  async (_kind, binary) => {
369
- mockUploadFirmwareFromSource.mockResolvedValue({
345
+ mockUploadFirmware.mockResolvedValue({
370
346
  success: true,
371
- } as Awaited<ReturnType<typeof uploadFirmwareFromSource>>);
347
+ } as Awaited<ReturnType<typeof uploadFirmware>>);
372
348
  const { method } = createMethod({ binary });
373
349
 
374
350
  await method.run();
375
351
 
376
352
  expect(mockGetBinary).not.toHaveBeenCalled();
377
- expect(mockUploadFirmwareFromSource).toHaveBeenCalledWith(
353
+ expect(mockUploadFirmware).toHaveBeenCalledWith(
378
354
  'firmware',
379
355
  expect.any(Function),
380
356
  expect.any(Function),
381
357
  method.device,
382
- expect.objectContaining({
383
- size: binary.byteLength,
384
- }),
385
- true,
358
+ {
359
+ payload: binary,
360
+ rebootOnSuccess: true,
361
+ },
386
362
  undefined
387
363
  );
388
364
  }
@@ -393,21 +369,17 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
393
369
  ['DataView', new DataView(new Uint8Array([9, 3, 4, 9]).buffer, 1, 2), [3, 4]],
394
370
  ['custom Buffer', createCustomBuffer(4), [1, 2, 3, 4]],
395
371
  ])('normalizes a non-empty %s before rebooting', async (_kind, binary, expectedBytes) => {
396
- let uploadedBytes: number[] = [];
397
- mockUploadFirmwareFromSource.mockImplementation(
398
- async (_updateType, _typedCall, _postMessage, _device, source) => {
399
- uploadedBytes = Array.from(new Uint8Array(await source.readAt(0, source.size)));
400
- return {
401
- success: true,
402
- } as Awaited<ReturnType<typeof uploadFirmwareFromSource>>;
403
- }
404
- );
372
+ mockUploadFirmware.mockResolvedValue({
373
+ success: true,
374
+ } as Awaited<ReturnType<typeof uploadFirmware>>);
405
375
  const { method } = createMethod({ binary });
406
376
 
407
377
  await method.run();
408
378
 
409
379
  expect(mockGetBinary).not.toHaveBeenCalled();
410
- expect(uploadedBytes).toEqual(expectedBytes);
380
+ const normalizedBinary = mockUploadFirmware.mock.calls[0][4].payload;
381
+ expect(normalizedBinary).toBeInstanceOf(ArrayBuffer);
382
+ expect(Array.from(new Uint8Array(normalizedBinary))).toEqual(expectedBytes);
411
383
  });
412
384
 
413
385
  it.each([
@@ -425,6 +397,6 @@ describe('FirmwareUpdateV2 download-before-reboot safety', () => {
425
397
  expect(mockGetBinary).not.toHaveBeenCalled();
426
398
  expectNoFirmwareMutationCalls(typedCall);
427
399
  expect(acquire).not.toHaveBeenCalled();
428
- expect(mockUploadFirmwareFromSource).not.toHaveBeenCalled();
400
+ expect(mockUploadFirmware).not.toHaveBeenCalled();
429
401
  });
430
402
  });
@@ -27,6 +27,26 @@ describe('networkUtils httpRequest retry bounds', () => {
27
27
  expect(requestSpy.mock.calls[0][0]).not.toHaveProperty('onDownloadProgress');
28
28
  });
29
29
 
30
+ it('accepts partial content and forwards byte-range headers', async () => {
31
+ const packageHeader = new ArrayBuffer(640);
32
+ const requestSpy = jest.spyOn(axios, 'request').mockResolvedValue({
33
+ status: 206,
34
+ data: packageHeader,
35
+ });
36
+
37
+ await expect(
38
+ httpRequest('https://example.com/application-p1.okpkg', 'binary', {
39
+ headers: { Range: 'bytes=0-639' },
40
+ })
41
+ ).resolves.toBe(packageHeader);
42
+
43
+ expect(requestSpy).toHaveBeenCalledWith(
44
+ expect.objectContaining({
45
+ headers: { Range: 'bytes=0-639' },
46
+ })
47
+ );
48
+ });
49
+
30
50
  it('passes the per-attempt timeout and stops after the configured retry count', async () => {
31
51
  const timeoutError = Object.assign(new Error('timeout'), {
32
52
  code: 'ECONNABORTED',
@@ -101,12 +101,15 @@ describe('openWalletSession', () => {
101
101
  expect(promptPassphrase).toHaveBeenCalledWith(
102
102
  {
103
103
  existsAttachPinUser: false,
104
+ deviceOnly: true,
104
105
  source: 'wallet-session-coordinator',
105
106
  reason: 'open-wallet',
106
107
  },
107
108
  { cancelDeviceOnReject: false }
108
109
  );
109
- expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
110
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
111
+ on_device: true,
112
+ });
110
113
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
111
114
  });
112
115
 
@@ -221,7 +224,9 @@ describe('openWalletSession', () => {
221
224
  passphraseState: 'hidden-state',
222
225
  });
223
226
  expect(promptPassphrase).toHaveBeenCalled();
224
- expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
227
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
228
+ on_device: true,
229
+ });
225
230
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
226
231
  });
227
232
 
@@ -298,7 +303,9 @@ describe('openWalletSession', () => {
298
303
  expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
299
304
  eventless_wallet_session: true,
300
305
  });
301
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
306
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
307
+ on_device: true,
308
+ });
302
309
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
303
310
  expect(promptPassphrase).toHaveBeenCalled();
304
311
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
@@ -590,7 +597,9 @@ describe('openWalletSession', () => {
590
597
  passphraseState: 'hidden-state',
591
598
  resumed: false,
592
599
  });
593
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
600
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
601
+ on_device: true,
602
+ });
594
603
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
595
604
  expect(promptPassphrase).toHaveBeenCalled();
596
605
  expect(device.passphraseState).toBeUndefined();
@@ -616,7 +625,9 @@ describe('openWalletSession', () => {
616
625
  walletType: 'hidden',
617
626
  passphraseState: 'device-state',
618
627
  });
619
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
628
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
629
+ on_device: true,
630
+ });
620
631
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
621
632
  });
622
633
 
@@ -649,6 +660,7 @@ describe('openWalletSession', () => {
649
660
  expect(promptPassphrase).toHaveBeenCalledWith(
650
661
  {
651
662
  existsAttachPinUser: false,
663
+ deviceOnly: true,
652
664
  source: 'wallet-session-coordinator',
653
665
  reason: 'open-wallet',
654
666
  },
@@ -656,6 +668,7 @@ describe('openWalletSession', () => {
656
668
  );
657
669
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
658
670
  passphrase: 'host hidden wallet',
671
+ on_device: false,
659
672
  });
660
673
  });
661
674
 
@@ -682,6 +695,7 @@ describe('openWalletSession', () => {
682
695
  expect(promptPassphrase).toHaveBeenCalledWith(
683
696
  {
684
697
  existsAttachPinUser: true,
698
+ deviceOnly: true,
685
699
  source: 'wallet-session-coordinator',
686
700
  reason: 'open-wallet',
687
701
  },
@@ -1,4 +1,10 @@
1
1
  import { ProtocolV2FirmwareTargetType } from '../src/protocols/protocol-v2/firmware';
2
+ import { isProtocolV2FirmwareFingerprintValid } from '../src/api/FirmwareUpdateV4';
3
+
4
+ jest.mock('../src/data/config', () => ({
5
+ DEFAULT_DOMAIN: 'https://example.com/',
6
+ getSDKVersion: () => '0.0.0-test',
7
+ }));
2
8
 
3
9
  describe('Protocol V2 firmware target contract', () => {
4
10
  test('matches the current firmware-pro2 target ids', () => {
@@ -16,4 +22,16 @@ describe('Protocol V2 firmware target contract', () => {
16
22
  FW_MGMT_TARGET_SE04: 10,
17
23
  });
18
24
  });
25
+
26
+ test('validates the full package SHA-256 fingerprint from config', () => {
27
+ const binary = Uint8Array.from([1, 2, 3]);
28
+
29
+ expect(
30
+ isProtocolV2FirmwareFingerprintValid(
31
+ binary,
32
+ '039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81'
33
+ )
34
+ ).toBe(true);
35
+ expect(isProtocolV2FirmwareFingerprintValid(binary, '00'.repeat(32))).toBe(false);
36
+ });
19
37
  });