@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.2
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.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +19 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/open-wallet-session.test.ts +144 -37
- package/__tests__/pro2HostAssetPackage.test.ts +58 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +720 -51
- package/dist/api/FirmwareUpdateV4.d.ts +4 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2278 -1435
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +434 -75
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +27 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/index.ts +121 -10
- package/src/data/messages/messages-protocol-v2.json +1541 -1332
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +88 -50
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +354 -0
|
@@ -254,6 +254,88 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
+
test('uploads and applies the fixed wallpaper package on firmware 1.0.1', async () => {
|
|
258
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
259
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
260
|
+
if (request === 'FilesystemFileWrite') {
|
|
261
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
262
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
263
|
+
}
|
|
264
|
+
if (request === 'DeviceSettingsSet') {
|
|
265
|
+
return { message: { message: 'wallpaper applied' } };
|
|
266
|
+
}
|
|
267
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
268
|
+
});
|
|
269
|
+
const method = new DeviceUploadWallpaper({
|
|
270
|
+
id: 1,
|
|
271
|
+
payload: {
|
|
272
|
+
method: 'deviceUploadWallpaper',
|
|
273
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
274
|
+
fileName: 'ignored-on-package-firmware.bin',
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
const device = stubWallpaperDevice({
|
|
278
|
+
commands: { typedCall },
|
|
279
|
+
state: { versions: { firmware: '1.0.1' } },
|
|
280
|
+
getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
|
|
281
|
+
});
|
|
282
|
+
(method as any).device = device;
|
|
283
|
+
method.postMessage = jest.fn();
|
|
284
|
+
|
|
285
|
+
method.init();
|
|
286
|
+
const result = await method.run();
|
|
287
|
+
|
|
288
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
289
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
290
|
+
new Set(['vol1:/wallpapers/wallpaper.okpkg'])
|
|
291
|
+
);
|
|
292
|
+
expect(fileWrites[0][2].file.data.subarray(0, 4)).toEqual(
|
|
293
|
+
new Uint8Array([0x4f, 0x4b, 0x50, 0x50])
|
|
294
|
+
);
|
|
295
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
|
|
296
|
+
settings: { wallpaper_path: 'vol1:/wallpapers/wallpaper.okpkg' },
|
|
297
|
+
});
|
|
298
|
+
expect(result).toMatchObject({
|
|
299
|
+
path: 'vol1:/wallpapers/wallpaper.okpkg',
|
|
300
|
+
colorFormat: 'RGB565',
|
|
301
|
+
message: 'wallpaper applied',
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('keeps the legacy wallpaper path on a 1.0.1 prerelease', async () => {
|
|
306
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
307
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
308
|
+
if (request === 'FilesystemFileWrite') {
|
|
309
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
310
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
311
|
+
}
|
|
312
|
+
if (request === 'DeviceSettingsSet') return { message: {} };
|
|
313
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
314
|
+
});
|
|
315
|
+
const method = new DeviceUploadWallpaper({
|
|
316
|
+
id: 1,
|
|
317
|
+
payload: {
|
|
318
|
+
method: 'deviceUploadWallpaper',
|
|
319
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
320
|
+
fileName: 'prerelease-wallpaper.bin',
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const device = stubWallpaperDevice({
|
|
324
|
+
commands: { typedCall },
|
|
325
|
+
state: { versions: { firmware: '1.0.1-beta.1' } },
|
|
326
|
+
});
|
|
327
|
+
(method as any).device = device;
|
|
328
|
+
|
|
329
|
+
method.init();
|
|
330
|
+
const result = await method.run();
|
|
331
|
+
|
|
332
|
+
expect(result.path).toBe('vol1:/wallpapers/prerelease-wallpaper.bin');
|
|
333
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
334
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
335
|
+
new Set(['vol1:/wallpapers/prerelease-wallpaper.bin'])
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
257
339
|
test('文件上传失败时不修改 wallpaper_path', async () => {
|
|
258
340
|
const typedCall = jest.fn().mockImplementation(request => {
|
|
259
341
|
if (request === 'FilesystemDirMake') return { message: {} };
|
|
@@ -1022,6 +1104,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1022
1104
|
newSession: 'session-1',
|
|
1023
1105
|
unlockedAttachPin: false,
|
|
1024
1106
|
resumed: false,
|
|
1107
|
+
walletStatusRefreshed: true,
|
|
1025
1108
|
});
|
|
1026
1109
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
1027
1110
|
eventless_wallet_session: true,
|
|
@@ -1032,10 +1115,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1032
1115
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1033
1116
|
passphrase: '',
|
|
1034
1117
|
on_device: false,
|
|
1035
|
-
});
|
|
1036
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1037
1118
|
seed_domains: [],
|
|
1038
1119
|
});
|
|
1120
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1039
1121
|
device.passphraseState = 'state-1';
|
|
1040
1122
|
expect(device.getInternalState()).toBe('session-1');
|
|
1041
1123
|
});
|
|
@@ -1088,13 +1170,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1088
1170
|
expect(
|
|
1089
1171
|
typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionAskPassphrase')
|
|
1090
1172
|
).toHaveLength(1);
|
|
1091
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1092
|
-
seed_domains: [],
|
|
1093
|
-
});
|
|
1173
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1094
1174
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1095
1175
|
session_id: 'standard-session',
|
|
1096
1176
|
btc_test_address: 'standard-state',
|
|
1097
|
-
seed_domains: [],
|
|
1098
1177
|
});
|
|
1099
1178
|
expect(deviceWalletSessionStore.getStandard(deviceId)).toEqual({
|
|
1100
1179
|
passphraseState: 'standard-state',
|
|
@@ -1285,6 +1364,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1285
1364
|
newSession: 'main-wallet-session',
|
|
1286
1365
|
unlockedAttachPin: undefined,
|
|
1287
1366
|
resumed: false,
|
|
1367
|
+
walletStatusRefreshed: true,
|
|
1288
1368
|
});
|
|
1289
1369
|
|
|
1290
1370
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
@@ -1371,6 +1451,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1371
1451
|
newSession: 'hidden-after-unlock-session',
|
|
1372
1452
|
unlockedAttachPin: undefined,
|
|
1373
1453
|
resumed: false,
|
|
1454
|
+
walletStatusRefreshed: true,
|
|
1374
1455
|
});
|
|
1375
1456
|
|
|
1376
1457
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
@@ -1457,10 +1538,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1457
1538
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1458
1539
|
passphrase: 'host hidden wallet',
|
|
1459
1540
|
on_device: false,
|
|
1460
|
-
});
|
|
1461
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1462
1541
|
seed_domains: [],
|
|
1463
1542
|
});
|
|
1543
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1464
1544
|
});
|
|
1465
1545
|
|
|
1466
1546
|
test('deviceStatusGet returns raw DeviceStatus and updates dynamic features', async () => {
|
|
@@ -1569,7 +1649,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1569
1649
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1570
1650
|
session_id: 'session-a',
|
|
1571
1651
|
btc_test_address: 'state-a',
|
|
1572
|
-
seed_domains: [],
|
|
1573
1652
|
});
|
|
1574
1653
|
expect(device.getInternalState()).toBe('session-b');
|
|
1575
1654
|
});
|
|
@@ -1643,7 +1722,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1643
1722
|
{
|
|
1644
1723
|
session_id: 'session-a',
|
|
1645
1724
|
btc_test_address: 'state-a',
|
|
1646
|
-
seed_domains: [],
|
|
1647
1725
|
},
|
|
1648
1726
|
],
|
|
1649
1727
|
]);
|
|
@@ -1690,10 +1768,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1690
1768
|
[
|
|
1691
1769
|
'DeviceSessionAskPassphrase',
|
|
1692
1770
|
'Success',
|
|
1693
|
-
{ passphrase: 'host hidden wallet', on_device: false },
|
|
1771
|
+
{ passphrase: 'host hidden wallet', on_device: false, seed_domains: [] },
|
|
1694
1772
|
],
|
|
1695
1773
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1696
|
-
['DeviceSessionGet', 'DeviceSession', {
|
|
1774
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
1697
1775
|
]);
|
|
1698
1776
|
});
|
|
1699
1777
|
|
|
@@ -1855,7 +1933,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1855
1933
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1856
1934
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1857
1935
|
btc_test_address: 'expected-state',
|
|
1858
|
-
seed_domains: [],
|
|
1859
1936
|
});
|
|
1860
1937
|
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1861
1938
|
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
@@ -2246,7 +2323,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2246
2323
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2247
2324
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2248
2325
|
btc_test_address: 'expected-state',
|
|
2249
|
-
seed_domains: [],
|
|
2250
2326
|
});
|
|
2251
2327
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2252
2328
|
});
|
|
@@ -2320,7 +2396,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2320
2396
|
{
|
|
2321
2397
|
session_id: 'session-pro2-app',
|
|
2322
2398
|
btc_test_address: 'state-pro2-app',
|
|
2323
|
-
seed_domains: [],
|
|
2324
2399
|
},
|
|
2325
2400
|
],
|
|
2326
2401
|
]);
|
|
@@ -2510,10 +2585,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2510
2585
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2511
2586
|
passphrase: 'host hidden wallet',
|
|
2512
2587
|
on_device: false,
|
|
2513
|
-
});
|
|
2514
|
-
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2515
2588
|
seed_domains: [],
|
|
2516
2589
|
});
|
|
2590
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2517
2591
|
});
|
|
2518
2592
|
|
|
2519
2593
|
test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
|
|
@@ -2593,16 +2667,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2593
2667
|
expect(device.getInternalState()).toBeUndefined();
|
|
2594
2668
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2595
2669
|
btc_test_address: 'expected-state',
|
|
2596
|
-
seed_domains: [],
|
|
2597
2670
|
});
|
|
2598
2671
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2599
2672
|
passphrase: 'host hidden wallet',
|
|
2600
2673
|
on_device: false,
|
|
2601
|
-
});
|
|
2602
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2603
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2604
2674
|
seed_domains: [],
|
|
2605
2675
|
});
|
|
2676
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2677
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2606
2678
|
});
|
|
2607
2679
|
|
|
2608
2680
|
test('fails closed instead of switching to Main PIN during a standard-wallet safety check', async () => {
|
|
@@ -2704,12 +2776,11 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2704
2776
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2705
2777
|
eventless_wallet_session: true,
|
|
2706
2778
|
});
|
|
2707
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2708
|
-
seed_domains: [],
|
|
2709
|
-
});
|
|
2779
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2710
2780
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2711
2781
|
passphrase: '',
|
|
2712
2782
|
on_device: false,
|
|
2783
|
+
seed_domains: [],
|
|
2713
2784
|
});
|
|
2714
2785
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2715
2786
|
expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
|
|
@@ -4820,6 +4891,23 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4820
4891
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
|
|
4821
4892
|
});
|
|
4822
4893
|
|
|
4894
|
+
test('stops V4 bootloader reconnect polling on a stale BLE bond', async () => {
|
|
4895
|
+
const method = new FirmwareUpdateV4({
|
|
4896
|
+
id: 1,
|
|
4897
|
+
payload: {
|
|
4898
|
+
method: 'firmwareUpdateV4',
|
|
4899
|
+
},
|
|
4900
|
+
});
|
|
4901
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
4902
|
+
const reconnectProtocolV2Device = jest.fn().mockRejectedValue(staleBondError);
|
|
4903
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
4904
|
+
|
|
4905
|
+
await expect((method as any).waitForProtocolV2BootloaderMode(60_000, 0)).rejects.toBe(
|
|
4906
|
+
staleBondError
|
|
4907
|
+
);
|
|
4908
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
|
|
4909
|
+
});
|
|
4910
|
+
|
|
4823
4911
|
test('polls again when Protocol V2 bootloader serial is temporarily unavailable', async () => {
|
|
4824
4912
|
const method = new FirmwareUpdateV4({
|
|
4825
4913
|
id: 1,
|
|
@@ -4901,6 +4989,87 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4901
4989
|
expect(initialize).not.toHaveBeenCalled();
|
|
4902
4990
|
});
|
|
4903
4991
|
|
|
4992
|
+
test('reacquires the same WebUSB device without a protocol probe during install recovery', async () => {
|
|
4993
|
+
const method = new FirmwareUpdateV4({
|
|
4994
|
+
id: 1,
|
|
4995
|
+
payload: {
|
|
4996
|
+
method: 'firmwareUpdateV4',
|
|
4997
|
+
},
|
|
4998
|
+
});
|
|
4999
|
+
const acquire = jest.fn().mockResolvedValue('usb-path');
|
|
5000
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5001
|
+
descriptors: [
|
|
5002
|
+
{ id: 'other-usb', path: 'other-usb', protocolType: 'V2' },
|
|
5003
|
+
{ id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5004
|
+
],
|
|
5005
|
+
});
|
|
5006
|
+
const commands = { disposed: true, mainId: '' };
|
|
5007
|
+
const device = stubDevice({
|
|
5008
|
+
originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
|
|
5009
|
+
deviceConnector: { acquire, enumerate },
|
|
5010
|
+
updateDescriptor: jest.fn(),
|
|
5011
|
+
acquire: jest.fn(),
|
|
5012
|
+
commands,
|
|
5013
|
+
getCommands: () => commands,
|
|
5014
|
+
mainId: 'old-usb-path',
|
|
5015
|
+
});
|
|
5016
|
+
const getDevices = jest.spyOn(DevicePool, 'getDevices');
|
|
5017
|
+
const isBrowserWebUsb = jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(false);
|
|
5018
|
+
const isDesktopWebUsb = jest.spyOn(DataManager, 'isDesktopWebUsb').mockReturnValue(true);
|
|
5019
|
+
(method as any).device = device;
|
|
5020
|
+
(method as any).protocolV2ExpectedPath = 'usb-path';
|
|
5021
|
+
|
|
5022
|
+
try {
|
|
5023
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5024
|
+
} finally {
|
|
5025
|
+
getDevices.mockRestore();
|
|
5026
|
+
isBrowserWebUsb.mockRestore();
|
|
5027
|
+
isDesktopWebUsb.mockRestore();
|
|
5028
|
+
}
|
|
5029
|
+
|
|
5030
|
+
expect(getDevices).not.toHaveBeenCalled();
|
|
5031
|
+
expect(acquire).toHaveBeenCalledWith(
|
|
5032
|
+
'usb-path',
|
|
5033
|
+
null,
|
|
5034
|
+
undefined,
|
|
5035
|
+
'V2',
|
|
5036
|
+
undefined,
|
|
5037
|
+
undefined,
|
|
5038
|
+
true
|
|
5039
|
+
);
|
|
5040
|
+
expect(device.acquire).not.toHaveBeenCalled();
|
|
5041
|
+
expect(commands.disposed).toBe(false);
|
|
5042
|
+
expect(commands.mainId).toBe('usb-path');
|
|
5043
|
+
});
|
|
5044
|
+
|
|
5045
|
+
test('reacquires the same BLE peripheral by id without scanning during install recovery', async () => {
|
|
5046
|
+
const method = new FirmwareUpdateV4({
|
|
5047
|
+
id: 1,
|
|
5048
|
+
payload: {
|
|
5049
|
+
method: 'firmwareUpdateV4',
|
|
5050
|
+
},
|
|
5051
|
+
});
|
|
5052
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
|
|
5053
|
+
const enumerate = jest.fn().mockResolvedValue({
|
|
5054
|
+
descriptors: [{ id: 'ble-id', name: 'OneKey Pro 2', protocolType: 'V2' }],
|
|
5055
|
+
});
|
|
5056
|
+
const commands = { disposed: true, mainId: '' };
|
|
5057
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5058
|
+
(method as any).device = stubDevice({
|
|
5059
|
+
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
5060
|
+
deviceConnector: { acquire, enumerate },
|
|
5061
|
+
commands,
|
|
5062
|
+
getCommands: () => commands,
|
|
5063
|
+
});
|
|
5064
|
+
|
|
5065
|
+
await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
5066
|
+
|
|
5067
|
+
expect(enumerate).not.toHaveBeenCalled();
|
|
5068
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2', undefined, undefined, true);
|
|
5069
|
+
expect(commands.disposed).toBe(false);
|
|
5070
|
+
expect(commands.mainId).toBe('ble-id');
|
|
5071
|
+
});
|
|
5072
|
+
|
|
4904
5073
|
test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
|
|
4905
5074
|
const method = new FirmwareUpdateV4({
|
|
4906
5075
|
id: 1,
|
|
@@ -5290,6 +5459,108 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5290
5459
|
});
|
|
5291
5460
|
});
|
|
5292
5461
|
|
|
5462
|
+
test('treats a typed BLE disconnect during Protocol V2 normal reboot as expected', async () => {
|
|
5463
|
+
const method = new FirmwareUpdateV4({
|
|
5464
|
+
id: 1,
|
|
5465
|
+
payload: {
|
|
5466
|
+
method: 'firmwareUpdateV4',
|
|
5467
|
+
},
|
|
5468
|
+
});
|
|
5469
|
+
const typedCall = jest
|
|
5470
|
+
.fn()
|
|
5471
|
+
.mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
|
|
5472
|
+
|
|
5473
|
+
(method as any).device = stubDevice({
|
|
5474
|
+
getCommands: () => ({ typedCall }),
|
|
5475
|
+
});
|
|
5476
|
+
|
|
5477
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5478
|
+
message: 'Device rebooted successfully',
|
|
5479
|
+
});
|
|
5480
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5481
|
+
DeviceRebootType.Normal
|
|
5482
|
+
);
|
|
5483
|
+
});
|
|
5484
|
+
|
|
5485
|
+
test('uses a short timeout and continues after a BLE reboot response timeout', async () => {
|
|
5486
|
+
const method = new FirmwareUpdateV4({
|
|
5487
|
+
id: 1,
|
|
5488
|
+
payload: {
|
|
5489
|
+
method: 'firmwareUpdateV4',
|
|
5490
|
+
},
|
|
5491
|
+
});
|
|
5492
|
+
const typedCall = jest
|
|
5493
|
+
.fn()
|
|
5494
|
+
.mockRejectedValue(
|
|
5495
|
+
ERRORS.TypedError(
|
|
5496
|
+
HardwareErrorCode.BleTimeoutError,
|
|
5497
|
+
'Lowlevel response timeout after 5000ms for DeviceReboot'
|
|
5498
|
+
)
|
|
5499
|
+
);
|
|
5500
|
+
|
|
5501
|
+
(method as any).device = stubDevice({
|
|
5502
|
+
getCommands: () => ({ typedCall }),
|
|
5503
|
+
});
|
|
5504
|
+
|
|
5505
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5506
|
+
message: 'Device rebooted successfully',
|
|
5507
|
+
});
|
|
5508
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
5509
|
+
'DeviceReboot',
|
|
5510
|
+
'Success',
|
|
5511
|
+
{ reboot_type: DeviceRebootType.Normal },
|
|
5512
|
+
{ timeoutMs: 5000 }
|
|
5513
|
+
);
|
|
5514
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5515
|
+
DeviceRebootType.Normal
|
|
5516
|
+
);
|
|
5517
|
+
});
|
|
5518
|
+
|
|
5519
|
+
test('continues after a USB Protocol V2 reboot response timeout', async () => {
|
|
5520
|
+
const method = new FirmwareUpdateV4({
|
|
5521
|
+
id: 1,
|
|
5522
|
+
payload: {
|
|
5523
|
+
method: 'firmwareUpdateV4',
|
|
5524
|
+
},
|
|
5525
|
+
});
|
|
5526
|
+
const timeoutError = Object.assign(
|
|
5527
|
+
new Error('Protocol V2 response timeout after 5000ms for DeviceReboot'),
|
|
5528
|
+
{ code: 'response-timeout' }
|
|
5529
|
+
);
|
|
5530
|
+
const typedCall = jest.fn().mockRejectedValue(timeoutError);
|
|
5531
|
+
|
|
5532
|
+
(method as any).device = stubDevice({
|
|
5533
|
+
getCommands: () => ({ typedCall }),
|
|
5534
|
+
});
|
|
5535
|
+
|
|
5536
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
|
|
5537
|
+
message: 'Device rebooted successfully',
|
|
5538
|
+
});
|
|
5539
|
+
expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
|
|
5540
|
+
DeviceRebootType.Normal
|
|
5541
|
+
);
|
|
5542
|
+
});
|
|
5543
|
+
|
|
5544
|
+
test('propagates non-transport errors from a Protocol V2 reboot', async () => {
|
|
5545
|
+
const method = new FirmwareUpdateV4({
|
|
5546
|
+
id: 1,
|
|
5547
|
+
payload: {
|
|
5548
|
+
method: 'firmwareUpdateV4',
|
|
5549
|
+
},
|
|
5550
|
+
});
|
|
5551
|
+
const rebootError = new Error('Device rejected reboot');
|
|
5552
|
+
const typedCall = jest.fn().mockRejectedValue(rebootError);
|
|
5553
|
+
|
|
5554
|
+
(method as any).device = stubDevice({
|
|
5555
|
+
getCommands: () => ({ typedCall }),
|
|
5556
|
+
});
|
|
5557
|
+
|
|
5558
|
+
await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).rejects.toBe(
|
|
5559
|
+
rebootError
|
|
5560
|
+
);
|
|
5561
|
+
expect((method as any).device.markProtocolV2Reboot).not.toHaveBeenCalled();
|
|
5562
|
+
});
|
|
5563
|
+
|
|
5293
5564
|
test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
|
|
5294
5565
|
const method = new FirmwareUpdateV4({
|
|
5295
5566
|
id: 1,
|
|
@@ -5365,7 +5636,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5365
5636
|
expect(call).toHaveBeenCalledWith(
|
|
5366
5637
|
'DeviceFirmwareUpdateRequest',
|
|
5367
5638
|
{},
|
|
5368
|
-
{
|
|
5639
|
+
expect.objectContaining({
|
|
5640
|
+
returnAfterWrite: true,
|
|
5641
|
+
expectedTypes: ['Success'],
|
|
5642
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5643
|
+
})
|
|
5369
5644
|
);
|
|
5370
5645
|
expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
|
|
5371
5646
|
expect((method.postMessage as jest.Mock).mock.invocationCallOrder[0]).toBeLessThan(
|
|
@@ -5406,7 +5681,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5406
5681
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5407
5682
|
});
|
|
5408
5683
|
|
|
5409
|
-
test('does not enter install state when
|
|
5684
|
+
test('does not enter install state when the empty request fails', async () => {
|
|
5410
5685
|
const method = new FirmwareUpdateV4({
|
|
5411
5686
|
id: 1,
|
|
5412
5687
|
payload: {
|
|
@@ -5414,7 +5689,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5414
5689
|
},
|
|
5415
5690
|
});
|
|
5416
5691
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5417
|
-
const call = jest.fn().mockRejectedValue(new Error('
|
|
5692
|
+
const call = jest.fn().mockRejectedValue(new Error('request failed'));
|
|
5418
5693
|
|
|
5419
5694
|
(method as any).device = stubDevice({
|
|
5420
5695
|
getCommands: () => ({ typedCall, call }),
|
|
@@ -5429,13 +5704,118 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5429
5704
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
5430
5705
|
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5431
5706
|
})
|
|
5432
|
-
).rejects.toThrow('
|
|
5707
|
+
).rejects.toThrow('request failed');
|
|
5433
5708
|
|
|
5434
|
-
expect(call).
|
|
5709
|
+
expect(call).toHaveBeenCalledWith(
|
|
5710
|
+
'DeviceFirmwareUpdateRequest',
|
|
5711
|
+
{},
|
|
5712
|
+
expect.objectContaining({
|
|
5713
|
+
returnAfterWrite: true,
|
|
5714
|
+
expectedTypes: ['Success'],
|
|
5715
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5716
|
+
})
|
|
5717
|
+
);
|
|
5435
5718
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
5436
5719
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
5437
5720
|
});
|
|
5438
5721
|
|
|
5722
|
+
test('continues to install polling when the empty request releases BLE transport', async () => {
|
|
5723
|
+
const method = new FirmwareUpdateV4({
|
|
5724
|
+
id: 1,
|
|
5725
|
+
payload: {
|
|
5726
|
+
method: 'firmwareUpdateV4',
|
|
5727
|
+
},
|
|
5728
|
+
});
|
|
5729
|
+
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
5730
|
+
const call = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
|
|
5731
|
+
|
|
5732
|
+
(method as any).device = stubDevice({
|
|
5733
|
+
getCommands: () => ({ typedCall, call }),
|
|
5734
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
5735
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
5736
|
+
});
|
|
5737
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5738
|
+
method.postMessage = jest.fn();
|
|
5739
|
+
|
|
5740
|
+
await expect(
|
|
5741
|
+
(method as any).protocolV2StartFirmwareUpdate({
|
|
5742
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
5743
|
+
})
|
|
5744
|
+
).resolves.toBeUndefined();
|
|
5745
|
+
|
|
5746
|
+
expect(call).toHaveBeenCalledWith(
|
|
5747
|
+
'DeviceFirmwareUpdateRequest',
|
|
5748
|
+
{},
|
|
5749
|
+
expect.objectContaining({
|
|
5750
|
+
returnAfterWrite: true,
|
|
5751
|
+
expectedTypes: ['Success'],
|
|
5752
|
+
onResponseAfterWrite: expect.any(Function),
|
|
5753
|
+
})
|
|
5754
|
+
);
|
|
5755
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
|
|
5756
|
+
expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
|
|
5757
|
+
expect((method as any).protocolV2InstallDisconnectObserved).toBe(true);
|
|
5758
|
+
});
|
|
5759
|
+
|
|
5760
|
+
test.each([
|
|
5761
|
+
{ name: 'bootloader', targetId: 3, path: 'vol0:/bootloader.bin' },
|
|
5762
|
+
{ name: 'application', targetId: 4, path: 'vol0:/application_p1.bin' },
|
|
5763
|
+
{ name: 'secure element', targetId: 7, path: 'vol0:/se01.bin' },
|
|
5764
|
+
])(
|
|
5765
|
+
'reconnects a released BLE install link before polling $name status without DeviceInfo',
|
|
5766
|
+
async ({ targetId, path }) => {
|
|
5767
|
+
const method = new FirmwareUpdateV4({
|
|
5768
|
+
id: 1,
|
|
5769
|
+
payload: {
|
|
5770
|
+
method: 'firmwareUpdateV4',
|
|
5771
|
+
},
|
|
5772
|
+
});
|
|
5773
|
+
const targets = [{ target_id: targetId, path }];
|
|
5774
|
+
const typedCall = jest
|
|
5775
|
+
.fn()
|
|
5776
|
+
.mockResolvedValueOnce({
|
|
5777
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5778
|
+
message: { records: [{ ...targets[0], status: 1, payload_version: 0 }] },
|
|
5779
|
+
})
|
|
5780
|
+
.mockResolvedValueOnce({
|
|
5781
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5782
|
+
message: { records: [{ ...targets[0], status: 2, payload_version: 0x010000 }] },
|
|
5783
|
+
});
|
|
5784
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5785
|
+
const verifyProtocolV2ReconnectIdentity = jest.fn();
|
|
5786
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
5787
|
+
callback: () => void
|
|
5788
|
+
) => {
|
|
5789
|
+
callback();
|
|
5790
|
+
return 0 as any;
|
|
5791
|
+
}) as typeof setTimeout);
|
|
5792
|
+
|
|
5793
|
+
(method as any).device = stubDevice({
|
|
5794
|
+
getCommands: () => ({ typedCall }),
|
|
5795
|
+
});
|
|
5796
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
5797
|
+
(method as any).protocolV2InstallNeedsReconnect = true;
|
|
5798
|
+
(method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
5799
|
+
(method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
|
|
5800
|
+
method.postProgressMessage = jest.fn();
|
|
5801
|
+
|
|
5802
|
+
try {
|
|
5803
|
+
await expect(
|
|
5804
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
5805
|
+
).resolves.toBeUndefined();
|
|
5806
|
+
} finally {
|
|
5807
|
+
setTimeoutSpy.mockRestore();
|
|
5808
|
+
}
|
|
5809
|
+
|
|
5810
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
5811
|
+
expect(verifyProtocolV2ReconnectIdentity).not.toHaveBeenCalled();
|
|
5812
|
+
expect(typedCall.mock.calls.map(callArgs => callArgs[0])).toEqual([
|
|
5813
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5814
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
5815
|
+
]);
|
|
5816
|
+
}
|
|
5817
|
+
);
|
|
5818
|
+
|
|
5439
5819
|
test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
|
|
5440
5820
|
const method = new FirmwareUpdateV4({
|
|
5441
5821
|
id: 1,
|
|
@@ -5479,7 +5859,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5479
5859
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5480
5860
|
});
|
|
5481
5861
|
|
|
5482
|
-
test('keeps polling after
|
|
5862
|
+
test('keeps polling after an empty status response until the target finishes', async () => {
|
|
5483
5863
|
const method = new FirmwareUpdateV4({
|
|
5484
5864
|
id: 1,
|
|
5485
5865
|
payload: {
|
|
@@ -5488,7 +5868,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5488
5868
|
});
|
|
5489
5869
|
const typedCall = jest
|
|
5490
5870
|
.fn()
|
|
5491
|
-
.mockResolvedValueOnce({
|
|
5871
|
+
.mockResolvedValueOnce({
|
|
5872
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
5873
|
+
message: { records: [] },
|
|
5874
|
+
})
|
|
5492
5875
|
.mockResolvedValueOnce({
|
|
5493
5876
|
type: 'DeviceFirmwareUpdateStatus',
|
|
5494
5877
|
message: { records: [] },
|
|
@@ -5630,6 +6013,38 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5630
6013
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5631
6014
|
});
|
|
5632
6015
|
|
|
6016
|
+
test('does not count a stale finished target when only another target was live', () => {
|
|
6017
|
+
const method = new FirmwareUpdateV4({
|
|
6018
|
+
id: 1,
|
|
6019
|
+
payload: {
|
|
6020
|
+
method: 'firmwareUpdateV4',
|
|
6021
|
+
},
|
|
6022
|
+
});
|
|
6023
|
+
const assertProtocolV2TargetStatus = (method as any).assertProtocolV2TargetStatus.bind(method);
|
|
6024
|
+
const expectedTargetIds = new Set([3, 4]);
|
|
6025
|
+
const expectedPaths = new Map([
|
|
6026
|
+
[3, 'vol0:/bootloader.bin'],
|
|
6027
|
+
[4, 'vol0:/application_p1.bin'],
|
|
6028
|
+
]);
|
|
6029
|
+
const liveTargetIds = new Set([4]);
|
|
6030
|
+
method.postProgressMessage = jest.fn();
|
|
6031
|
+
|
|
6032
|
+
const completed = assertProtocolV2TargetStatus(
|
|
6033
|
+
[
|
|
6034
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6035
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6036
|
+
],
|
|
6037
|
+
expectedTargetIds,
|
|
6038
|
+
expectedPaths,
|
|
6039
|
+
liveTargetIds
|
|
6040
|
+
);
|
|
6041
|
+
|
|
6042
|
+
expect(completed).toBe(false);
|
|
6043
|
+
expect((method as any).protocolV2CompletedTargetIds).toEqual(new Set([4]));
|
|
6044
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
|
|
6045
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6046
|
+
});
|
|
6047
|
+
|
|
5633
6048
|
test('ignores stale failed records until the current install starts', async () => {
|
|
5634
6049
|
const method = new FirmwareUpdateV4({
|
|
5635
6050
|
id: 1,
|
|
@@ -5678,6 +6093,141 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5678
6093
|
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
5679
6094
|
});
|
|
5680
6095
|
|
|
6096
|
+
test('ignores leftover failed records while another leftover target is still pending', async () => {
|
|
6097
|
+
const method = new FirmwareUpdateV4({
|
|
6098
|
+
id: 1,
|
|
6099
|
+
payload: {
|
|
6100
|
+
method: 'firmwareUpdateV4',
|
|
6101
|
+
},
|
|
6102
|
+
});
|
|
6103
|
+
const typedCall = jest
|
|
6104
|
+
.fn()
|
|
6105
|
+
.mockResolvedValueOnce({
|
|
6106
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6107
|
+
message: {
|
|
6108
|
+
records: [
|
|
6109
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6110
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6111
|
+
],
|
|
6112
|
+
},
|
|
6113
|
+
})
|
|
6114
|
+
.mockResolvedValueOnce({
|
|
6115
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6116
|
+
message: {
|
|
6117
|
+
records: [
|
|
6118
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6119
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6120
|
+
],
|
|
6121
|
+
},
|
|
6122
|
+
})
|
|
6123
|
+
.mockResolvedValueOnce({
|
|
6124
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6125
|
+
message: {
|
|
6126
|
+
records: [
|
|
6127
|
+
{ target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
|
|
6128
|
+
{ target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
|
|
6129
|
+
],
|
|
6130
|
+
},
|
|
6131
|
+
});
|
|
6132
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6133
|
+
callback: () => void
|
|
6134
|
+
) => {
|
|
6135
|
+
callback();
|
|
6136
|
+
return 0 as any;
|
|
6137
|
+
}) as typeof setTimeout);
|
|
6138
|
+
|
|
6139
|
+
(method as any).device = stubDevice({
|
|
6140
|
+
getCommands: () => ({ typedCall }),
|
|
6141
|
+
});
|
|
6142
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6143
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6144
|
+
method.postProgressMessage = jest.fn();
|
|
6145
|
+
|
|
6146
|
+
try {
|
|
6147
|
+
await (method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6148
|
+
[
|
|
6149
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6150
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6151
|
+
],
|
|
6152
|
+
true
|
|
6153
|
+
);
|
|
6154
|
+
} finally {
|
|
6155
|
+
setTimeoutSpy.mockRestore();
|
|
6156
|
+
}
|
|
6157
|
+
|
|
6158
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6159
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
6160
|
+
});
|
|
6161
|
+
|
|
6162
|
+
test('reports failed only after that target was live in the current install', async () => {
|
|
6163
|
+
const method = new FirmwareUpdateV4({
|
|
6164
|
+
id: 1,
|
|
6165
|
+
payload: {
|
|
6166
|
+
method: 'firmwareUpdateV4',
|
|
6167
|
+
},
|
|
6168
|
+
});
|
|
6169
|
+
const typedCall = jest
|
|
6170
|
+
.fn()
|
|
6171
|
+
.mockResolvedValueOnce({
|
|
6172
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6173
|
+
message: {
|
|
6174
|
+
records: [
|
|
6175
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6176
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6177
|
+
],
|
|
6178
|
+
},
|
|
6179
|
+
})
|
|
6180
|
+
.mockResolvedValueOnce({
|
|
6181
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6182
|
+
message: {
|
|
6183
|
+
records: [
|
|
6184
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6185
|
+
{ target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
|
|
6186
|
+
],
|
|
6187
|
+
},
|
|
6188
|
+
})
|
|
6189
|
+
.mockResolvedValueOnce({
|
|
6190
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6191
|
+
message: {
|
|
6192
|
+
records: [
|
|
6193
|
+
{ target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
|
|
6194
|
+
{ target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
|
|
6195
|
+
],
|
|
6196
|
+
},
|
|
6197
|
+
});
|
|
6198
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6199
|
+
callback: () => void
|
|
6200
|
+
) => {
|
|
6201
|
+
callback();
|
|
6202
|
+
return 0 as any;
|
|
6203
|
+
}) as typeof setTimeout);
|
|
6204
|
+
|
|
6205
|
+
(method as any).device = stubDevice({
|
|
6206
|
+
getCommands: () => ({ typedCall }),
|
|
6207
|
+
});
|
|
6208
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6209
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
|
|
6210
|
+
|
|
6211
|
+
try {
|
|
6212
|
+
await expect(
|
|
6213
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6214
|
+
[
|
|
6215
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
6216
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
6217
|
+
],
|
|
6218
|
+
true
|
|
6219
|
+
)
|
|
6220
|
+
).rejects.toMatchObject({
|
|
6221
|
+
errorCode: HardwareErrorCode.FirmwareError,
|
|
6222
|
+
message: 'Protocol V2 firmware install failed',
|
|
6223
|
+
});
|
|
6224
|
+
} finally {
|
|
6225
|
+
setTimeoutSpy.mockRestore();
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
expect(typedCall).toHaveBeenCalledTimes(3);
|
|
6229
|
+
});
|
|
6230
|
+
|
|
5681
6231
|
test('waits five minutes before rejecting normal mode without install evidence', async () => {
|
|
5682
6232
|
const method = new FirmwareUpdateV4({
|
|
5683
6233
|
id: 1,
|
|
@@ -5784,7 +6334,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5784
6334
|
});
|
|
5785
6335
|
const typedCall = jest
|
|
5786
6336
|
.fn()
|
|
5787
|
-
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
5788
6337
|
.mockRejectedValueOnce(new Error('Pro2 is rebooting'))
|
|
5789
6338
|
.mockResolvedValueOnce({
|
|
5790
6339
|
type: 'DeviceFirmwareUpdateStatus',
|
|
@@ -5801,6 +6350,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5801
6350
|
(method as any).device = stubDevice({
|
|
5802
6351
|
getCommands: () => ({ typedCall }),
|
|
5803
6352
|
});
|
|
6353
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
5804
6354
|
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
5805
6355
|
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
5806
6356
|
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
@@ -5821,6 +6371,54 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5821
6371
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
5822
6372
|
});
|
|
5823
6373
|
|
|
6374
|
+
test('uses the confirmed install request as evidence after the same device returns', async () => {
|
|
6375
|
+
const method = new FirmwareUpdateV4({
|
|
6376
|
+
id: 1,
|
|
6377
|
+
payload: {
|
|
6378
|
+
method: 'firmwareUpdateV4',
|
|
6379
|
+
},
|
|
6380
|
+
});
|
|
6381
|
+
const typedCall = jest
|
|
6382
|
+
.fn()
|
|
6383
|
+
.mockRejectedValueOnce(new Error('The USB device was disconnected'))
|
|
6384
|
+
.mockResolvedValueOnce({
|
|
6385
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
6386
|
+
message: { records: [] },
|
|
6387
|
+
});
|
|
6388
|
+
const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
|
|
6389
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
6390
|
+
callback: () => void
|
|
6391
|
+
) => {
|
|
6392
|
+
callback();
|
|
6393
|
+
return 0 as any;
|
|
6394
|
+
}) as typeof setTimeout);
|
|
6395
|
+
|
|
6396
|
+
(method as any).device = stubDevice({
|
|
6397
|
+
getCommands: () => ({ typedCall }),
|
|
6398
|
+
});
|
|
6399
|
+
(method as any).protocolV2InstallTerminalSuccessObserved = true;
|
|
6400
|
+
(method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
6401
|
+
(method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
|
|
6402
|
+
(method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
|
|
6403
|
+
method.postProgressMessage = jest.fn();
|
|
6404
|
+
|
|
6405
|
+
try {
|
|
6406
|
+
await expect(
|
|
6407
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete(
|
|
6408
|
+
[{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
6409
|
+
true
|
|
6410
|
+
)
|
|
6411
|
+
).resolves.toBeUndefined();
|
|
6412
|
+
} finally {
|
|
6413
|
+
setTimeoutSpy.mockRestore();
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6416
|
+
expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledWith({
|
|
6417
|
+
skipProtocolProbe: true,
|
|
6418
|
+
});
|
|
6419
|
+
expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'installingFirmware');
|
|
6420
|
+
});
|
|
6421
|
+
|
|
5824
6422
|
test.each([
|
|
5825
6423
|
[{ mode: 'normal', bootloaderMode: false }, true],
|
|
5826
6424
|
[{ mode: 'notInitialized', bootloaderMode: false }, true],
|
|
@@ -6183,7 +6781,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6183
6781
|
expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
|
|
6184
6782
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6185
6783
|
'DeviceFirmwareUpdateStatusGet',
|
|
6186
|
-
|
|
6784
|
+
'DeviceFirmwareUpdateStatus',
|
|
6187
6785
|
expect.anything(),
|
|
6188
6786
|
expect.anything()
|
|
6189
6787
|
);
|
|
@@ -6465,10 +7063,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
6465
7063
|
});
|
|
6466
7064
|
expect(typedCall).toHaveBeenCalledWith(
|
|
6467
7065
|
'DeviceFirmwareUpdateStatusGet',
|
|
6468
|
-
|
|
7066
|
+
'DeviceFirmwareUpdateStatus',
|
|
6469
7067
|
{
|
|
6470
7068
|
fields: {
|
|
6471
7069
|
status: true,
|
|
7070
|
+
progress_percent: true,
|
|
7071
|
+
phase_info: true,
|
|
6472
7072
|
payload_version: true,
|
|
6473
7073
|
path: true,
|
|
6474
7074
|
},
|
|
@@ -8183,6 +8783,36 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8183
8783
|
});
|
|
8184
8784
|
});
|
|
8185
8785
|
|
|
8786
|
+
test('does not retry or wrap a stale BLE bond during a V4 file transfer', async () => {
|
|
8787
|
+
const method = new FirmwareUpdateV4({
|
|
8788
|
+
id: 1,
|
|
8789
|
+
payload: {
|
|
8790
|
+
method: 'firmwareUpdateV4',
|
|
8791
|
+
},
|
|
8792
|
+
});
|
|
8793
|
+
const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
|
|
8794
|
+
(method as any).fileWriteChunk = jest.fn().mockRejectedValue(staleBondError);
|
|
8795
|
+
const recoverProtocolV2FileTransfer = jest.fn();
|
|
8796
|
+
(method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
|
|
8797
|
+
const source = await openFirmwareByteSource({
|
|
8798
|
+
binary: new Uint8Array([1]).buffer,
|
|
8799
|
+
});
|
|
8800
|
+
|
|
8801
|
+
try {
|
|
8802
|
+
await expect(
|
|
8803
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
8804
|
+
source,
|
|
8805
|
+
filePath: 'vol1:firmware.bin',
|
|
8806
|
+
processedSize: 0,
|
|
8807
|
+
totalSize: 1,
|
|
8808
|
+
})
|
|
8809
|
+
).rejects.toBe(staleBondError);
|
|
8810
|
+
} finally {
|
|
8811
|
+
await source?.close();
|
|
8812
|
+
}
|
|
8813
|
+
expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
|
|
8814
|
+
});
|
|
8815
|
+
|
|
8186
8816
|
test('throttles repeated transfer progress while preserving file completion', async () => {
|
|
8187
8817
|
const method = new FirmwareUpdateV4({
|
|
8188
8818
|
id: 1,
|
|
@@ -8689,26 +9319,28 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8689
9319
|
expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
|
|
8690
9320
|
});
|
|
8691
9321
|
|
|
8692
|
-
test('keeps
|
|
9322
|
+
test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
|
|
8693
9323
|
const method = new FirmwareUpdateV4({
|
|
8694
9324
|
id: 1,
|
|
8695
9325
|
payload: {
|
|
8696
9326
|
method: 'firmwareUpdateV4',
|
|
8697
9327
|
},
|
|
8698
9328
|
});
|
|
8699
|
-
let
|
|
9329
|
+
let onResponseAfterWrite:
|
|
9330
|
+
| ((response: { type: string; message: Record<string, never> }) => void)
|
|
9331
|
+
| undefined;
|
|
8700
9332
|
const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
|
|
8701
|
-
const call = jest.fn().mockImplementation(
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
);
|
|
9333
|
+
const call = jest.fn().mockImplementation((_type, _message, options) => {
|
|
9334
|
+
onResponseAfterWrite = options.onResponseAfterWrite;
|
|
9335
|
+
return Promise.resolve({ type: 'WriteCompleted', message: {} });
|
|
9336
|
+
});
|
|
9337
|
+
const clearCancelableAction = jest.fn();
|
|
8707
9338
|
|
|
8708
9339
|
(method as any).device = stubDevice({
|
|
8709
|
-
getCommands: () => ({ typedCall, call }),
|
|
9340
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8710
9341
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8711
9342
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
9343
|
+
clearCancelableAction,
|
|
8712
9344
|
});
|
|
8713
9345
|
method.postMessage = jest.fn();
|
|
8714
9346
|
method.postProgressMessage = jest.fn();
|
|
@@ -8729,18 +9361,25 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8729
9361
|
expect(call).toHaveBeenCalledWith(
|
|
8730
9362
|
'DeviceFirmwareUpdateRequest',
|
|
8731
9363
|
{},
|
|
8732
|
-
{
|
|
9364
|
+
expect.objectContaining({
|
|
9365
|
+
returnAfterWrite: true,
|
|
9366
|
+
expectedTypes: ['Success'],
|
|
9367
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9368
|
+
})
|
|
8733
9369
|
);
|
|
8734
9370
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8735
9371
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
9372
|
+
expect(clearCancelableAction).not.toHaveBeenCalled();
|
|
8736
9373
|
|
|
8737
|
-
resolveWrite?.({ type: 'WriteCompleted', message: {} });
|
|
8738
9374
|
await startPromise;
|
|
9375
|
+
onResponseAfterWrite?.({ type: 'Success', message: {} });
|
|
9376
|
+
expect(clearCancelableAction).toHaveBeenCalledTimes(1);
|
|
9377
|
+
expect((method as any).protocolV2InstallTerminalSuccessObserved).toBe(true);
|
|
8739
9378
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
8740
9379
|
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8741
9380
|
});
|
|
8742
9381
|
|
|
8743
|
-
test('
|
|
9382
|
+
test('returns after the empty Protocol V2 firmware install request is written', async () => {
|
|
8744
9383
|
const method = new FirmwareUpdateV4({
|
|
8745
9384
|
id: 1,
|
|
8746
9385
|
payload: {
|
|
@@ -8751,10 +9390,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8751
9390
|
type: 'Success',
|
|
8752
9391
|
message: { message: 'accepted' },
|
|
8753
9392
|
});
|
|
8754
|
-
const call = jest.fn().mockResolvedValue({
|
|
9393
|
+
const call = jest.fn().mockResolvedValue({
|
|
9394
|
+
type: 'WriteCompleted',
|
|
9395
|
+
message: {},
|
|
9396
|
+
});
|
|
8755
9397
|
|
|
8756
9398
|
(method as any).device = stubDevice({
|
|
8757
|
-
getCommands: () => ({ typedCall, call }),
|
|
9399
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
8758
9400
|
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
8759
9401
|
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
|
|
8760
9402
|
});
|
|
@@ -8770,9 +9412,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
8770
9412
|
expect(call).toHaveBeenCalledWith(
|
|
8771
9413
|
'DeviceFirmwareUpdateRequest',
|
|
8772
9414
|
{},
|
|
8773
|
-
{
|
|
9415
|
+
expect.objectContaining({
|
|
9416
|
+
returnAfterWrite: true,
|
|
9417
|
+
expectedTypes: ['Success'],
|
|
9418
|
+
onResponseAfterWrite: expect.any(Function),
|
|
9419
|
+
})
|
|
8774
9420
|
);
|
|
8775
9421
|
expect(method.postTipMessage).not.toHaveBeenCalled();
|
|
9422
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
8776
9423
|
});
|
|
8777
9424
|
});
|
|
8778
9425
|
|
|
@@ -8955,7 +9602,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8955
9602
|
test('passes firmware update status fields through to Protocol V2', async () => {
|
|
8956
9603
|
const typedCall = jest.fn().mockResolvedValue({
|
|
8957
9604
|
message: {
|
|
8958
|
-
records: [
|
|
9605
|
+
records: [
|
|
9606
|
+
{
|
|
9607
|
+
target_id: 4,
|
|
9608
|
+
status: 1,
|
|
9609
|
+
progress_percent: 42,
|
|
9610
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9611
|
+
payload_version: 1,
|
|
9612
|
+
path: 'vol1:application_p1.bin',
|
|
9613
|
+
},
|
|
9614
|
+
],
|
|
8959
9615
|
},
|
|
8960
9616
|
});
|
|
8961
9617
|
const method = new DeviceGetFirmwareUpdateStatus({
|
|
@@ -8964,6 +9620,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8964
9620
|
method: 'deviceGetFirmwareUpdateStatus',
|
|
8965
9621
|
fields: {
|
|
8966
9622
|
status: true,
|
|
9623
|
+
progress_percent: true,
|
|
9624
|
+
phase_info: true,
|
|
8967
9625
|
payload_version: true,
|
|
8968
9626
|
path: true,
|
|
8969
9627
|
},
|
|
@@ -8975,7 +9633,16 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8975
9633
|
});
|
|
8976
9634
|
|
|
8977
9635
|
await expect(method.run()).resolves.toEqual({
|
|
8978
|
-
records: [
|
|
9636
|
+
records: [
|
|
9637
|
+
{
|
|
9638
|
+
target_id: 4,
|
|
9639
|
+
status: 1,
|
|
9640
|
+
progress_percent: 42,
|
|
9641
|
+
phase_info: { phase: 1, progress_percent: 60 },
|
|
9642
|
+
payload_version: 1,
|
|
9643
|
+
path: 'vol1:application_p1.bin',
|
|
9644
|
+
},
|
|
9645
|
+
],
|
|
8979
9646
|
});
|
|
8980
9647
|
expect(typedCall).toHaveBeenCalledWith(
|
|
8981
9648
|
'DeviceFirmwareUpdateStatusGet',
|
|
@@ -8983,6 +9650,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
8983
9650
|
{
|
|
8984
9651
|
fields: {
|
|
8985
9652
|
status: true,
|
|
9653
|
+
progress_percent: true,
|
|
9654
|
+
phase_info: true,
|
|
8986
9655
|
payload_version: true,
|
|
8987
9656
|
path: true,
|
|
8988
9657
|
},
|