@onekeyfe/hd-core 1.2.2-alpha.0 → 1.2.2-alpha.100

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 (69) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +4 -20
  2. package/__tests__/DeviceCommands.test.ts +0 -13
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +2 -0
  4. package/__tests__/check-firmware-release-protocol-v2.test.ts +2 -0
  5. package/__tests__/device-lifecycle-events.test.ts +0 -19
  6. package/__tests__/deviceUploadNft.test.ts +0 -41
  7. package/__tests__/evmSignTypedData.test.ts +0 -42
  8. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +12 -524
  9. package/__tests__/open-wallet-session.test.ts +1 -127
  10. package/__tests__/protocol-v2-resources.test.ts +4 -7
  11. package/__tests__/protocol-v2-unlock-policy.test.ts +1 -35
  12. package/__tests__/protocol-v2.test.ts +27 -673
  13. package/dist/api/FirmwareUpdateV4.d.ts +0 -3
  14. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  15. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  16. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  17. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  18. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  19. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  20. package/dist/api/firmware/getBinary.d.ts +2 -2
  21. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  22. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  23. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  24. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  25. package/dist/core/index.d.ts +0 -1
  26. package/dist/core/index.d.ts.map +1 -1
  27. package/dist/device/Device.d.ts +1 -1
  28. package/dist/device/Device.d.ts.map +1 -1
  29. package/dist/device/DeviceConnector.d.ts +1 -1
  30. package/dist/device/DeviceConnector.d.ts.map +1 -1
  31. package/dist/events/ui-request.d.ts +0 -3
  32. package/dist/events/ui-request.d.ts.map +1 -1
  33. package/dist/index.d.ts +5 -10
  34. package/dist/index.js +398 -1116
  35. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  36. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -2
  37. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  38. package/dist/protocols/protocol-v2/walletSession.d.ts +0 -3
  39. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  40. package/dist/types/settings.d.ts +2 -2
  41. package/dist/types/settings.d.ts.map +1 -1
  42. package/dist/utils/deviceFeaturesUtils.d.ts +0 -3
  43. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  44. package/dist/utils/patch.d.ts +1 -1
  45. package/dist/utils/patch.d.ts.map +1 -1
  46. package/package.json +4 -4
  47. package/src/api/FirmwareUpdateV4.ts +75 -416
  48. package/src/api/OpenWalletSession.ts +9 -39
  49. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +1 -2
  50. package/src/api/evm/EVMSignTypedData.ts +0 -1
  51. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +3 -9
  52. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +1 -7
  53. package/src/api/protocol-v2/DeviceUploadNft.ts +8 -28
  54. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +8 -19
  55. package/src/api/protocol-v2/helpers.ts +0 -8
  56. package/src/core/index.ts +10 -121
  57. package/src/data/messages/messages-protocol-v2.json +1 -111
  58. package/src/device/Device.ts +1 -3
  59. package/src/device/DeviceConnector.ts +3 -7
  60. package/src/events/ui-request.ts +0 -3
  61. package/src/protocols/protocol-v2/resources.ts +1 -9
  62. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +1 -8
  63. package/src/protocols/protocol-v2/walletSession.ts +22 -65
  64. package/src/types/settings.ts +2 -4
  65. package/src/utils/deviceFeaturesUtils.ts +0 -4
  66. package/__tests__/pro2HostAssetPackage.test.ts +0 -58
  67. package/dist/utils/pro2HostAssetPackage.d.ts +0 -8
  68. package/dist/utils/pro2HostAssetPackage.d.ts.map +0 -1
  69. package/src/utils/pro2HostAssetPackage.ts +0 -315
@@ -254,52 +254,6 @@ 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
- },
275
- });
276
- const device = stubWallpaperDevice({
277
- commands: { typedCall },
278
- getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
279
- });
280
- (method as any).device = device;
281
- method.postMessage = jest.fn();
282
-
283
- method.init();
284
- const result = await method.run();
285
-
286
- const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
287
- expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
288
- new Set(['vol1:/wallpapers/wallpaper.okpkg'])
289
- );
290
- expect(fileWrites[0][2].file.data.subarray(0, 4)).toEqual(
291
- new Uint8Array([0x4f, 0x4b, 0x50, 0x50])
292
- );
293
- expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
294
- settings: { wallpaper_path: 'vol1:/wallpapers/wallpaper.okpkg' },
295
- });
296
- expect(result).toMatchObject({
297
- path: 'vol1:/wallpapers/wallpaper.okpkg',
298
- colorFormat: 'RGB565',
299
- message: 'wallpaper applied',
300
- });
301
- });
302
-
303
257
  test('文件上传失败时不修改 wallpaper_path', async () => {
304
258
  const typedCall = jest.fn().mockImplementation(request => {
305
259
  if (request === 'FilesystemDirMake') return { message: {} };
@@ -1068,7 +1022,6 @@ describe('Protocol V2 feature adapter', () => {
1068
1022
  newSession: 'session-1',
1069
1023
  unlockedAttachPin: false,
1070
1024
  resumed: false,
1071
- walletStatusRefreshed: true,
1072
1025
  });
1073
1026
  expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
1074
1027
  eventless_wallet_session: true,
@@ -1332,7 +1285,6 @@ describe('Protocol V2 feature adapter', () => {
1332
1285
  newSession: 'main-wallet-session',
1333
1286
  unlockedAttachPin: undefined,
1334
1287
  resumed: false,
1335
- walletStatusRefreshed: true,
1336
1288
  });
1337
1289
 
1338
1290
  expect(promptPassphrase).toHaveBeenCalled();
@@ -1419,7 +1371,6 @@ describe('Protocol V2 feature adapter', () => {
1419
1371
  newSession: 'hidden-after-unlock-session',
1420
1372
  unlockedAttachPin: undefined,
1421
1373
  resumed: false,
1422
- walletStatusRefreshed: true,
1423
1374
  });
1424
1375
 
1425
1376
  expect(unlockDevice).not.toHaveBeenCalled();
@@ -4869,23 +4820,6 @@ describe('Protocol V2 firmware update targets', () => {
4869
4820
  expect(typedCall.mock.calls.map(call => call[0])).toEqual(['DeviceInfoGet']);
4870
4821
  });
4871
4822
 
4872
- test('stops V4 bootloader reconnect polling on a stale BLE bond', async () => {
4873
- const method = new FirmwareUpdateV4({
4874
- id: 1,
4875
- payload: {
4876
- method: 'firmwareUpdateV4',
4877
- },
4878
- });
4879
- const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
4880
- const reconnectProtocolV2Device = jest.fn().mockRejectedValue(staleBondError);
4881
- (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
4882
-
4883
- await expect((method as any).waitForProtocolV2BootloaderMode(60_000, 0)).rejects.toBe(
4884
- staleBondError
4885
- );
4886
- expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
4887
- });
4888
-
4889
4823
  test('polls again when Protocol V2 bootloader serial is temporarily unavailable', async () => {
4890
4824
  const method = new FirmwareUpdateV4({
4891
4825
  id: 1,
@@ -4967,87 +4901,6 @@ describe('Protocol V2 firmware update targets', () => {
4967
4901
  expect(initialize).not.toHaveBeenCalled();
4968
4902
  });
4969
4903
 
4970
- test('reacquires the same WebUSB device without a protocol probe during install recovery', async () => {
4971
- const method = new FirmwareUpdateV4({
4972
- id: 1,
4973
- payload: {
4974
- method: 'firmwareUpdateV4',
4975
- },
4976
- });
4977
- const acquire = jest.fn().mockResolvedValue('usb-path');
4978
- const enumerate = jest.fn().mockResolvedValue({
4979
- descriptors: [
4980
- { id: 'other-usb', path: 'other-usb', protocolType: 'V2' },
4981
- { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
4982
- ],
4983
- });
4984
- const commands = { disposed: true, mainId: '' };
4985
- const device = stubDevice({
4986
- originalDescriptor: { id: 'usb-id', path: 'usb-path', protocolType: 'V2' },
4987
- deviceConnector: { acquire, enumerate },
4988
- updateDescriptor: jest.fn(),
4989
- acquire: jest.fn(),
4990
- commands,
4991
- getCommands: () => commands,
4992
- mainId: 'old-usb-path',
4993
- });
4994
- const getDevices = jest.spyOn(DevicePool, 'getDevices');
4995
- const isBrowserWebUsb = jest.spyOn(DataManager, 'isBrowserWebUsb').mockReturnValue(false);
4996
- const isDesktopWebUsb = jest.spyOn(DataManager, 'isDesktopWebUsb').mockReturnValue(true);
4997
- (method as any).device = device;
4998
- (method as any).protocolV2ExpectedPath = 'usb-path';
4999
-
5000
- try {
5001
- await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
5002
- } finally {
5003
- getDevices.mockRestore();
5004
- isBrowserWebUsb.mockRestore();
5005
- isDesktopWebUsb.mockRestore();
5006
- }
5007
-
5008
- expect(getDevices).not.toHaveBeenCalled();
5009
- expect(acquire).toHaveBeenCalledWith(
5010
- 'usb-path',
5011
- null,
5012
- undefined,
5013
- 'V2',
5014
- undefined,
5015
- undefined,
5016
- true
5017
- );
5018
- expect(device.acquire).not.toHaveBeenCalled();
5019
- expect(commands.disposed).toBe(false);
5020
- expect(commands.mainId).toBe('usb-path');
5021
- });
5022
-
5023
- test('reacquires the same BLE peripheral by id without scanning during install recovery', async () => {
5024
- const method = new FirmwareUpdateV4({
5025
- id: 1,
5026
- payload: {
5027
- method: 'firmwareUpdateV4',
5028
- },
5029
- });
5030
- const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
5031
- const enumerate = jest.fn().mockResolvedValue({
5032
- descriptors: [{ id: 'ble-id', name: 'OneKey Pro 2', protocolType: 'V2' }],
5033
- });
5034
- const commands = { disposed: true, mainId: '' };
5035
- (method as any).isBleReconnect = jest.fn(() => true);
5036
- (method as any).device = stubDevice({
5037
- originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
5038
- deviceConnector: { acquire, enumerate },
5039
- commands,
5040
- getCommands: () => commands,
5041
- });
5042
-
5043
- await (method as any).reconnectProtocolV2Device({ skipProtocolProbe: true });
5044
-
5045
- expect(enumerate).not.toHaveBeenCalled();
5046
- expect(acquire).toHaveBeenCalledWith('ble-id', null, true, 'V2', undefined, undefined, true);
5047
- expect(commands.disposed).toBe(false);
5048
- expect(commands.mainId).toBe('ble-id');
5049
- });
5050
-
5051
4904
  test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
5052
4905
  const method = new FirmwareUpdateV4({
5053
4906
  id: 1,
@@ -5437,108 +5290,6 @@ describe('Protocol V2 firmware update targets', () => {
5437
5290
  });
5438
5291
  });
5439
5292
 
5440
- test('treats a typed BLE disconnect during Protocol V2 normal reboot as expected', async () => {
5441
- const method = new FirmwareUpdateV4({
5442
- id: 1,
5443
- payload: {
5444
- method: 'firmwareUpdateV4',
5445
- },
5446
- });
5447
- const typedCall = jest
5448
- .fn()
5449
- .mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
5450
-
5451
- (method as any).device = stubDevice({
5452
- getCommands: () => ({ typedCall }),
5453
- });
5454
-
5455
- await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
5456
- message: 'Device rebooted successfully',
5457
- });
5458
- expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
5459
- DeviceRebootType.Normal
5460
- );
5461
- });
5462
-
5463
- test('uses a short timeout and continues after a BLE reboot response timeout', async () => {
5464
- const method = new FirmwareUpdateV4({
5465
- id: 1,
5466
- payload: {
5467
- method: 'firmwareUpdateV4',
5468
- },
5469
- });
5470
- const typedCall = jest
5471
- .fn()
5472
- .mockRejectedValue(
5473
- ERRORS.TypedError(
5474
- HardwareErrorCode.BleTimeoutError,
5475
- 'Lowlevel response timeout after 5000ms for DeviceReboot'
5476
- )
5477
- );
5478
-
5479
- (method as any).device = stubDevice({
5480
- getCommands: () => ({ typedCall }),
5481
- });
5482
-
5483
- await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
5484
- message: 'Device rebooted successfully',
5485
- });
5486
- expect(typedCall).toHaveBeenCalledWith(
5487
- 'DeviceReboot',
5488
- 'Success',
5489
- { reboot_type: DeviceRebootType.Normal },
5490
- { timeoutMs: 5000 }
5491
- );
5492
- expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
5493
- DeviceRebootType.Normal
5494
- );
5495
- });
5496
-
5497
- test('continues after a USB Protocol V2 reboot response timeout', async () => {
5498
- const method = new FirmwareUpdateV4({
5499
- id: 1,
5500
- payload: {
5501
- method: 'firmwareUpdateV4',
5502
- },
5503
- });
5504
- const timeoutError = Object.assign(
5505
- new Error('Protocol V2 response timeout after 5000ms for DeviceReboot'),
5506
- { code: 'response-timeout' }
5507
- );
5508
- const typedCall = jest.fn().mockRejectedValue(timeoutError);
5509
-
5510
- (method as any).device = stubDevice({
5511
- getCommands: () => ({ typedCall }),
5512
- });
5513
-
5514
- await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
5515
- message: 'Device rebooted successfully',
5516
- });
5517
- expect((method as any).device.markProtocolV2Reboot).toHaveBeenCalledWith(
5518
- DeviceRebootType.Normal
5519
- );
5520
- });
5521
-
5522
- test('propagates non-transport errors from a Protocol V2 reboot', async () => {
5523
- const method = new FirmwareUpdateV4({
5524
- id: 1,
5525
- payload: {
5526
- method: 'firmwareUpdateV4',
5527
- },
5528
- });
5529
- const rebootError = new Error('Device rejected reboot');
5530
- const typedCall = jest.fn().mockRejectedValue(rebootError);
5531
-
5532
- (method as any).device = stubDevice({
5533
- getCommands: () => ({ typedCall }),
5534
- });
5535
-
5536
- await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).rejects.toBe(
5537
- rebootError
5538
- );
5539
- expect((method as any).device.markProtocolV2Reboot).not.toHaveBeenCalled();
5540
- });
5541
-
5542
5293
  test('treats Android BLE transport release during Protocol V2 reboot as expected', async () => {
5543
5294
  const method = new FirmwareUpdateV4({
5544
5295
  id: 1,
@@ -5614,11 +5365,7 @@ describe('Protocol V2 firmware update targets', () => {
5614
5365
  expect(call).toHaveBeenCalledWith(
5615
5366
  'DeviceFirmwareUpdateRequest',
5616
5367
  {},
5617
- expect.objectContaining({
5618
- returnAfterWrite: true,
5619
- expectedTypes: ['Success'],
5620
- onResponseAfterWrite: expect.any(Function),
5621
- })
5368
+ { returnAfterWrite: true }
5622
5369
  );
5623
5370
  expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
5624
5371
  expect((method.postMessage as jest.Mock).mock.invocationCallOrder[0]).toBeLessThan(
@@ -5659,7 +5406,7 @@ describe('Protocol V2 firmware update targets', () => {
5659
5406
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5660
5407
  });
5661
5408
 
5662
- test('does not enter install state when the empty request fails', async () => {
5409
+ test('does not enter install state when writing the empty request fails', async () => {
5663
5410
  const method = new FirmwareUpdateV4({
5664
5411
  id: 1,
5665
5412
  payload: {
@@ -5667,7 +5414,7 @@ describe('Protocol V2 firmware update targets', () => {
5667
5414
  },
5668
5415
  });
5669
5416
  const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5670
- const call = jest.fn().mockRejectedValue(new Error('request failed'));
5417
+ const call = jest.fn().mockRejectedValue(new Error('write failed'));
5671
5418
 
5672
5419
  (method as any).device = stubDevice({
5673
5420
  getCommands: () => ({ typedCall, call }),
@@ -5682,117 +5429,13 @@ describe('Protocol V2 firmware update targets', () => {
5682
5429
  (method as any).protocolV2StartFirmwareUpdate({
5683
5430
  targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
5684
5431
  })
5685
- ).rejects.toThrow('request failed');
5432
+ ).rejects.toThrow('write failed');
5686
5433
 
5687
- expect(call).toHaveBeenCalledWith(
5688
- 'DeviceFirmwareUpdateRequest',
5689
- {},
5690
- expect.objectContaining({
5691
- returnAfterWrite: true,
5692
- expectedTypes: ['Success'],
5693
- onResponseAfterWrite: expect.any(Function),
5694
- })
5695
- );
5434
+ expect(call).toHaveBeenCalledTimes(1);
5696
5435
  expect(method.postTipMessage).not.toHaveBeenCalled();
5697
5436
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5698
5437
  });
5699
5438
 
5700
- test('continues to install polling when the empty request releases BLE transport', async () => {
5701
- const method = new FirmwareUpdateV4({
5702
- id: 1,
5703
- payload: {
5704
- method: 'firmwareUpdateV4',
5705
- },
5706
- });
5707
- const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5708
- const call = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
5709
-
5710
- (method as any).device = stubDevice({
5711
- getCommands: () => ({ typedCall, call }),
5712
- createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
5713
- toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
5714
- });
5715
- (method as any).isBleReconnect = jest.fn(() => true);
5716
- method.postMessage = jest.fn();
5717
-
5718
- await expect(
5719
- (method as any).protocolV2StartFirmwareUpdate({
5720
- targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
5721
- })
5722
- ).resolves.toBeUndefined();
5723
-
5724
- expect(call).toHaveBeenCalledWith(
5725
- 'DeviceFirmwareUpdateRequest',
5726
- {},
5727
- expect.objectContaining({
5728
- returnAfterWrite: true,
5729
- expectedTypes: ['Success'],
5730
- onResponseAfterWrite: expect.any(Function),
5731
- })
5732
- );
5733
- expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5734
- expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
5735
- });
5736
-
5737
- test.each([
5738
- { name: 'bootloader', targetId: 3, path: 'vol0:/bootloader.bin' },
5739
- { name: 'application', targetId: 4, path: 'vol0:/application_p1.bin' },
5740
- { name: 'secure element', targetId: 7, path: 'vol0:/se01.bin' },
5741
- ])(
5742
- 'reconnects a released BLE install link before polling $name status without DeviceInfo',
5743
- async ({ targetId, path }) => {
5744
- const method = new FirmwareUpdateV4({
5745
- id: 1,
5746
- payload: {
5747
- method: 'firmwareUpdateV4',
5748
- },
5749
- });
5750
- const targets = [{ target_id: targetId, path }];
5751
- const typedCall = jest
5752
- .fn()
5753
- .mockResolvedValueOnce({
5754
- type: 'DeviceFirmwareUpdateStatus',
5755
- message: { records: [{ ...targets[0], status: 1, payload_version: 0 }] },
5756
- })
5757
- .mockResolvedValueOnce({
5758
- type: 'DeviceFirmwareUpdateStatus',
5759
- message: { records: [{ ...targets[0], status: 2, payload_version: 0x010000 }] },
5760
- });
5761
- const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
5762
- const verifyProtocolV2ReconnectIdentity = jest.fn();
5763
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
5764
- callback: () => void
5765
- ) => {
5766
- callback();
5767
- return 0 as any;
5768
- }) as typeof setTimeout);
5769
-
5770
- (method as any).device = stubDevice({
5771
- getCommands: () => ({ typedCall }),
5772
- });
5773
- (method as any).isBleReconnect = jest.fn(() => true);
5774
- (method as any).protocolV2InstallNeedsReconnect = true;
5775
- (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
5776
- (method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
5777
- method.postProgressMessage = jest.fn();
5778
-
5779
- try {
5780
- await expect(
5781
- (method as any).waitForProtocolV2FirmwareUpdateComplete(targets, true)
5782
- ).resolves.toBeUndefined();
5783
- } finally {
5784
- setTimeoutSpy.mockRestore();
5785
- }
5786
-
5787
- expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
5788
- expect(verifyProtocolV2ReconnectIdentity).not.toHaveBeenCalled();
5789
- expect(typedCall.mock.calls.map(callArgs => callArgs[0])).toEqual([
5790
- 'DeviceFirmwareUpdateStatusGet',
5791
- 'DeviceFirmwareUpdateStatusGet',
5792
- ]);
5793
- }
5794
- );
5795
-
5796
5439
  test('polls only firmware status while Protocol V2 bootloader is installing', async () => {
5797
5440
  const method = new FirmwareUpdateV4({
5798
5441
  id: 1,
@@ -5836,7 +5479,7 @@ describe('Protocol V2 firmware update targets', () => {
5836
5479
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5837
5480
  });
5838
5481
 
5839
- test('keeps polling after an empty status response until the target finishes', async () => {
5482
+ test('keeps polling after install ACK until the target finishes', async () => {
5840
5483
  const method = new FirmwareUpdateV4({
5841
5484
  id: 1,
5842
5485
  payload: {
@@ -5845,10 +5488,7 @@ describe('Protocol V2 firmware update targets', () => {
5845
5488
  });
5846
5489
  const typedCall = jest
5847
5490
  .fn()
5848
- .mockResolvedValueOnce({
5849
- type: 'DeviceFirmwareUpdateStatus',
5850
- message: { records: [] },
5851
- })
5491
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
5852
5492
  .mockResolvedValueOnce({
5853
5493
  type: 'DeviceFirmwareUpdateStatus',
5854
5494
  message: { records: [] },
@@ -5990,38 +5630,6 @@ describe('Protocol V2 firmware update targets', () => {
5990
5630
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5991
5631
  });
5992
5632
 
5993
- test('does not count a stale finished target when only another target was live', () => {
5994
- const method = new FirmwareUpdateV4({
5995
- id: 1,
5996
- payload: {
5997
- method: 'firmwareUpdateV4',
5998
- },
5999
- });
6000
- const assertProtocolV2TargetStatus = (method as any).assertProtocolV2TargetStatus.bind(method);
6001
- const expectedTargetIds = new Set([3, 4]);
6002
- const expectedPaths = new Map([
6003
- [3, 'vol0:/bootloader.bin'],
6004
- [4, 'vol0:/application_p1.bin'],
6005
- ]);
6006
- const liveTargetIds = new Set([4]);
6007
- method.postProgressMessage = jest.fn();
6008
-
6009
- const completed = assertProtocolV2TargetStatus(
6010
- [
6011
- { target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
6012
- { target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
6013
- ],
6014
- expectedTargetIds,
6015
- expectedPaths,
6016
- liveTargetIds
6017
- );
6018
-
6019
- expect(completed).toBe(false);
6020
- expect((method as any).protocolV2CompletedTargetIds).toEqual(new Set([4]));
6021
- expect(method.postProgressMessage).toHaveBeenCalledWith(50, 'installingFirmware');
6022
- expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
6023
- });
6024
-
6025
5633
  test('ignores stale failed records until the current install starts', async () => {
6026
5634
  const method = new FirmwareUpdateV4({
6027
5635
  id: 1,
@@ -6070,141 +5678,6 @@ describe('Protocol V2 firmware update targets', () => {
6070
5678
  expect(typedCall).toHaveBeenCalledTimes(3);
6071
5679
  });
6072
5680
 
6073
- test('ignores leftover failed records while another leftover target is still pending', async () => {
6074
- const method = new FirmwareUpdateV4({
6075
- id: 1,
6076
- payload: {
6077
- method: 'firmwareUpdateV4',
6078
- },
6079
- });
6080
- const typedCall = jest
6081
- .fn()
6082
- .mockResolvedValueOnce({
6083
- type: 'DeviceFirmwareUpdateStatus',
6084
- message: {
6085
- records: [
6086
- { target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
6087
- { target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
6088
- ],
6089
- },
6090
- })
6091
- .mockResolvedValueOnce({
6092
- type: 'DeviceFirmwareUpdateStatus',
6093
- message: {
6094
- records: [
6095
- { target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
6096
- { target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
6097
- ],
6098
- },
6099
- })
6100
- .mockResolvedValueOnce({
6101
- type: 'DeviceFirmwareUpdateStatus',
6102
- message: {
6103
- records: [
6104
- { target_id: 3, status: 2, path: 'vol0:/bootloader.bin' },
6105
- { target_id: 4, status: 2, path: 'vol0:/application_p1.bin' },
6106
- ],
6107
- },
6108
- });
6109
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
6110
- callback: () => void
6111
- ) => {
6112
- callback();
6113
- return 0 as any;
6114
- }) as typeof setTimeout);
6115
-
6116
- (method as any).device = stubDevice({
6117
- getCommands: () => ({ typedCall }),
6118
- });
6119
- (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6120
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
6121
- method.postProgressMessage = jest.fn();
6122
-
6123
- try {
6124
- await (method as any).waitForProtocolV2FirmwareUpdateComplete(
6125
- [
6126
- { target_id: 3, path: 'vol0:/bootloader.bin' },
6127
- { target_id: 4, path: 'vol0:/application_p1.bin' },
6128
- ],
6129
- true
6130
- );
6131
- } finally {
6132
- setTimeoutSpy.mockRestore();
6133
- }
6134
-
6135
- expect(typedCall).toHaveBeenCalledTimes(3);
6136
- expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
6137
- });
6138
-
6139
- test('reports failed only after that target was live in the current install', async () => {
6140
- const method = new FirmwareUpdateV4({
6141
- id: 1,
6142
- payload: {
6143
- method: 'firmwareUpdateV4',
6144
- },
6145
- });
6146
- const typedCall = jest
6147
- .fn()
6148
- .mockResolvedValueOnce({
6149
- type: 'DeviceFirmwareUpdateStatus',
6150
- message: {
6151
- records: [
6152
- { target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
6153
- { target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
6154
- ],
6155
- },
6156
- })
6157
- .mockResolvedValueOnce({
6158
- type: 'DeviceFirmwareUpdateStatus',
6159
- message: {
6160
- records: [
6161
- { target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
6162
- { target_id: 4, status: 0, path: 'vol0:/application_p1.bin' },
6163
- ],
6164
- },
6165
- })
6166
- .mockResolvedValueOnce({
6167
- type: 'DeviceFirmwareUpdateStatus',
6168
- message: {
6169
- records: [
6170
- { target_id: 3, status: 0, path: 'vol0:/bootloader.bin' },
6171
- { target_id: 4, status: 3, path: 'vol0:/application_p1.bin' },
6172
- ],
6173
- },
6174
- });
6175
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
6176
- callback: () => void
6177
- ) => {
6178
- callback();
6179
- return 0 as any;
6180
- }) as typeof setTimeout);
6181
-
6182
- (method as any).device = stubDevice({
6183
- getCommands: () => ({ typedCall }),
6184
- });
6185
- (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6186
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(undefined);
6187
-
6188
- try {
6189
- await expect(
6190
- (method as any).waitForProtocolV2FirmwareUpdateComplete(
6191
- [
6192
- { target_id: 3, path: 'vol0:/bootloader.bin' },
6193
- { target_id: 4, path: 'vol0:/application_p1.bin' },
6194
- ],
6195
- true
6196
- )
6197
- ).rejects.toMatchObject({
6198
- errorCode: HardwareErrorCode.FirmwareError,
6199
- message: 'Protocol V2 firmware install failed',
6200
- });
6201
- } finally {
6202
- setTimeoutSpy.mockRestore();
6203
- }
6204
-
6205
- expect(typedCall).toHaveBeenCalledTimes(3);
6206
- });
6207
-
6208
5681
  test('waits five minutes before rejecting normal mode without install evidence', async () => {
6209
5682
  const method = new FirmwareUpdateV4({
6210
5683
  id: 1,
@@ -6311,6 +5784,7 @@ describe('Protocol V2 firmware update targets', () => {
6311
5784
  });
6312
5785
  const typedCall = jest
6313
5786
  .fn()
5787
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
6314
5788
  .mockRejectedValueOnce(new Error('Pro2 is rebooting'))
6315
5789
  .mockResolvedValueOnce({
6316
5790
  type: 'DeviceFirmwareUpdateStatus',
@@ -6327,7 +5801,6 @@ describe('Protocol V2 firmware update targets', () => {
6327
5801
  (method as any).device = stubDevice({
6328
5802
  getCommands: () => ({ typedCall }),
6329
5803
  });
6330
- (method as any).protocolV2InstallTerminalSuccessObserved = true;
6331
5804
  (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6332
5805
  (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
6333
5806
  (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
@@ -6348,54 +5821,6 @@ describe('Protocol V2 firmware update targets', () => {
6348
5821
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
6349
5822
  });
6350
5823
 
6351
- test('uses the confirmed install request as evidence after the same device returns', async () => {
6352
- const method = new FirmwareUpdateV4({
6353
- id: 1,
6354
- payload: {
6355
- method: 'firmwareUpdateV4',
6356
- },
6357
- });
6358
- const typedCall = jest
6359
- .fn()
6360
- .mockRejectedValueOnce(new Error('The USB device was disconnected'))
6361
- .mockResolvedValueOnce({
6362
- type: 'DeviceFirmwareUpdateStatus',
6363
- message: { records: [] },
6364
- });
6365
- const deviceInfo = { hw: { serial_no: 'PRO2-PHYSICAL-1' } };
6366
- const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
6367
- callback: () => void
6368
- ) => {
6369
- callback();
6370
- return 0 as any;
6371
- }) as typeof setTimeout);
6372
-
6373
- (method as any).device = stubDevice({
6374
- getCommands: () => ({ typedCall }),
6375
- });
6376
- (method as any).protocolV2InstallTerminalSuccessObserved = true;
6377
- (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6378
- (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
6379
- (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
6380
- method.postProgressMessage = jest.fn();
6381
-
6382
- try {
6383
- await expect(
6384
- (method as any).waitForProtocolV2FirmwareUpdateComplete(
6385
- [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
6386
- true
6387
- )
6388
- ).resolves.toBeUndefined();
6389
- } finally {
6390
- setTimeoutSpy.mockRestore();
6391
- }
6392
-
6393
- expect((method as any).reconnectProtocolV2Device).toHaveBeenCalledWith({
6394
- skipProtocolProbe: true,
6395
- });
6396
- expect(method.postProgressMessage).toHaveBeenLastCalledWith(100, 'installingFirmware');
6397
- });
6398
-
6399
5824
  test.each([
6400
5825
  [{ mode: 'normal', bootloaderMode: false }, true],
6401
5826
  [{ mode: 'notInitialized', bootloaderMode: false }, true],
@@ -6758,7 +6183,7 @@ describe('Protocol V2 firmware update targets', () => {
6758
6183
  expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
6759
6184
  expect(typedCall).toHaveBeenCalledWith(
6760
6185
  'DeviceFirmwareUpdateStatusGet',
6761
- 'DeviceFirmwareUpdateStatus',
6186
+ ['DeviceFirmwareUpdateStatus', 'Success'],
6762
6187
  expect.anything(),
6763
6188
  expect.anything()
6764
6189
  );
@@ -7040,12 +6465,10 @@ describe('Protocol V2 firmware update targets', () => {
7040
6465
  });
7041
6466
  expect(typedCall).toHaveBeenCalledWith(
7042
6467
  'DeviceFirmwareUpdateStatusGet',
7043
- 'DeviceFirmwareUpdateStatus',
6468
+ ['DeviceFirmwareUpdateStatus', 'Success'],
7044
6469
  {
7045
6470
  fields: {
7046
6471
  status: true,
7047
- progress_percent: true,
7048
- phase_info: true,
7049
6472
  payload_version: true,
7050
6473
  path: true,
7051
6474
  },
@@ -8760,36 +8183,6 @@ describe('Protocol V2 firmware update targets', () => {
8760
8183
  });
8761
8184
  });
8762
8185
 
8763
- test('does not retry or wrap a stale BLE bond during a V4 file transfer', async () => {
8764
- const method = new FirmwareUpdateV4({
8765
- id: 1,
8766
- payload: {
8767
- method: 'firmwareUpdateV4',
8768
- },
8769
- });
8770
- const staleBondError = ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
8771
- (method as any).fileWriteChunk = jest.fn().mockRejectedValue(staleBondError);
8772
- const recoverProtocolV2FileTransfer = jest.fn();
8773
- (method as any).recoverProtocolV2FileTransfer = recoverProtocolV2FileTransfer;
8774
- const source = await openFirmwareByteSource({
8775
- binary: new Uint8Array([1]).buffer,
8776
- });
8777
-
8778
- try {
8779
- await expect(
8780
- (method as any).protocolV2SourceUpdateProcess({
8781
- source,
8782
- filePath: 'vol1:firmware.bin',
8783
- processedSize: 0,
8784
- totalSize: 1,
8785
- })
8786
- ).rejects.toBe(staleBondError);
8787
- } finally {
8788
- await source?.close();
8789
- }
8790
- expect(recoverProtocolV2FileTransfer).not.toHaveBeenCalled();
8791
- });
8792
-
8793
8186
  test('throttles repeated transfer progress while preserving file completion', async () => {
8794
8187
  const method = new FirmwareUpdateV4({
8795
8188
  id: 1,
@@ -9296,28 +8689,26 @@ describe('Protocol V2 firmware update targets', () => {
9296
8689
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
9297
8690
  });
9298
8691
 
9299
- test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
8692
+ test('keeps the confirmation UI active after Stage and the empty Request are written', async () => {
9300
8693
  const method = new FirmwareUpdateV4({
9301
8694
  id: 1,
9302
8695
  payload: {
9303
8696
  method: 'firmwareUpdateV4',
9304
8697
  },
9305
8698
  });
9306
- let onResponseAfterWrite:
9307
- | ((response: { type: string; message: Record<string, never> }) => void)
9308
- | undefined;
8699
+ let resolveWrite: ((value: unknown) => void) | undefined;
9309
8700
  const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
9310
- const call = jest.fn().mockImplementation((_type, _message, options) => {
9311
- onResponseAfterWrite = options.onResponseAfterWrite;
9312
- return Promise.resolve({ type: 'WriteCompleted', message: {} });
9313
- });
9314
- const clearCancelableAction = jest.fn();
8701
+ const call = jest.fn().mockImplementation(
8702
+ () =>
8703
+ new Promise(resolve => {
8704
+ resolveWrite = resolve;
8705
+ })
8706
+ );
9315
8707
 
9316
8708
  (method as any).device = stubDevice({
9317
- getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
8709
+ getCommands: () => ({ typedCall, call }),
9318
8710
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
9319
8711
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
9320
- clearCancelableAction,
9321
8712
  });
9322
8713
  method.postMessage = jest.fn();
9323
8714
  method.postProgressMessage = jest.fn();
@@ -9338,25 +8729,18 @@ describe('Protocol V2 firmware update targets', () => {
9338
8729
  expect(call).toHaveBeenCalledWith(
9339
8730
  'DeviceFirmwareUpdateRequest',
9340
8731
  {},
9341
- expect.objectContaining({
9342
- returnAfterWrite: true,
9343
- expectedTypes: ['Success'],
9344
- onResponseAfterWrite: expect.any(Function),
9345
- })
8732
+ { returnAfterWrite: true }
9346
8733
  );
9347
8734
  expect(method.postTipMessage).not.toHaveBeenCalled();
9348
8735
  expect(method.postProgressMessage).not.toHaveBeenCalled();
9349
- expect(clearCancelableAction).not.toHaveBeenCalled();
9350
8736
 
8737
+ resolveWrite?.({ type: 'WriteCompleted', message: {} });
9351
8738
  await startPromise;
9352
- onResponseAfterWrite?.({ type: 'Success', message: {} });
9353
- expect(clearCancelableAction).toHaveBeenCalledTimes(1);
9354
- expect((method as any).protocolV2InstallTerminalSuccessObserved).toBe(true);
9355
8739
  expect(method.postTipMessage).not.toHaveBeenCalled();
9356
8740
  expect(method.postProgressMessage).not.toHaveBeenCalled();
9357
8741
  });
9358
8742
 
9359
- test('returns after the empty Protocol V2 firmware install request is written', async () => {
8743
+ test('sends an empty Protocol V2 firmware install request without waiting for its response', async () => {
9360
8744
  const method = new FirmwareUpdateV4({
9361
8745
  id: 1,
9362
8746
  payload: {
@@ -9367,13 +8751,10 @@ describe('Protocol V2 firmware update targets', () => {
9367
8751
  type: 'Success',
9368
8752
  message: { message: 'accepted' },
9369
8753
  });
9370
- const call = jest.fn().mockResolvedValue({
9371
- type: 'WriteCompleted',
9372
- message: {},
9373
- });
8754
+ const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
9374
8755
 
9375
8756
  (method as any).device = stubDevice({
9376
- getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
8757
+ getCommands: () => ({ typedCall, call }),
9377
8758
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
9378
8759
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
9379
8760
  });
@@ -9389,14 +8770,9 @@ describe('Protocol V2 firmware update targets', () => {
9389
8770
  expect(call).toHaveBeenCalledWith(
9390
8771
  'DeviceFirmwareUpdateRequest',
9391
8772
  {},
9392
- expect.objectContaining({
9393
- returnAfterWrite: true,
9394
- expectedTypes: ['Success'],
9395
- onResponseAfterWrite: expect.any(Function),
9396
- })
8773
+ { returnAfterWrite: true }
9397
8774
  );
9398
8775
  expect(method.postTipMessage).not.toHaveBeenCalled();
9399
- expect(method.postProgressMessage).not.toHaveBeenCalled();
9400
8776
  });
9401
8777
  });
9402
8778
 
@@ -9579,16 +8955,7 @@ describe('Protocol V2 firmware update method', () => {
9579
8955
  test('passes firmware update status fields through to Protocol V2', async () => {
9580
8956
  const typedCall = jest.fn().mockResolvedValue({
9581
8957
  message: {
9582
- records: [
9583
- {
9584
- target_id: 4,
9585
- status: 1,
9586
- progress_percent: 42,
9587
- phase_info: { phase: 1, progress_percent: 60 },
9588
- payload_version: 1,
9589
- path: 'vol1:application_p1.bin',
9590
- },
9591
- ],
8958
+ records: [{ target_id: 4, status: 2, payload_version: 1, path: 'vol1:application_p1.bin' }],
9592
8959
  },
9593
8960
  });
9594
8961
  const method = new DeviceGetFirmwareUpdateStatus({
@@ -9597,8 +8964,6 @@ describe('Protocol V2 firmware update method', () => {
9597
8964
  method: 'deviceGetFirmwareUpdateStatus',
9598
8965
  fields: {
9599
8966
  status: true,
9600
- progress_percent: true,
9601
- phase_info: true,
9602
8967
  payload_version: true,
9603
8968
  path: true,
9604
8969
  },
@@ -9610,16 +8975,7 @@ describe('Protocol V2 firmware update method', () => {
9610
8975
  });
9611
8976
 
9612
8977
  await expect(method.run()).resolves.toEqual({
9613
- records: [
9614
- {
9615
- target_id: 4,
9616
- status: 1,
9617
- progress_percent: 42,
9618
- phase_info: { phase: 1, progress_percent: 60 },
9619
- payload_version: 1,
9620
- path: 'vol1:application_p1.bin',
9621
- },
9622
- ],
8978
+ records: [{ target_id: 4, status: 2, payload_version: 1, path: 'vol1:application_p1.bin' }],
9623
8979
  });
9624
8980
  expect(typedCall).toHaveBeenCalledWith(
9625
8981
  'DeviceFirmwareUpdateStatusGet',
@@ -9627,8 +8983,6 @@ describe('Protocol V2 firmware update method', () => {
9627
8983
  {
9628
8984
  fields: {
9629
8985
  status: true,
9630
- progress_percent: true,
9631
- phase_info: true,
9632
8986
  payload_version: true,
9633
8987
  path: true,
9634
8988
  },