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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +678 -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 +2198 -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 +19 -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 +2135 -1470
  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 +918 -741
  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
@@ -0,0 +1,678 @@
1
+ import { ERRORS, 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('writes the USB Request before polling current install status', async () => {
18
+ const method = new FirmwareUpdateV4({
19
+ id: 1,
20
+ payload: {
21
+ method: 'firmwareUpdateV4',
22
+ connectId: 'pro2-usb',
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: { records: [] },
32
+ })
33
+ .mockResolvedValueOnce({
34
+ type: 'DeviceFirmwareUpdateStatus',
35
+ message: {
36
+ records: [
37
+ {
38
+ target_id: 4,
39
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
40
+ progress_percent: 42,
41
+ phase_info: {
42
+ phase: 'FW_MGMT_UPDATER_PHASE_INSTALL',
43
+ progress_percent: 60,
44
+ },
45
+ path: 'vol0:/application_p1.bin',
46
+ },
47
+ ],
48
+ },
49
+ })
50
+ .mockResolvedValueOnce({
51
+ type: 'DeviceFirmwareUpdateStatus',
52
+ message: {
53
+ records: [
54
+ {
55
+ target_id: 4,
56
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
57
+ path: 'vol0:/application_p1.bin',
58
+ },
59
+ ],
60
+ },
61
+ });
62
+ const call = jest.fn().mockResolvedValue({
63
+ type: 'WriteCompleted',
64
+ message: {},
65
+ });
66
+ const setCancelableAction = jest.fn();
67
+
68
+ method.device = {
69
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
70
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
71
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
72
+ setCancelableAction,
73
+ clearCancelableAction: jest.fn(),
74
+ } as unknown as Device;
75
+ method.postMessage = jest.fn();
76
+ method.postTipMessage = jest.fn();
77
+ method.postProgressMessage = jest.fn();
78
+
79
+ const firmwareUpdate = method as unknown as {
80
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
81
+ waitForProtocolV2FirmwareUpdateComplete: (
82
+ value: typeof targets,
83
+ requireCurrentInstallStatus: boolean
84
+ ) => Promise<void>;
85
+ reconnectProtocolV2Device: () => Promise<void>;
86
+ verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
87
+ };
88
+ firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
89
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue({});
90
+ (method as any).isBleReconnect = jest.fn(() => false);
91
+
92
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
93
+
94
+ expect(typedCall.mock.calls[0]).toEqual(['DeviceFirmwareUpdateStage', 'Success', { targets }]);
95
+ expect(call).toHaveBeenCalledWith(
96
+ 'DeviceFirmwareUpdateRequest',
97
+ {},
98
+ expect.objectContaining({
99
+ returnAfterWrite: true,
100
+ expectedTypes: ['Success'],
101
+ onResponseAfterWrite: expect.any(Function),
102
+ })
103
+ );
104
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
105
+
106
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
107
+
108
+ expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
109
+ expect(typedCall.mock.calls[1]?.[2]).toEqual({
110
+ fields: {
111
+ status: true,
112
+ progress_percent: true,
113
+ phase_info: true,
114
+ payload_version: true,
115
+ path: true,
116
+ },
117
+ });
118
+ expect(method.postProgressMessage).toHaveBeenCalledWith(42, 'installingFirmware', {
119
+ installTargetId: 4,
120
+ installPhase: 'install',
121
+ installPhaseProgress: 60,
122
+ });
123
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
124
+ expect(call.mock.invocationCallOrder[0]).toBeLessThan(typedCall.mock.invocationCallOrder[1]);
125
+ expect(setCancelableAction).toHaveBeenCalledTimes(2);
126
+ });
127
+
128
+ test('accepts path-less finished USB status after the Request terminal Success arrives', async () => {
129
+ const method = new FirmwareUpdateV4({
130
+ id: 1,
131
+ payload: {
132
+ method: 'firmwareUpdateV4',
133
+ connectId: 'pro2-usb',
134
+ },
135
+ });
136
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
137
+ const typedCall = jest
138
+ .fn()
139
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
140
+ .mockResolvedValueOnce({
141
+ type: 'DeviceFirmwareUpdateStatus',
142
+ message: {
143
+ records: [
144
+ {
145
+ target_id: 4,
146
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
147
+ },
148
+ ],
149
+ },
150
+ });
151
+ const call = jest.fn().mockResolvedValue({
152
+ type: 'WriteCompleted',
153
+ message: {},
154
+ });
155
+
156
+ method.device = {
157
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
158
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
159
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
160
+ setCancelableAction: jest.fn(),
161
+ clearCancelableAction: jest.fn(),
162
+ } as unknown as Device;
163
+ method.postMessage = jest.fn();
164
+ method.postProgressMessage = jest.fn();
165
+
166
+ const firmwareUpdate = method as unknown as {
167
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
168
+ waitForProtocolV2FirmwareUpdateComplete: (
169
+ value: typeof targets,
170
+ requireCurrentInstallStatus: boolean
171
+ ) => Promise<void>;
172
+ };
173
+
174
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
175
+ const requestOptions = call.mock.calls[0]?.[2] as {
176
+ onResponseAfterWrite: (response: { type: 'Success'; message: Record<string, never> }) => void;
177
+ };
178
+ requestOptions.onResponseAfterWrite({ type: 'Success', message: {} });
179
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
180
+
181
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
182
+ });
183
+
184
+ test('reconnects USB when the link is released while writing the Request', async () => {
185
+ const method = new FirmwareUpdateV4({
186
+ id: 1,
187
+ payload: {
188
+ method: 'firmwareUpdateV4',
189
+ connectId: 'pro2-usb',
190
+ },
191
+ });
192
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
193
+ const typedCall = jest
194
+ .fn()
195
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
196
+ .mockResolvedValueOnce({
197
+ type: 'DeviceFirmwareUpdateStatus',
198
+ message: {
199
+ records: [
200
+ {
201
+ target_id: 4,
202
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
203
+ path: 'vol0:/application_p1.bin',
204
+ },
205
+ ],
206
+ },
207
+ })
208
+ .mockResolvedValueOnce({
209
+ type: 'DeviceFirmwareUpdateStatus',
210
+ message: {
211
+ records: [
212
+ {
213
+ target_id: 4,
214
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
215
+ path: 'vol0:/application_p1.bin',
216
+ },
217
+ ],
218
+ },
219
+ });
220
+ const call = jest.fn().mockRejectedValue(new Error('device was disconnected'));
221
+
222
+ method.device = {
223
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
224
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
225
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
226
+ setCancelableAction: jest.fn(),
227
+ clearCancelableAction: jest.fn(),
228
+ } as unknown as Device;
229
+ method.postMessage = jest.fn();
230
+ method.postProgressMessage = jest.fn();
231
+
232
+ const firmwareUpdate = method as unknown as {
233
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
234
+ waitForProtocolV2FirmwareUpdateComplete: (
235
+ value: typeof targets,
236
+ requireCurrentInstallStatus: boolean
237
+ ) => Promise<void>;
238
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
239
+ verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
240
+ };
241
+ firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
242
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue({});
243
+ (method as any).isBleReconnect = jest.fn(() => false);
244
+
245
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
246
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
247
+
248
+ expect(firmwareUpdate.reconnectProtocolV2Device).toHaveBeenCalledWith({
249
+ skipProtocolProbe: true,
250
+ });
251
+ expect(firmwareUpdate.verifyProtocolV2ReconnectIdentity).toHaveBeenCalledTimes(1);
252
+ expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
253
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
254
+ });
255
+
256
+ test('writes the BLE Request and completes only from target status polling', async () => {
257
+ const method = new FirmwareUpdateV4({
258
+ id: 1,
259
+ payload: {
260
+ method: 'firmwareUpdateV4',
261
+ connectId: 'pro2-ble',
262
+ },
263
+ });
264
+ const targets = [{ target_id: 6, path: 'vol0:/coprocessor.bin' }];
265
+ const typedCall = jest
266
+ .fn()
267
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
268
+ .mockResolvedValueOnce({
269
+ type: 'DeviceFirmwareUpdateStatus',
270
+ message: {
271
+ records: [
272
+ {
273
+ target_id: 6,
274
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
275
+ path: 'vol0:/coprocessor.bin',
276
+ },
277
+ ],
278
+ },
279
+ })
280
+ .mockResolvedValueOnce({
281
+ type: 'DeviceFirmwareUpdateStatus',
282
+ message: {
283
+ records: [
284
+ {
285
+ target_id: 6,
286
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
287
+ path: 'vol0:/coprocessor.bin',
288
+ },
289
+ ],
290
+ },
291
+ });
292
+ const call = jest.fn().mockResolvedValue({
293
+ type: 'WriteCompleted',
294
+ message: {},
295
+ });
296
+
297
+ method.device = {
298
+ getCommands: () => ({ typedCall, call }),
299
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
300
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
301
+ setCancelableAction: jest.fn(),
302
+ clearCancelableAction: jest.fn(),
303
+ } as unknown as Device;
304
+ method.postMessage = jest.fn();
305
+ method.postProgressMessage = jest.fn();
306
+ (method as any).isBleReconnect = jest.fn(() => true);
307
+
308
+ const firmwareUpdate = method as unknown as {
309
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
310
+ waitForProtocolV2FirmwareUpdateComplete: (
311
+ value: typeof targets,
312
+ requireCurrentInstallStatus: boolean
313
+ ) => Promise<void>;
314
+ };
315
+
316
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
317
+
318
+ expect(typedCall).toHaveBeenCalledTimes(1);
319
+ expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateStage', 'Success', { targets });
320
+ expect(call).toHaveBeenCalledWith(
321
+ 'DeviceFirmwareUpdateRequest',
322
+ {},
323
+ expect.objectContaining({
324
+ returnAfterWrite: true,
325
+ expectedTypes: ['Success'],
326
+ onResponseAfterWrite: expect.any(Function),
327
+ })
328
+ );
329
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
330
+
331
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
332
+
333
+ expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
334
+ expect(typedCall.mock.calls[1]?.[1]).toBe('DeviceFirmwareUpdateStatus');
335
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
336
+ });
337
+
338
+ test('accepts stable finished BLE status after an actual install disconnect hides in-progress', async () => {
339
+ const method = new FirmwareUpdateV4({
340
+ id: 1,
341
+ payload: {
342
+ method: 'firmwareUpdateV4',
343
+ connectId: 'pro2-ble',
344
+ },
345
+ });
346
+ const targets = [{ target_id: 6, path: 'vol0:/coprocessor.bin' }];
347
+ const finishedStatus = {
348
+ type: 'DeviceFirmwareUpdateStatus',
349
+ message: {
350
+ records: [
351
+ {
352
+ target_id: 6,
353
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
354
+ payload_version: 65_556,
355
+ path: 'vol0:/coprocessor.bin',
356
+ },
357
+ ],
358
+ },
359
+ };
360
+ const typedCall = jest
361
+ .fn()
362
+ .mockResolvedValueOnce(finishedStatus)
363
+ .mockImplementationOnce(() => {
364
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
365
+ throw new Error('device was disconnected');
366
+ })
367
+ .mockResolvedValueOnce(finishedStatus)
368
+ .mockImplementationOnce(() => {
369
+ expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
370
+ expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
371
+ return finishedStatus;
372
+ })
373
+ .mockResolvedValueOnce(finishedStatus)
374
+ .mockResolvedValueOnce(finishedStatus)
375
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
376
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
377
+
378
+ (method as any).params = {
379
+ expectedTargetVersions: { coprocessor: '1.0.20' },
380
+ };
381
+
382
+ method.device = {
383
+ getCommands: () => ({ typedCall }),
384
+ setCancelableAction: jest.fn(),
385
+ } as unknown as Device;
386
+ method.postProgressMessage = jest.fn();
387
+
388
+ const firmwareUpdate = method as unknown as {
389
+ waitForProtocolV2FirmwareUpdateComplete: (
390
+ value: typeof targets,
391
+ requireCurrentInstallStatus: boolean
392
+ ) => Promise<void>;
393
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
394
+ };
395
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
396
+ (method as any).isBleReconnect = jest.fn(() => true);
397
+
398
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
399
+
400
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
401
+ expect(typedCall).toHaveBeenCalledTimes(6);
402
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
403
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
404
+ expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
405
+ }, 10_000);
406
+
407
+ test('rejects stable stale finished BLE status after a status response timeout', async () => {
408
+ const method = new FirmwareUpdateV4({
409
+ id: 1,
410
+ payload: {
411
+ method: 'firmwareUpdateV4',
412
+ connectId: 'pro2-ble',
413
+ },
414
+ });
415
+ const targets = [{ target_id: 6, path: 'vol0:/coprocessor.bin' }];
416
+ const finishedStatus = {
417
+ type: 'DeviceFirmwareUpdateStatus',
418
+ message: {
419
+ records: [
420
+ {
421
+ target_id: 6,
422
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
423
+ payload_version: 65_556,
424
+ path: 'vol0:/coprocessor.bin',
425
+ },
426
+ ],
427
+ },
428
+ };
429
+ const typedCall = jest
430
+ .fn()
431
+ .mockResolvedValueOnce(finishedStatus)
432
+ .mockRejectedValueOnce(
433
+ ERRORS.TypedError(
434
+ HardwareErrorCode.BleTimeoutError,
435
+ 'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
436
+ )
437
+ )
438
+ .mockResolvedValueOnce(finishedStatus)
439
+ .mockResolvedValueOnce(finishedStatus)
440
+ .mockResolvedValueOnce(finishedStatus)
441
+ .mockResolvedValueOnce(finishedStatus)
442
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
443
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
444
+
445
+ (method as any).params = {
446
+ expectedTargetVersions: { coprocessor: '1.0.20' },
447
+ };
448
+ method.device = {
449
+ getCommands: () => ({ typedCall }),
450
+ setCancelableAction: jest.fn(),
451
+ } as unknown as Device;
452
+ method.postProgressMessage = jest.fn();
453
+
454
+ const firmwareUpdate = method as unknown as {
455
+ waitForProtocolV2FirmwareUpdateComplete: (
456
+ value: typeof targets,
457
+ requireCurrentInstallStatus: boolean
458
+ ) => Promise<void>;
459
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
460
+ };
461
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
462
+ (method as any).isBleReconnect = jest.fn(() => true);
463
+
464
+ await expect(
465
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
466
+ ).rejects.toMatchObject({
467
+ errorCode: HardwareErrorCode.ActionCancelled,
468
+ });
469
+
470
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
471
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
472
+ }, 10_000);
473
+
474
+ test('confirms App mode when BLE status becomes empty after current install progress', async () => {
475
+ const method = new FirmwareUpdateV4({
476
+ id: 1,
477
+ payload: {
478
+ method: 'firmwareUpdateV4',
479
+ connectId: 'pro2-ble',
480
+ },
481
+ });
482
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
483
+ const typedCall = jest
484
+ .fn()
485
+ .mockResolvedValueOnce({
486
+ type: 'DeviceFirmwareUpdateStatus',
487
+ message: {
488
+ records: [
489
+ {
490
+ target_id: 4,
491
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
492
+ path: 'vol0:/application_p1.bin',
493
+ },
494
+ ],
495
+ },
496
+ })
497
+ .mockResolvedValueOnce({
498
+ type: 'DeviceFirmwareUpdateStatus',
499
+ message: { records: [] },
500
+ });
501
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
502
+ const deviceInfo = {};
503
+ const verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
504
+ const probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
505
+
506
+ method.device = {
507
+ getCommands: () => ({ typedCall }),
508
+ } as unknown as Device;
509
+ method.postProgressMessage = jest.fn();
510
+
511
+ const firmwareUpdate = method as unknown as {
512
+ protocolV2InstallNeedsReconnect: boolean;
513
+ waitForProtocolV2FirmwareUpdateComplete: (
514
+ value: typeof targets,
515
+ requireCurrentInstallStatus: boolean
516
+ ) => Promise<void>;
517
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
518
+ verifyProtocolV2ReconnectIdentity: () => Promise<typeof deviceInfo>;
519
+ probeProtocolV2NormalMode: (value: typeof deviceInfo) => Promise<boolean>;
520
+ };
521
+ firmwareUpdate.protocolV2InstallNeedsReconnect = true;
522
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
523
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
524
+ firmwareUpdate.probeProtocolV2NormalMode = probeProtocolV2NormalMode;
525
+ (method as any).isBleReconnect = jest.fn(() => true);
526
+
527
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
528
+
529
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
530
+ expect(verifyProtocolV2ReconnectIdentity).toHaveBeenCalledTimes(1);
531
+ expect(probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
532
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
533
+ });
534
+
535
+ test('does not send Request when Stage is rejected', async () => {
536
+ const method = new FirmwareUpdateV4({
537
+ id: 1,
538
+ payload: {
539
+ method: 'firmwareUpdateV4',
540
+ connectId: 'pro2-ble',
541
+ },
542
+ });
543
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
544
+ const typedCall = jest.fn().mockRejectedValue(new Error('stage rejected'));
545
+
546
+ method.device = {
547
+ getCommands: () => ({ typedCall }),
548
+ } as unknown as Device;
549
+
550
+ await expect(
551
+ (
552
+ method as unknown as {
553
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
554
+ }
555
+ ).protocolV2StartFirmwareUpdate({ targets })
556
+ ).rejects.toThrow('stage rejected');
557
+
558
+ expect(typedCall).toHaveBeenCalledTimes(1);
559
+ });
560
+
561
+ test('does not enter install state when the device cancels the Request', async () => {
562
+ const method = new FirmwareUpdateV4({
563
+ id: 1,
564
+ payload: {
565
+ method: 'firmwareUpdateV4',
566
+ connectId: 'pro2-usb',
567
+ },
568
+ });
569
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
570
+ const actionCancelledError = ERRORS.TypedError(HardwareErrorCode.ActionCancelled);
571
+ const typedCall = jest.fn().mockImplementation((type: string) => {
572
+ if (type === 'DeviceFirmwareUpdateStatusGet') {
573
+ return Promise.reject(actionCancelledError);
574
+ }
575
+ return Promise.resolve({ type: 'Success', message: {} });
576
+ });
577
+ const call = jest.fn().mockResolvedValue({
578
+ type: 'WriteCompleted',
579
+ message: {},
580
+ });
581
+
582
+ method.device = {
583
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
584
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
585
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
586
+ setCancelableAction: jest.fn(),
587
+ clearCancelableAction: jest.fn(),
588
+ } as unknown as Device;
589
+ method.postMessage = jest.fn();
590
+ method.postProgressMessage = jest.fn();
591
+
592
+ const firmwareUpdate = method as unknown as {
593
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
594
+ waitForProtocolV2FirmwareUpdateComplete: (
595
+ value: typeof targets,
596
+ requireCurrentInstallStatus: boolean
597
+ ) => Promise<void>;
598
+ };
599
+
600
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
601
+ await expect(
602
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
603
+ ).rejects.toMatchObject({
604
+ errorCode: HardwareErrorCode.ActionCancelled,
605
+ });
606
+
607
+ expect(call).toHaveBeenCalledWith(
608
+ 'DeviceFirmwareUpdateRequest',
609
+ {},
610
+ expect.objectContaining({ returnAfterWrite: true })
611
+ );
612
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
613
+ });
614
+
615
+ test('does not hide an explicit workflow cancellation during status polling', async () => {
616
+ const method = new FirmwareUpdateV4({
617
+ id: 1,
618
+ payload: {
619
+ method: 'firmwareUpdateV4',
620
+ connectId: 'pro2-ble',
621
+ },
622
+ });
623
+ const abortController = new AbortController();
624
+ abortController.abort();
625
+
626
+ method.abortSignal = abortController.signal;
627
+
628
+ await expect(
629
+ (
630
+ method as unknown as {
631
+ waitForProtocolV2FirmwareUpdateComplete: (
632
+ targets: Array<{ target_id: number; path: string }>
633
+ ) => Promise<void>;
634
+ }
635
+ ).waitForProtocolV2FirmwareUpdateComplete([
636
+ { target_id: 4, path: 'vol0:/application_p1.bin' },
637
+ ])
638
+ ).rejects.toMatchObject({
639
+ errorCode: HardwareErrorCode.CallQueueActionCancelled,
640
+ });
641
+ });
642
+
643
+ test('stops polling when the device cancels firmware installation', async () => {
644
+ const method = new FirmwareUpdateV4({
645
+ id: 1,
646
+ payload: {
647
+ method: 'firmwareUpdateV4',
648
+ connectId: 'pro2-ble',
649
+ },
650
+ });
651
+ const typedCall = jest
652
+ .fn()
653
+ .mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
654
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
655
+
656
+ method.device = {
657
+ getCommands: () => ({ typedCall }),
658
+ } as unknown as Device;
659
+ const firmwareUpdate = method as unknown as {
660
+ waitForProtocolV2FirmwareUpdateComplete: (
661
+ targets: Array<{ target_id: number; path: string }>
662
+ ) => Promise<void>;
663
+ reconnectProtocolV2Device: () => Promise<void>;
664
+ };
665
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
666
+
667
+ await expect(
668
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete([
669
+ { target_id: 4, path: 'vol0:/application_p1.bin' },
670
+ ])
671
+ ).rejects.toMatchObject({
672
+ errorCode: HardwareErrorCode.ActionCancelled,
673
+ });
674
+
675
+ expect(typedCall).toHaveBeenCalledTimes(1);
676
+ expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
677
+ });
678
+ });