@onekeyfe/hd-core 1.2.0-alpha.135 → 1.2.0-alpha.137

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.
@@ -485,7 +485,7 @@ describe('DeviceCommands failure mapping', () => {
485
485
  });
486
486
  });
487
487
 
488
- it.each(['Cancelled on device', 'Confirm dismissed', 'Update cancelled'])(
488
+ it.each(['Cancelled on device', 'Confirm dismissed'])(
489
489
  'maps legacy Protocol V2 cancellation message "%s" without a subcode',
490
490
  async message => {
491
491
  const commands = createCommands();
@@ -128,7 +128,7 @@ describe('public device lifecycle events', () => {
128
128
 
129
129
  await device.acquire();
130
130
 
131
- expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined);
131
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined, undefined);
132
132
  expect(device.getProtocol()).toBe('V1');
133
133
  expect(device.originalDescriptor.protocolType).toBe('V1');
134
134
  });
@@ -146,7 +146,9 @@ describe('public device lifecycle events', () => {
146
146
 
147
147
  await device.acquire(undefined, { forceProtocolDetection: true });
148
148
 
149
- expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, undefined);
149
+ // Explicit active detection forwards forceProtocolDetection so the
150
+ // transport bypasses its protocol cache.
151
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, undefined, true);
150
152
  expect(device.getProtocol()).toBe('V2');
151
153
  expect(device.originalDescriptor.protocolType).toBe('V2');
152
154
  });
@@ -247,7 +249,7 @@ describe('public device lifecycle events', () => {
247
249
 
248
250
  await device.acquire('V2');
249
251
 
250
- expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V2', undefined);
252
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V2', undefined, undefined);
251
253
  expect(device.getProtocol()).toBe('V2');
252
254
  });
253
255
 
@@ -343,29 +345,25 @@ describe('public device lifecycle events', () => {
343
345
  }
344
346
  );
345
347
 
346
- test.each(['react-native', 'webusb', 'desktop-webusb'] as const)(
347
- 'sends a fallback Cancel for an acquired Protocol V2 %s call without a prompt callback',
348
- async env => {
349
- jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
350
- const device = createInitializedDevice('V2');
351
- const post = jest.fn().mockResolvedValue(undefined);
352
- const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
353
- const cancel = jest.fn().mockResolvedValue(undefined);
354
- device.originalDescriptor.session = device.mainId;
355
- (device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
356
- device.commands = {
357
- transport: { post },
358
- cancelDevice,
359
- cancel,
360
- } as never;
348
+ test('sends a fallback Cancel for an acquired Protocol V2 BLE call without a prompt callback', async () => {
349
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
350
+ const device = createInitializedDevice('V2');
351
+ const post = jest.fn().mockResolvedValue(undefined);
352
+ const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
353
+ const cancel = jest.fn().mockResolvedValue(undefined);
354
+ (device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
355
+ device.commands = {
356
+ transport: { post },
357
+ cancelDevice,
358
+ cancel,
359
+ } as never;
361
360
 
362
- await device.interruptionFromUser();
361
+ await device.interruptionFromUser();
363
362
 
364
- expect(cancelDevice).toHaveBeenCalledTimes(1);
365
- expect(post).toHaveBeenCalledWith(device.mainId, 'Cancel', {});
366
- expect(cancel).toHaveBeenCalledTimes(1);
367
- }
368
- );
363
+ expect(cancelDevice).toHaveBeenCalledTimes(1);
364
+ expect(post).toHaveBeenCalledWith(device.mainId, 'Cancel', {});
365
+ expect(cancel).toHaveBeenCalledTimes(1);
366
+ });
369
367
 
370
368
  test('waits for the canceled run to finish releasing before cancellation completes', async () => {
371
369
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
@@ -352,7 +352,75 @@ describe('Protocol V1 wallet identity initialization', () => {
352
352
  jest.restoreAllMocks();
353
353
  });
354
354
 
355
- test('verifies the live device id without resetting the cached wallet session', async () => {
355
+ test('sends no wallet context before identity is proven on first contact', async () => {
356
+ const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
357
+ // No cached features: there is no local evidence which device sits here.
358
+ deviceWalletSessionStore.set('device-a', 'hidden-a', 'session-a');
359
+ const typedCall = jest.fn().mockResolvedValue({
360
+ type: 'Features',
361
+ message: { device_id: 'intruder-device', session_id: 'intruder-session' },
362
+ });
363
+ device.commands = { typedCall } as never;
364
+ jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
365
+
366
+ await expect(
367
+ device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' })
368
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
369
+
370
+ // Exactly one context-free Initialize: neither the cached session_id nor
371
+ // the passphrase_state may reach an unproven device.
372
+ expect(typedCall).toHaveBeenCalledTimes(1);
373
+ expect(typedCall).toHaveBeenCalledWith(
374
+ 'Initialize',
375
+ 'Features',
376
+ { is_contains_attach: true },
377
+ expect.any(Object)
378
+ );
379
+ // The expected device's cached session is untouched.
380
+ expect(deviceWalletSessionStore.get('device-a', 'hidden-a')).toBe('session-a');
381
+ });
382
+
383
+ test('proves identity first, then resumes the wallet session on first contact', async () => {
384
+ const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
385
+ deviceWalletSessionStore.set('device-a', 'hidden-a', 'session-a');
386
+ const typedCall = jest
387
+ .fn()
388
+ .mockResolvedValueOnce({
389
+ type: 'Features',
390
+ message: { device_id: 'device-a', session_id: 'standard-session' },
391
+ })
392
+ .mockResolvedValueOnce({
393
+ type: 'Features',
394
+ message: { device_id: 'device-a', session_id: 'session-a' },
395
+ });
396
+ device.commands = { typedCall } as never;
397
+ jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
398
+
399
+ await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
400
+
401
+ expect(typedCall).toHaveBeenCalledTimes(2);
402
+ expect(typedCall).toHaveBeenNthCalledWith(
403
+ 1,
404
+ 'Initialize',
405
+ 'Features',
406
+ { is_contains_attach: true },
407
+ expect.any(Object)
408
+ );
409
+ expect(typedCall).toHaveBeenNthCalledWith(
410
+ 2,
411
+ 'Initialize',
412
+ 'Features',
413
+ expect.objectContaining({
414
+ session_id: 'session-a',
415
+ passphrase_state: 'hidden-a',
416
+ }),
417
+ expect.any(Object)
418
+ );
419
+ // The context-free identity call must not overwrite the cached session.
420
+ expect(deviceWalletSessionStore.get('device-a', 'hidden-a')).toBe('session-a');
421
+ });
422
+
423
+ test('purges cached sessions and rejects when the live device identity changes', async () => {
356
424
  const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
357
425
  device.features = {
358
426
  protocol: 'V1',
@@ -363,7 +431,7 @@ describe('Protocol V1 wallet identity initialization', () => {
363
431
  deviceWalletSessionStore.set('cached-device-a', 'hidden-a', 'session-a');
364
432
  const typedCall = jest.fn().mockResolvedValue({
365
433
  type: 'Features',
366
- message: { device_id: 'live-device-b' },
434
+ message: { device_id: 'live-device-b', session_id: 'wrong-device-session' },
367
435
  });
368
436
  device.commands = { typedCall } as never;
369
437
  jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
@@ -371,11 +439,76 @@ describe('Protocol V1 wallet identity initialization', () => {
371
439
  await expect(
372
440
  device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
373
441
  ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
442
+ // Identity is validated from the single Initialize round trip; no separate
443
+ // GetFeatures preflight.
374
444
  expect(typedCall).toHaveBeenCalledTimes(1);
375
- expect(typedCall).toHaveBeenCalledWith('GetFeatures', 'Features', {});
445
+ expect(typedCall).toHaveBeenCalledWith(
446
+ 'Initialize',
447
+ 'Features',
448
+ expect.objectContaining({ passphrase_state: 'hidden-a' }),
449
+ expect.any(Object)
450
+ );
451
+ // Identity change purges the previous device's cached sessions (pre-existing
452
+ // reconcileDeviceIdentity behavior — never carry sessions across devices)…
453
+ expect(deviceWalletSessionStore.get('cached-device-a', 'hidden-a')).toBeUndefined();
454
+ // …and the session the mismatched Initialize cached under the wrong
455
+ // device's identity is dropped as well.
456
+ expect(deviceWalletSessionStore.get('live-device-b', 'hidden-a')).toBeUndefined();
376
457
  });
377
458
 
378
- test('resumes a cached V1 wallet after a non-destructive live identity read', async () => {
459
+ test('drops the wrong-device session even when reconfigure fails after Initialize', async () => {
460
+ const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
461
+ device.features = {
462
+ protocol: 'V1',
463
+ deviceId: 'cached-device-a',
464
+ unlocked: true,
465
+ passphraseProtection: true,
466
+ } as never;
467
+ deviceWalletSessionStore.set('cached-device-a', 'hidden-a', 'session-a');
468
+ const typedCall = jest.fn().mockResolvedValue({
469
+ type: 'Features',
470
+ message: { device_id: 'live-device-b', session_id: 'wrong-device-session' },
471
+ });
472
+ device.commands = { typedCall } as never;
473
+ // Pre-encode resync succeeds; the post-response reconfigure inside
474
+ // callInitialize rejects — the session write has already happened by then.
475
+ jest
476
+ .spyOn(TransportManager, 'reconfigure')
477
+ .mockResolvedValueOnce(undefined)
478
+ .mockRejectedValueOnce(new Error('configure failed'));
479
+
480
+ await expect(
481
+ device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
482
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
483
+ // The wrong-device session must not survive the error path either.
484
+ expect(deviceWalletSessionStore.get('live-device-b', 'hidden-a')).toBeUndefined();
485
+ });
486
+
487
+ test('re-syncs the V1 message schema for this device before encoding Initialize', async () => {
488
+ const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
489
+ device.features = {
490
+ protocol: 'V1',
491
+ deviceId: 'device-a',
492
+ unlocked: true,
493
+ passphraseProtection: true,
494
+ } as never;
495
+ const typedCall = jest.fn().mockResolvedValue({
496
+ type: 'Features',
497
+ message: { device_id: 'device-a' },
498
+ });
499
+ device.commands = { typedCall } as never;
500
+ const reconfigure = jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
501
+
502
+ await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
503
+
504
+ // A stale process-global schema (from another device) would silently strip
505
+ // passphrase_state from the wire message, so the resync must come first.
506
+ expect(reconfigure.mock.invocationCallOrder[0]).toBeLessThan(
507
+ typedCall.mock.invocationCallOrder[0]
508
+ );
509
+ });
510
+
511
+ test('resumes a cached V1 wallet with a single identity-validated Initialize', async () => {
379
512
  const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
380
513
  device.features = {
381
514
  protocol: 'V1',
@@ -384,22 +517,17 @@ describe('Protocol V1 wallet identity initialization', () => {
384
517
  passphraseProtection: true,
385
518
  } as never;
386
519
  deviceWalletSessionStore.set('device-a', 'hidden-a', 'session-a');
387
- const typedCall = jest
388
- .fn()
389
- .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
390
- .mockResolvedValueOnce({
391
- type: 'Features',
392
- message: { device_id: 'device-a', session_id: 'session-a' },
393
- });
520
+ const typedCall = jest.fn().mockResolvedValueOnce({
521
+ type: 'Features',
522
+ message: { device_id: 'device-a', session_id: 'session-a' },
523
+ });
394
524
  device.commands = { typedCall } as never;
395
525
  jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
396
526
 
397
527
  await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
398
528
 
399
- expect(typedCall).toHaveBeenCalledTimes(2);
400
- expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
401
- expect(typedCall).toHaveBeenNthCalledWith(
402
- 2,
529
+ expect(typedCall).toHaveBeenCalledTimes(1);
530
+ expect(typedCall).toHaveBeenCalledWith(
403
531
  'Initialize',
404
532
  'Features',
405
533
  expect.objectContaining({
@@ -408,9 +536,10 @@ describe('Protocol V1 wallet identity initialization', () => {
408
536
  }),
409
537
  expect.any(Object)
410
538
  );
539
+ expect(deviceWalletSessionStore.get('device-a', 'hidden-a')).toBe('session-a');
411
540
  });
412
541
 
413
- test('selects the standard V1 wallet after a non-destructive live identity read', async () => {
542
+ test('selects the standard V1 wallet with a single identity-validated Initialize', async () => {
414
543
  const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
415
544
  device.features = {
416
545
  protocol: 'V1',
@@ -420,17 +549,14 @@ describe('Protocol V1 wallet identity initialization', () => {
420
549
  } as never;
421
550
  const typedCall = jest
422
551
  .fn()
423
- .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
424
552
  .mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } });
425
553
  device.commands = { typedCall } as never;
426
554
  jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
427
555
 
428
556
  await device.initialize({ deviceId: 'device-a' });
429
557
 
430
- expect(typedCall).toHaveBeenCalledTimes(2);
431
- expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
432
- expect(typedCall).toHaveBeenNthCalledWith(
433
- 2,
558
+ expect(typedCall).toHaveBeenCalledTimes(1);
559
+ expect(typedCall).toHaveBeenCalledWith(
434
560
  'Initialize',
435
561
  'Features',
436
562
  {
@@ -1,4 +1,4 @@
1
- import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import FirmwareUpdateV4 from '../../src/api/FirmwareUpdateV4';
4
4
 
@@ -127,40 +127,4 @@ describe('FirmwareUpdateV4 install polling', () => {
127
127
  errorCode: HardwareErrorCode.CallQueueActionCancelled,
128
128
  });
129
129
  });
130
-
131
- test('stops polling when the device cancels firmware installation', async () => {
132
- const method = new FirmwareUpdateV4({
133
- id: 1,
134
- payload: {
135
- method: 'firmwareUpdateV4',
136
- connectId: 'pro2-ble',
137
- },
138
- });
139
- const typedCall = jest
140
- .fn()
141
- .mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
142
- const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
143
-
144
- method.device = {
145
- getCommands: () => ({ typedCall }),
146
- } as unknown as Device;
147
- const firmwareUpdate = method as unknown as {
148
- waitForProtocolV2FirmwareUpdateComplete: (
149
- targets: Array<{ target_id: number; path: string }>
150
- ) => Promise<void>;
151
- reconnectProtocolV2Device: () => Promise<void>;
152
- };
153
- firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
154
-
155
- await expect(
156
- firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete([
157
- { target_id: 4, path: 'vol0:/application_p1.bin' },
158
- ])
159
- ).rejects.toMatchObject({
160
- errorCode: HardwareErrorCode.ActionCancelled,
161
- });
162
-
163
- expect(typedCall).toHaveBeenCalledTimes(1);
164
- expect(reconnectProtocolV2Device).not.toHaveBeenCalled();
165
- });
166
130
  });
@@ -1,12 +1,8 @@
1
1
  import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
2
 
3
3
  import ConfluxSignMessageCIP23 from '../src/api/conflux/ConfluxSignMessageCIP23';
4
- import DeviceChangePin from '../src/api/device/DeviceChangePin';
5
4
  import DeviceLock from '../src/api/device/DeviceLock';
6
5
  import DeviceSettings from '../src/api/device/DeviceSettings';
7
- import DeviceVerify from '../src/api/device/DeviceVerify';
8
- import DeviceWipe from '../src/api/device/DeviceWipe';
9
- import FirmwareUpdateV4 from '../src/api/FirmwareUpdateV4';
10
6
  import OpenWalletSession from '../src/api/OpenWalletSession';
11
7
  import { runMethodWithUnlockPolicy } from '../src/protocols/protocol-v2/unlockPolicyRunner';
12
8
 
@@ -195,61 +191,6 @@ describe('Protocol V2 unlock semantics', () => {
195
191
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
196
192
  });
197
193
 
198
- test.each([
199
- [
200
- 'PIN changes',
201
- () =>
202
- new DeviceChangePin({
203
- id: 1,
204
- payload: { method: 'deviceChangePin', remove: false },
205
- }),
206
- ],
207
- ['device wipe', () => new DeviceWipe({ id: 1, payload: { method: 'deviceWipe' } })],
208
- [
209
- 'firmware updates',
210
- () =>
211
- new FirmwareUpdateV4({
212
- id: 1,
213
- payload: { method: 'firmwareUpdateV4', platform: 'desktop' } as any,
214
- }),
215
- ],
216
- [
217
- 'genuine-device verification',
218
- () =>
219
- new DeviceVerify({
220
- id: 1,
221
- payload: { method: 'deviceVerify', dataHex: '00' },
222
- }),
223
- ],
224
- ])('allows either PIN type when pre-unlocking %s', async (_name, createMethod) => {
225
- const method = createMethod();
226
- method.init();
227
- const features = { unlocked: false };
228
- const device = {
229
- features,
230
- commands: {
231
- typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
232
- },
233
- isProtocolV2: () => true,
234
- isBootloader: () => false,
235
- isRomloader: () => false,
236
- updateProtocolV2Status: jest.fn(() => features),
237
- unlockDevice: jest.fn().mockImplementation(() => {
238
- features.unlocked = true;
239
- return Promise.resolve(features);
240
- }),
241
- };
242
- method.run = jest.fn().mockResolvedValue({ message: 'ok' });
243
-
244
- await expect(runMethodWithUnlockPolicy(method, device as any)).resolves.toEqual({
245
- message: 'ok',
246
- });
247
-
248
- expect(method.unlockPolicy).toBe('unlock-before-run');
249
- expect(method.getSupportedProtocols()).toContain('V2');
250
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
251
- });
252
-
253
194
  test('pre-unlocks a locked standard wallet before wallet-session preparation', async () => {
254
195
  const calls: string[] = [];
255
196
  const features = {
@@ -5501,6 +5501,7 @@ describe('Protocol V2 firmware update targets', () => {
5501
5501
  'DeviceFirmwareUpdateStatusGet',
5502
5502
  'DeviceFirmwareUpdateStatusGet',
5503
5503
  ]);
5504
+ expect((method as any).protocolV2FinalStatusVerified).toBe(true);
5504
5505
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
5505
5506
  });
5506
5507
 
@@ -5648,7 +5649,7 @@ describe('Protocol V2 firmware update targets', () => {
5648
5649
  expect(typedCall).toHaveBeenCalledTimes(3);
5649
5650
  });
5650
5651
 
5651
- test('waits five minutes before rejecting normal mode without install evidence', async () => {
5652
+ test('rejects normal mode without install ACK, target status, or a version change', async () => {
5652
5653
  const method = new FirmwareUpdateV4({
5653
5654
  id: 1,
5654
5655
  payload: {
@@ -5662,7 +5663,7 @@ describe('Protocol V2 firmware update targets', () => {
5662
5663
  let now = 0;
5663
5664
  jest.spyOn(Date, 'now').mockImplementation(() => now);
5664
5665
  jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
5665
- now += 60 * 1000;
5666
+ now += 31 * 1000;
5666
5667
  callback();
5667
5668
  return 0 as any;
5668
5669
  }) as typeof setTimeout);
@@ -5679,14 +5680,9 @@ describe('Protocol V2 firmware update targets', () => {
5679
5680
  (method as any).waitForProtocolV2FirmwareUpdateComplete([
5680
5681
  { target_id: 4, path: 'vol0:/application_p1.bin' },
5681
5682
  ])
5682
- ).rejects.toMatchObject({
5683
- errorCode: HardwareErrorCode.FirmwareError,
5684
- message: 'Firmware installation failed',
5685
- params: { firmwareUpdateCode: 'FirmwareInstallTimeout' },
5686
- });
5683
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.FirmwareError });
5687
5684
 
5688
5685
  expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
5689
- expect(typedCall).toHaveBeenCalledTimes(5);
5690
5686
  });
5691
5687
 
5692
5688
  test('accepts ACK-less normal mode after the requested target version changes', async () => {
@@ -5903,8 +5899,8 @@ describe('Protocol V2 firmware update targets', () => {
5903
5899
  { target_id: 4, path: 'vol0:/application_p1.bin' },
5904
5900
  ])
5905
5901
  ).rejects.toMatchObject({
5906
- errorCode: HardwareErrorCode.FirmwareError,
5907
- message: 'Firmware installation failed',
5902
+ errorCode: HardwareErrorCode.RuntimeError,
5903
+ message: 'Protocol V2 install status conflicts with target 4',
5908
5904
  params: {
5909
5905
  firmwareUpdateCode: 'FirmwareInstallStatusConflict',
5910
5906
  },
@@ -5941,7 +5937,7 @@ describe('Protocol V2 firmware update targets', () => {
5941
5937
  expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
5942
5938
  });
5943
5939
 
5944
- test('accepts unobservable final versions after the status endpoint fallback', () => {
5940
+ test('rejects unobservable final versions after the status endpoint fallback', () => {
5945
5941
  const method = new FirmwareUpdateV4({
5946
5942
  id: 1,
5947
5943
  payload: {
@@ -5961,30 +5957,8 @@ describe('Protocol V2 firmware update targets', () => {
5961
5957
  patch_version: 0,
5962
5958
  };
5963
5959
 
5964
- expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
5965
- });
5966
-
5967
- test('still rejects an unobservable non-app version after the status endpoint fallback', () => {
5968
- const method = new FirmwareUpdateV4({
5969
- id: 1,
5970
- payload: {
5971
- method: 'firmwareUpdateV4',
5972
- platform: 'desktop',
5973
- targetsToUpdate: ['se01'],
5974
- expectedTargetVersions: {
5975
- se01: '1.0.0',
5976
- },
5977
- },
5978
- });
5979
- method.init();
5980
- (method as any).protocolV2LatestFinalFeatures = {
5981
- major_version: 3,
5982
- minor_version: 0,
5983
- patch_version: 0,
5984
- };
5985
-
5986
5960
  expect(() => (method as any).assertExpectedProtocolV2Versions()).toThrow(
5987
- 'target se01 has no observable final version after status fallback'
5961
+ 'has no observable final version after status fallback'
5988
5962
  );
5989
5963
  });
5990
5964
 
@@ -6030,7 +6004,7 @@ describe('Protocol V2 firmware update targets', () => {
6030
6004
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
6031
6005
  });
6032
6006
 
6033
- test('keeps polling missing target records until the five-minute timeout', async () => {
6007
+ test('fails clearly when a requested target stays absent from the full status dump', async () => {
6034
6008
  const method = new FirmwareUpdateV4({
6035
6009
  id: 1,
6036
6010
  payload: {
@@ -6046,7 +6020,7 @@ describe('Protocol V2 firmware update targets', () => {
6046
6020
  let now = 0;
6047
6021
  jest.spyOn(Date, 'now').mockImplementation(() => now);
6048
6022
  jest.spyOn(global, 'setTimeout').mockImplementation(((callback: () => void) => {
6049
- now += 60 * 1000;
6023
+ now += 31 * 1000;
6050
6024
  callback();
6051
6025
  return 0 as any;
6052
6026
  }) as typeof setTimeout);
@@ -6065,13 +6039,12 @@ describe('Protocol V2 firmware update targets', () => {
6065
6039
  ])
6066
6040
  ).rejects.toMatchObject({
6067
6041
  errorCode: HardwareErrorCode.FirmwareError,
6068
- message: 'Firmware installation failed',
6069
- params: { firmwareUpdateCode: 'FirmwareInstallTimeout' },
6042
+ message: expect.stringContaining('targetIds=3'),
6070
6043
  });
6071
- expect(typedCall).toHaveBeenCalledTimes(5);
6044
+ expect(typedCall).toHaveBeenCalledTimes(2);
6072
6045
  });
6073
6046
 
6074
- test('keeps polling incomplete records after a Pro2 reboot interruption', async () => {
6047
+ test('resets the missing-record grace period after a Pro2 reboot interruption', async () => {
6075
6048
  const method = new FirmwareUpdateV4({
6076
6049
  id: 1,
6077
6050
  payload: {
@@ -6124,7 +6097,7 @@ describe('Protocol V2 firmware update targets', () => {
6124
6097
  expect(typedCall).toHaveBeenCalledTimes(4);
6125
6098
  });
6126
6099
 
6127
- test('uses a five-minute Pro2 install status window', async () => {
6100
+ test('uses an eight-minute Pro2 install status window', async () => {
6128
6101
  const method = new FirmwareUpdateV4({
6129
6102
  id: 1,
6130
6103
  payload: {
@@ -6134,17 +6107,13 @@ describe('Protocol V2 firmware update targets', () => {
6134
6107
  jest
6135
6108
  .spyOn(Date, 'now')
6136
6109
  .mockReturnValueOnce(0)
6137
- .mockReturnValue(5 * 60 * 1000);
6110
+ .mockReturnValue(8 * 60 * 1000);
6138
6111
 
6139
6112
  await expect(
6140
6113
  (method as any).waitForProtocolV2FirmwareUpdateComplete([
6141
6114
  { target_id: 4, path: 'vol0:/application_p1.bin' },
6142
6115
  ])
6143
- ).rejects.toMatchObject({
6144
- errorCode: HardwareErrorCode.FirmwareError,
6145
- message: 'Firmware installation failed',
6146
- params: { firmwareUpdateCode: 'FirmwareInstallTimeout' },
6147
- });
6116
+ ).rejects.toThrow('within 480s');
6148
6117
  });
6149
6118
 
6150
6119
  test('uses a 90-second Pro2 bootloader reconnect window', async () => {
@@ -6222,7 +6191,7 @@ describe('Protocol V2 firmware update targets', () => {
6222
6191
  ],
6223
6192
  new Set([4, 10])
6224
6193
  )
6225
- ).toThrow('Firmware installation failed');
6194
+ ).toThrow('Protocol V2 install status conflicts with target 4');
6226
6195
  expect(method.postProgressMessage).not.toHaveBeenCalled();
6227
6196
  expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
6228
6197
 
@@ -6262,8 +6231,8 @@ describe('Protocol V2 firmware update targets', () => {
6262
6231
  throw new Error('Expected Protocol V2 failed firmware status to throw');
6263
6232
  } catch (error: any) {
6264
6233
  expect(error.errorCode).toBe(HardwareErrorCode.FirmwareError);
6265
- expect(error.message).toBe('Firmware installation failed');
6266
- expect(error.params).toEqual({ firmwareUpdateCode: 'FirmwareInstallFailed' });
6234
+ expect(error.message).toContain('FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY');
6235
+ expect(error.message).toContain('vol0:/application_p1.bin');
6267
6236
  }
6268
6237
  });
6269
6238
 
@@ -6300,8 +6269,7 @@ describe('Protocol V2 firmware update targets', () => {
6300
6269
  ])
6301
6270
  ).rejects.toMatchObject({
6302
6271
  errorCode: HardwareErrorCode.FirmwareError,
6303
- message: 'Firmware installation failed',
6304
- params: { firmwareUpdateCode: 'FirmwareInstallFailed' },
6272
+ message: expect.stringContaining('vol0:/application_p1.bin'),
6305
6273
  });
6306
6274
  expect(typedCall).toHaveBeenCalledWith(
6307
6275
  'DeviceFirmwareUpdateStatusGet',
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceChangePin.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceChangePin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,SAAS,CAAC;IAChE,qBAAqB;IAIrB,IAAI;IAuBE,GAAG;CAmBV"}
1
+ {"version":3,"file":"DeviceChangePin.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceChangePin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,SAAS,CAAC;IAChE,qBAAqB;IAIrB,IAAI;IAoBE,GAAG;CAmBV"}
@@ -2,7 +2,6 @@ import { BaseMethod } from '../BaseMethod';
2
2
  import type { BixinVerifyDeviceRequest } from '@onekeyfe/hd-transport';
3
3
  import type { DeviceVerifySignature } from '../../types';
4
4
  export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
5
- getSupportedProtocols(): readonly ["V1", "V2"];
6
5
  init(): void;
7
6
  run(): Promise<DeviceVerifySignature>;
8
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceVerify.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceVerify.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,UAAU,CAAC,wBAAwB,CAAC;IAC5E,qBAAqB;IAIrB,IAAI;IAgBE,GAAG;CAsCV"}
1
+ {"version":3,"file":"DeviceVerify.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceVerify.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,UAAU,CAAC,wBAAwB,CAAC;IAC5E,IAAI;IAYE,GAAG;CAsCV"}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceWipe.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceWipe.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,UAAU,CAAC,UAAU,CAAC;IAC5D,qBAAqB;IAIrB,IAAI;IAiBE,GAAG;CAcV"}
1
+ {"version":3,"file":"DeviceWipe.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceWipe.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,UAAU,CAAC,UAAU,CAAC;IAC5D,qBAAqB;IAIrB,IAAI;IAcE,GAAG;CAcV"}