@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.41
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 +0 -78
- package/__tests__/DeviceEventRegistration.test.ts +0 -6
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +58 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/get-device-state.test.ts +12 -132
- package/__tests__/homescreen.test.ts +1 -41
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +18 -259
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -86
- package/__tests__/protocol-v2.test.ts +632 -333
- package/__tests__/public-pro2-api-boundary.test.ts +0 -1
- package/dist/api/BaseMethod.d.ts +1 -3
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +22 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.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/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +0 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/deviceEventRegistration.d.ts +0 -2
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +4 -22
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +0 -4
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -27
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +181 -88
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3162 -1614
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +2 -0
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +10 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -4
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +69 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +10 -3
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +0 -3
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -2
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +0 -4
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.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/pro2Wallpaper.d.ts +0 -10
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +10 -8
- package/src/api/CheckAllFirmwareRelease.ts +51 -6
- package/src/api/FirmwareUpdateV2.ts +298 -120
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +748 -281
- package/src/api/GetPassphraseState.ts +1 -7
- package/src/api/OpenWalletSession.ts +17 -54
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +11 -32
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/device/DeviceUpdateBootloader.ts +122 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +57 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +745 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +4 -11
- package/src/api/index.ts +0 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -3
- package/src/core/deviceEventRegistration.ts +0 -4
- package/src/core/index.ts +75 -118
- package/src/data/messages/messages-protocol-v2.json +0 -10
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +21 -193
- package/src/events/device.ts +0 -4
- package/src/events/logBlockEvent.ts +0 -1
- package/src/events/ui-request.ts +2 -32
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -3
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/features.ts +2 -9
- package/src/protocols/protocol-v2/lockedError.ts +10 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +5 -31
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -1
- package/src/protocols/protocol-v2/unlockRetry.ts +109 -0
- package/src/protocols/protocol-v2/walletSession.ts +18 -93
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -2
- package/src/types/api/protocolV2.ts +0 -13
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +0 -4
- package/src/utils/homescreen.ts +6 -32
- package/src/utils/index.ts +1 -6
- package/src/utils/pro2Wallpaper.ts +8 -35
- package/__tests__/deviceUploadNft.test.ts +0 -187
- package/__tests__/pro2Nft.test.ts +0 -108
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +0 -56
- package/__tests__/uiProgressThrottle.test.ts +0 -74
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +0 -30
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +0 -22
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +0 -1
- package/dist/utils/pro2Nft.d.ts +0 -29
- package/dist/utils/pro2Nft.d.ts.map +0 -1
- package/dist/utils/uiProgressThrottle.d.ts +0 -3
- package/dist/utils/uiProgressThrottle.d.ts.map +0 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +0 -166
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +0 -117
- package/src/utils/pro2Nft.ts +0 -91
- package/src/utils/uiProgressThrottle.ts +0 -63
|
@@ -61,7 +61,6 @@ import { createCoreApi } from '../src/inject';
|
|
|
61
61
|
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
62
62
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
63
63
|
import { DevicePool } from '../src/device/DevicePool';
|
|
64
|
-
import { DEVICE } from '../src/events';
|
|
65
64
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
66
65
|
import {
|
|
67
66
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
@@ -79,10 +78,7 @@ import {
|
|
|
79
78
|
getProtocolV2WalletSession,
|
|
80
79
|
refreshProtocolV2DeviceStatus,
|
|
81
80
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
82
|
-
import {
|
|
83
|
-
createProtocolV2UnlockContext,
|
|
84
|
-
runMethodWithUnlockPolicy,
|
|
85
|
-
} from '../src/protocols/protocol-v2/unlockPolicyRunner';
|
|
81
|
+
import { runMethodWithUnlockRetry } from '../src/protocols/protocol-v2/unlockRetry';
|
|
86
82
|
import { BaseMethod } from '../src/api/BaseMethod';
|
|
87
83
|
import {
|
|
88
84
|
buildProtocolV1FeaturesPayload,
|
|
@@ -96,6 +92,7 @@ import {
|
|
|
96
92
|
getMethodVersionRange,
|
|
97
93
|
} from '../src/utils';
|
|
98
94
|
import { getDeviceFirmwareVersion } from '../src/utils/deviceVersionUtils';
|
|
95
|
+
import { openFirmwareByteSource } from '../src/api/firmware/FirmwareArtifactSource';
|
|
99
96
|
import {
|
|
100
97
|
getPassphraseState,
|
|
101
98
|
getPassphraseStateWithRefreshDeviceInfo,
|
|
@@ -244,7 +241,7 @@ describe('UploadPortfolio', () => {
|
|
|
244
241
|
(method as any).device = device;
|
|
245
242
|
|
|
246
243
|
method.init();
|
|
247
|
-
await
|
|
244
|
+
await runMethodWithUnlockRetry(method, device as any);
|
|
248
245
|
|
|
249
246
|
expect(unlockDevice).not.toHaveBeenCalled();
|
|
250
247
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
@@ -420,16 +417,14 @@ function stubDevice<T extends Record<string, any>>(device: T): T {
|
|
|
420
417
|
}
|
|
421
418
|
return d.features;
|
|
422
419
|
};
|
|
423
|
-
d.ensureProtocolV2RuntimeContext ??= (timeoutMs?: number) =>
|
|
424
|
-
requestProtocolV2ProtocolInfo({
|
|
425
|
-
commands: d.getCommands?.() ?? d.commands,
|
|
426
|
-
timeoutMs,
|
|
427
|
-
});
|
|
428
420
|
d.probeProtocolV2RuntimeState ??= async (
|
|
429
421
|
deviceInfo: ProtocolV2DeviceInfo,
|
|
430
422
|
timeoutMs?: number
|
|
431
423
|
) => {
|
|
432
|
-
const protocolInfo = await
|
|
424
|
+
const protocolInfo = await requestProtocolV2ProtocolInfo({
|
|
425
|
+
commands: d.getCommands(),
|
|
426
|
+
timeoutMs,
|
|
427
|
+
});
|
|
433
428
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
434
429
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
435
430
|
return d.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -810,7 +805,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
810
805
|
).resolves.toEqual({
|
|
811
806
|
passphraseState: 'state-1',
|
|
812
807
|
newSession: 'session-1',
|
|
813
|
-
unlockedAttachPin:
|
|
808
|
+
unlockedAttachPin: undefined,
|
|
814
809
|
resumed: false,
|
|
815
810
|
});
|
|
816
811
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
@@ -1558,70 +1553,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1558
1553
|
expect(device.getInternalState()).toBeUndefined();
|
|
1559
1554
|
});
|
|
1560
1555
|
|
|
1561
|
-
test.each([false, true])(
|
|
1562
|
-
'rejects a different Attach PIN wallet when Pro2 lock failure is %s',
|
|
1563
|
-
async lockFails => {
|
|
1564
|
-
const deviceId = 'attach-mismatch-device';
|
|
1565
|
-
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1566
|
-
(device as any).features = normalizeProtocolV2Features(
|
|
1567
|
-
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1568
|
-
{
|
|
1569
|
-
status: {
|
|
1570
|
-
device_id: deviceId,
|
|
1571
|
-
unlocked: true,
|
|
1572
|
-
passphrase_enabled: true,
|
|
1573
|
-
attach_to_pin_enabled: true,
|
|
1574
|
-
unlocked_by_attach_to_pin: false,
|
|
1575
|
-
},
|
|
1576
|
-
}
|
|
1577
|
-
);
|
|
1578
|
-
device.passphraseState = 'expected-state';
|
|
1579
|
-
|
|
1580
|
-
const typedCall = jest.fn((request: string) => {
|
|
1581
|
-
if (request === 'ProtocolInfoRequest') return { message: { version: 2 } };
|
|
1582
|
-
if (request === 'DeviceSessionAskPin') return { message: { message: 'PIN verified' } };
|
|
1583
|
-
if (request === 'DeviceStatusGet') {
|
|
1584
|
-
return {
|
|
1585
|
-
message: {
|
|
1586
|
-
device_id: deviceId,
|
|
1587
|
-
unlocked: true,
|
|
1588
|
-
passphrase_enabled: true,
|
|
1589
|
-
attach_to_pin_enabled: true,
|
|
1590
|
-
unlocked_by_attach_to_pin: true,
|
|
1591
|
-
},
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
if (request === 'DeviceSessionGet') {
|
|
1595
|
-
return {
|
|
1596
|
-
message: {
|
|
1597
|
-
btc_test_address: 'wrong-attach-state',
|
|
1598
|
-
session_id: 'wrong-attach-session',
|
|
1599
|
-
},
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
if (request === 'LockDevice') {
|
|
1603
|
-
if (lockFails) throw new Error('Lock unsupported');
|
|
1604
|
-
return { message: { message: 'Device locked' } };
|
|
1605
|
-
}
|
|
1606
|
-
throw new Error(`Unexpected request: ${request}`);
|
|
1607
|
-
});
|
|
1608
|
-
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1609
|
-
(device as any).commands = { typedCall, promptPassphrase };
|
|
1610
|
-
|
|
1611
|
-
await expect(
|
|
1612
|
-
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
1613
|
-
).rejects.toMatchObject({
|
|
1614
|
-
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
1615
|
-
});
|
|
1616
|
-
|
|
1617
|
-
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1618
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1619
|
-
expect(typedCall).toHaveBeenCalledWith('LockDevice', 'Success', {});
|
|
1620
|
-
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(1);
|
|
1621
|
-
expect(device.getInternalState()).toBeUndefined();
|
|
1622
|
-
}
|
|
1623
|
-
);
|
|
1624
|
-
|
|
1625
1556
|
test('recovers an expired Attach PIN wallet through the unified wallet selector', async () => {
|
|
1626
1557
|
const device = Device.fromDescriptor({
|
|
1627
1558
|
id: 'cache-device-attach-recovery',
|
|
@@ -1678,17 +1609,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1678
1609
|
}),
|
|
1679
1610
|
{ cancelDeviceOnReject: false }
|
|
1680
1611
|
);
|
|
1681
|
-
expect(unlockDevice).toHaveBeenCalledWith(
|
|
1682
|
-
DeviceSessionPinType.AttachToPin,
|
|
1683
|
-
expect.objectContaining({
|
|
1684
|
-
emitUiEvent: false,
|
|
1685
|
-
interaction: expect.objectContaining({
|
|
1686
|
-
phase: 'pin',
|
|
1687
|
-
transition: 'start',
|
|
1688
|
-
protocol: 'V2',
|
|
1689
|
-
}),
|
|
1690
|
-
})
|
|
1691
|
-
);
|
|
1612
|
+
expect(unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
|
|
1692
1613
|
expect(device.getInternalState()).toBe('new-attach-session');
|
|
1693
1614
|
});
|
|
1694
1615
|
|
|
@@ -1972,10 +1893,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1972
1893
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
1973
1894
|
});
|
|
1974
1895
|
|
|
1975
|
-
test('
|
|
1896
|
+
test('marks an interactive cache miss as a wallet-session recovery', async () => {
|
|
1976
1897
|
const typedCall = jest
|
|
1977
1898
|
.fn()
|
|
1978
1899
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1900
|
+
.mockResolvedValueOnce({ message: { message: 'Passphrase prepared' } })
|
|
1901
|
+
.mockResolvedValueOnce({ message: { unlocked: true, unlocked_attach_pin: false } })
|
|
1979
1902
|
.mockResolvedValueOnce({
|
|
1980
1903
|
message: {
|
|
1981
1904
|
btc_test_address: 'expected-state',
|
|
@@ -2003,8 +1926,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2003
1926
|
await expect(
|
|
2004
1927
|
getProtocolV2WalletSession(device, { expectedPassphraseState: 'expected-state' })
|
|
2005
1928
|
).resolves.toMatchObject({ passphraseState: 'expected-state' });
|
|
2006
|
-
expect(
|
|
2007
|
-
|
|
1929
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
1930
|
+
{
|
|
1931
|
+
existsAttachPinUser: false,
|
|
1932
|
+
deviceOnly: false,
|
|
1933
|
+
source: 'wallet-session-coordinator',
|
|
1934
|
+
reason: 'session-recovery',
|
|
1935
|
+
expectedPassphraseState: 'expected-state',
|
|
1936
|
+
},
|
|
1937
|
+
{ cancelDeviceOnReject: false }
|
|
1938
|
+
);
|
|
2008
1939
|
});
|
|
2009
1940
|
|
|
2010
1941
|
test('reuses the Pro2 session opened by legacy getPassphraseState on the next App call', async () => {
|
|
@@ -2070,6 +2001,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2070
2001
|
true
|
|
2071
2002
|
);
|
|
2072
2003
|
expect(typedCall.mock.calls).toEqual([
|
|
2004
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
2073
2005
|
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-pro2-app' }],
|
|
2074
2006
|
]);
|
|
2075
2007
|
});
|
|
@@ -2337,13 +2269,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2337
2269
|
);
|
|
2338
2270
|
|
|
2339
2271
|
expect(device.getInternalState()).toBeUndefined();
|
|
2340
|
-
expect(typedCall).
|
|
2341
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2272
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
2342
2273
|
passphrase: 'host hidden wallet',
|
|
2343
2274
|
on_device: false,
|
|
2344
2275
|
});
|
|
2345
|
-
expect(typedCall).
|
|
2346
|
-
expect(typedCall).
|
|
2276
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2277
|
+
expect(typedCall).toHaveBeenNthCalledWith(4, 'DeviceSessionGet', 'DeviceSession', {});
|
|
2347
2278
|
});
|
|
2348
2279
|
|
|
2349
2280
|
test('useEmptyPassphrase ignores a stale hidden-wallet state during Protocol V2 safety check', async () => {
|
|
@@ -2380,9 +2311,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2380
2311
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2381
2312
|
eventless_wallet_session: true,
|
|
2382
2313
|
});
|
|
2383
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
2384
|
-
type: DeviceSessionPinType.Main,
|
|
2385
|
-
});
|
|
2386
2314
|
});
|
|
2387
2315
|
|
|
2388
2316
|
test('marks fallback features as unavailable when DeviceInfo is missing', () => {
|
|
@@ -2753,9 +2681,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2753
2681
|
timeoutMs: PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
2754
2682
|
}
|
|
2755
2683
|
);
|
|
2756
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2757
|
-
eventless_wallet_session: true,
|
|
2758
|
-
});
|
|
2684
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2759
2685
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceStatusGet', 'DeviceStatus', {});
|
|
2760
2686
|
});
|
|
2761
2687
|
|
|
@@ -2793,9 +2719,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
2793
2719
|
}),
|
|
2794
2720
|
expect.anything()
|
|
2795
2721
|
);
|
|
2796
|
-
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {
|
|
2797
|
-
eventless_wallet_session: true,
|
|
2798
|
-
});
|
|
2722
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
2799
2723
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
2800
2724
|
expect(device.features).toMatchObject({
|
|
2801
2725
|
deviceId: null,
|
|
@@ -3061,6 +2985,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3061
2985
|
fw: { application: { version: '1.2.4' } },
|
|
3062
2986
|
},
|
|
3063
2987
|
})
|
|
2988
|
+
.mockResolvedValueOnce({
|
|
2989
|
+
type: 'ProtocolInfo',
|
|
2990
|
+
message: protocolV2ApplicationInfo,
|
|
2991
|
+
})
|
|
3064
2992
|
.mockResolvedValueOnce({
|
|
3065
2993
|
type: 'DeviceStatus',
|
|
3066
2994
|
message: { init_states: true, unlocked: true },
|
|
@@ -3082,7 +3010,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3082
3010
|
firmwareVersion: '1.2.4',
|
|
3083
3011
|
passphraseProtection: null,
|
|
3084
3012
|
});
|
|
3085
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
3013
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
3086
3014
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
3087
3015
|
4,
|
|
3088
3016
|
'DeviceInfoGet',
|
|
@@ -3196,23 +3124,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3196
3124
|
status: { init_states: true },
|
|
3197
3125
|
}
|
|
3198
3126
|
);
|
|
3199
|
-
const pinOnDevice = jest.fn();
|
|
3200
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3201
3127
|
|
|
3202
3128
|
const features = await device.unlockDevice();
|
|
3203
3129
|
|
|
3204
|
-
expect(pinOnDevice).toHaveBeenCalledWith(
|
|
3205
|
-
device,
|
|
3206
|
-
DeviceSessionPinType.Main,
|
|
3207
|
-
expect.objectContaining({
|
|
3208
|
-
source: 'unlock-coordinator',
|
|
3209
|
-
reason: 'device-unlock',
|
|
3210
|
-
deviceOnly: true,
|
|
3211
|
-
})
|
|
3212
|
-
);
|
|
3213
|
-
expect(pinOnDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
3214
|
-
typedCall.mock.invocationCallOrder[0]
|
|
3215
|
-
);
|
|
3216
3130
|
expect(typedCall.mock.calls).toEqual([
|
|
3217
3131
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3218
3132
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3258,12 +3172,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
3258
3172
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
3259
3173
|
});
|
|
3260
3174
|
(device as any).commands = { typedCall };
|
|
3261
|
-
const pinOnDevice = jest.fn();
|
|
3262
|
-
device.on(DEVICE.PIN_ON_DEVICE, pinOnDevice);
|
|
3263
3175
|
|
|
3264
|
-
await device.unlockDevice(
|
|
3176
|
+
await device.unlockDevice();
|
|
3265
3177
|
|
|
3266
|
-
expect(pinOnDevice).not.toHaveBeenCalled();
|
|
3267
3178
|
expect(typedCall.mock.calls).toEqual([
|
|
3268
3179
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.Main }],
|
|
3269
3180
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
@@ -3806,7 +3717,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3806
3717
|
});
|
|
3807
3718
|
});
|
|
3808
3719
|
|
|
3809
|
-
test('
|
|
3720
|
+
test('delegates Protocol V2 mode transition to the phase executor', async () => {
|
|
3810
3721
|
const method = new FirmwareUpdateV4({
|
|
3811
3722
|
id: 1,
|
|
3812
3723
|
payload: {
|
|
@@ -3841,7 +3752,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3841
3752
|
|
|
3842
3753
|
await method.run();
|
|
3843
3754
|
|
|
3844
|
-
expect((method as any).enterProtocolV2BootloaderMode).
|
|
3755
|
+
expect((method as any).enterProtocolV2BootloaderMode).not.toHaveBeenCalled();
|
|
3845
3756
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3846
3757
|
fwBinaryMap: [
|
|
3847
3758
|
{
|
|
@@ -4224,9 +4135,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4224
4135
|
const typedCall = jest
|
|
4225
4136
|
.fn()
|
|
4226
4137
|
.mockRejectedValue(
|
|
4227
|
-
|
|
4228
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
4229
|
-
'NotFoundError'
|
|
4138
|
+
Object.assign(
|
|
4139
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
4140
|
+
{ name: 'NotFoundError' }
|
|
4230
4141
|
)
|
|
4231
4142
|
);
|
|
4232
4143
|
|
|
@@ -4407,6 +4318,77 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4407
4318
|
expect((method as any).probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
|
|
4408
4319
|
});
|
|
4409
4320
|
|
|
4321
|
+
test('rejects a finished target whose payload version conflicts with the plan', () => {
|
|
4322
|
+
const method = new FirmwareUpdateV4({
|
|
4323
|
+
id: 1,
|
|
4324
|
+
payload: {
|
|
4325
|
+
method: 'firmwareUpdateV4',
|
|
4326
|
+
platform: 'desktop',
|
|
4327
|
+
expectedTargetVersions: {
|
|
4328
|
+
app_v1: '2.0.0',
|
|
4329
|
+
},
|
|
4330
|
+
},
|
|
4331
|
+
});
|
|
4332
|
+
method.init();
|
|
4333
|
+
method.postProgressMessage = jest.fn();
|
|
4334
|
+
|
|
4335
|
+
expect(() =>
|
|
4336
|
+
(method as any).assertProtocolV2TargetStatus(
|
|
4337
|
+
[{ target_id: 4, status: 2, payload_version: 0x010000 }],
|
|
4338
|
+
new Set([4])
|
|
4339
|
+
)
|
|
4340
|
+
).toThrow('expected 131072');
|
|
4341
|
+
});
|
|
4342
|
+
|
|
4343
|
+
test('does not fail a completed multi-app install when final app versions are unobservable', () => {
|
|
4344
|
+
const method = new FirmwareUpdateV4({
|
|
4345
|
+
id: 1,
|
|
4346
|
+
payload: {
|
|
4347
|
+
method: 'firmwareUpdateV4',
|
|
4348
|
+
platform: 'desktop',
|
|
4349
|
+
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4350
|
+
expectedTargetVersions: {
|
|
4351
|
+
app_v1: '1.0.0',
|
|
4352
|
+
app_v2: '2.0.0',
|
|
4353
|
+
},
|
|
4354
|
+
},
|
|
4355
|
+
});
|
|
4356
|
+
method.init();
|
|
4357
|
+
(method as any).protocolV2LatestFinalFeatures = {
|
|
4358
|
+
major_version: 3,
|
|
4359
|
+
minor_version: 0,
|
|
4360
|
+
patch_version: 0,
|
|
4361
|
+
};
|
|
4362
|
+
(method as any).protocolV2FinalStatusVerified = true;
|
|
4363
|
+
|
|
4364
|
+
expect(() => (method as any).assertExpectedProtocolV2Versions()).not.toThrow();
|
|
4365
|
+
});
|
|
4366
|
+
|
|
4367
|
+
test('rejects unobservable final versions after the status endpoint fallback', () => {
|
|
4368
|
+
const method = new FirmwareUpdateV4({
|
|
4369
|
+
id: 1,
|
|
4370
|
+
payload: {
|
|
4371
|
+
method: 'firmwareUpdateV4',
|
|
4372
|
+
platform: 'desktop',
|
|
4373
|
+
targetsToUpdate: ['app_v1', 'app_v2'],
|
|
4374
|
+
expectedTargetVersions: {
|
|
4375
|
+
app_v1: '1.0.0',
|
|
4376
|
+
app_v2: '2.0.0',
|
|
4377
|
+
},
|
|
4378
|
+
},
|
|
4379
|
+
});
|
|
4380
|
+
method.init();
|
|
4381
|
+
(method as any).protocolV2LatestFinalFeatures = {
|
|
4382
|
+
major_version: 3,
|
|
4383
|
+
minor_version: 0,
|
|
4384
|
+
patch_version: 0,
|
|
4385
|
+
};
|
|
4386
|
+
|
|
4387
|
+
expect(() => (method as any).assertExpectedProtocolV2Versions()).toThrow(
|
|
4388
|
+
'has no observable final version after status fallback'
|
|
4389
|
+
);
|
|
4390
|
+
});
|
|
4391
|
+
|
|
4410
4392
|
test('polls target status after update ACK and finishes when all targets complete', async () => {
|
|
4411
4393
|
const method = new FirmwareUpdateV4({
|
|
4412
4394
|
id: 1,
|
|
@@ -4483,7 +4465,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4483
4465
|
).toBe(true);
|
|
4484
4466
|
|
|
4485
4467
|
(method.postProgressMessage as jest.Mock).mockClear();
|
|
4486
|
-
expect(
|
|
4468
|
+
expect(() =>
|
|
4487
4469
|
(method as any).assertProtocolV2TargetStatus(
|
|
4488
4470
|
[
|
|
4489
4471
|
{ target_id: 4, status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
@@ -4491,8 +4473,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4491
4473
|
],
|
|
4492
4474
|
new Set([4, 10])
|
|
4493
4475
|
)
|
|
4494
|
-
).
|
|
4495
|
-
expect(method.postProgressMessage).
|
|
4476
|
+
).toThrow('Protocol V2 install status conflicts with target 4');
|
|
4477
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
4496
4478
|
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
4497
4479
|
|
|
4498
4480
|
expect(
|
|
@@ -4596,10 +4578,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4596
4578
|
const writtenPaths: string[] = [];
|
|
4597
4579
|
method.postTipMessage = jest.fn();
|
|
4598
4580
|
method.postProgressMessage = jest.fn();
|
|
4599
|
-
(method as any).
|
|
4581
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockImplementation(params => {
|
|
4600
4582
|
writtenPaths.push(params.filePath);
|
|
4601
|
-
return Number(params.processedSize ?? 0) + Number(params.
|
|
4583
|
+
return Number(params.processedSize ?? 0) + Number(params.source.size);
|
|
4602
4584
|
});
|
|
4585
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4586
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4587
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({});
|
|
4588
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4603
4589
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4604
4590
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4605
4591
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4639,10 +4625,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4639
4625
|
'vol0:/coprocessor.bin',
|
|
4640
4626
|
1
|
|
4641
4627
|
);
|
|
4642
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(
|
|
4643
|
-
expect((method as any).protocolV2StartFirmwareUpdate).
|
|
4628
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(2);
|
|
4629
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(1, {
|
|
4630
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
4631
|
+
});
|
|
4632
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenNthCalledWith(2, {
|
|
4644
4633
|
targets: [
|
|
4645
|
-
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
4646
4634
|
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
4647
4635
|
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
4648
4636
|
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
@@ -4652,7 +4640,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4652
4640
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
4653
4641
|
});
|
|
4654
4642
|
|
|
4655
|
-
test('announces
|
|
4643
|
+
test('announces separate resource and component transfer phases', async () => {
|
|
4656
4644
|
const method = new FirmwareUpdateV4({
|
|
4657
4645
|
id: 1,
|
|
4658
4646
|
payload: {
|
|
@@ -4662,11 +4650,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4662
4650
|
|
|
4663
4651
|
method.postTipMessage = jest.fn();
|
|
4664
4652
|
method.postProgressMessage = jest.fn();
|
|
4665
|
-
(method as any).
|
|
4653
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
4666
4654
|
.fn()
|
|
4667
4655
|
.mockImplementation(params =>
|
|
4668
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4656
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
4669
4657
|
);
|
|
4658
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4659
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4660
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4670
4661
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4671
4662
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4672
4663
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -4686,21 +4677,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4686
4677
|
{
|
|
4687
4678
|
fileName: 'application_p1.bin',
|
|
4688
4679
|
binary: new Uint8Array([3]).buffer,
|
|
4689
|
-
targetId:
|
|
4680
|
+
targetId: 4,
|
|
4690
4681
|
},
|
|
4691
4682
|
],
|
|
4692
4683
|
});
|
|
4693
4684
|
|
|
4694
|
-
expect(method.postTipMessage).toHaveBeenCalledTimes(
|
|
4685
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(3);
|
|
4695
4686
|
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
4696
|
-
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, '
|
|
4697
|
-
expect(
|
|
4687
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'StartTransferData');
|
|
4688
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(3, 'ConfirmOnDevice');
|
|
4689
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
4698
4690
|
1,
|
|
4699
|
-
expect.objectContaining({ processedSize: 0, totalSize:
|
|
4691
|
+
expect.objectContaining({ processedSize: 0, totalSize: 2 })
|
|
4700
4692
|
);
|
|
4701
|
-
expect((method as any).
|
|
4693
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenNthCalledWith(
|
|
4702
4694
|
2,
|
|
4703
|
-
expect.objectContaining({ processedSize:
|
|
4695
|
+
expect.objectContaining({ processedSize: 0, totalSize: 1 })
|
|
4704
4696
|
);
|
|
4705
4697
|
});
|
|
4706
4698
|
|
|
@@ -4714,7 +4706,8 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4714
4706
|
|
|
4715
4707
|
method.postTipMessage = jest.fn();
|
|
4716
4708
|
method.postProgressMessage = jest.fn();
|
|
4717
|
-
(method as any).
|
|
4709
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
4710
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4718
4711
|
(method as any).device = stubDevice({
|
|
4719
4712
|
getCommands: () => ({
|
|
4720
4713
|
typedCall: jest.fn().mockResolvedValue({
|
|
@@ -4760,11 +4753,14 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
4760
4753
|
|
|
4761
4754
|
method.postTipMessage = jest.fn();
|
|
4762
4755
|
method.postProgressMessage = jest.fn();
|
|
4763
|
-
(method as any).
|
|
4756
|
+
(method as any).protocolV2SourceUpdateProcess = jest
|
|
4764
4757
|
.fn()
|
|
4765
4758
|
.mockImplementation(params =>
|
|
4766
|
-
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.
|
|
4759
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.source.size))
|
|
4767
4760
|
);
|
|
4761
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
4762
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
4763
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
4768
4764
|
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
4769
4765
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
4770
4766
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
@@ -5087,6 +5083,58 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5087
5083
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5088
5084
|
});
|
|
5089
5085
|
|
|
5086
|
+
test('rejects prepared RESC bundle paths before opening an artifact source', async () => {
|
|
5087
|
+
const method = new FirmwareUpdateV4({
|
|
5088
|
+
id: 1,
|
|
5089
|
+
payload: {
|
|
5090
|
+
method: 'firmwareUpdateV4',
|
|
5091
|
+
},
|
|
5092
|
+
});
|
|
5093
|
+
const openPreparedSource = jest.fn();
|
|
5094
|
+
(method as any).params = {
|
|
5095
|
+
targetsToUpdate: ['resource'],
|
|
5096
|
+
resourceBundleArtifacts: [
|
|
5097
|
+
{
|
|
5098
|
+
name: 'images',
|
|
5099
|
+
artifact: {
|
|
5100
|
+
artifactRef: `fw:${'a'.repeat(64)}`,
|
|
5101
|
+
size: 1,
|
|
5102
|
+
sha256: 'a'.repeat(64),
|
|
5103
|
+
},
|
|
5104
|
+
},
|
|
5105
|
+
],
|
|
5106
|
+
};
|
|
5107
|
+
(method as any).openProtocolV2PreparedSource = openPreparedSource;
|
|
5108
|
+
const getFirmwareLatestReleaseSpy = jest
|
|
5109
|
+
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
5110
|
+
.mockReturnValue({
|
|
5111
|
+
required: false,
|
|
5112
|
+
version: [1, 0, 0],
|
|
5113
|
+
url: '',
|
|
5114
|
+
resourceBundles: [
|
|
5115
|
+
{
|
|
5116
|
+
name: 'images',
|
|
5117
|
+
url: 'https://example.com/images.okpkg',
|
|
5118
|
+
devicePath: 'vol0:/resource/../images.okpkg',
|
|
5119
|
+
},
|
|
5120
|
+
],
|
|
5121
|
+
fingerprint: '',
|
|
5122
|
+
changelog: {
|
|
5123
|
+
'zh-CN': '',
|
|
5124
|
+
'en-US': '',
|
|
5125
|
+
},
|
|
5126
|
+
});
|
|
5127
|
+
|
|
5128
|
+
await expect(
|
|
5129
|
+
(method as any).prepareProtocolV2ResourceSources('universal', {
|
|
5130
|
+
deviceType: 'pro2',
|
|
5131
|
+
})
|
|
5132
|
+
).rejects.toThrow('resourceBundles[].devicePath');
|
|
5133
|
+
expect(openPreparedSource).not.toHaveBeenCalled();
|
|
5134
|
+
|
|
5135
|
+
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5136
|
+
});
|
|
5137
|
+
|
|
5090
5138
|
test('downloads and validates remote RESC bundles before entering bootloader', async () => {
|
|
5091
5139
|
const method = new FirmwareUpdateV4({
|
|
5092
5140
|
id: 1,
|
|
@@ -5139,7 +5187,9 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5139
5187
|
order.push('bootloader');
|
|
5140
5188
|
return Promise.resolve(true);
|
|
5141
5189
|
});
|
|
5142
|
-
(method as any
|
|
5190
|
+
const executeProtocolV2UpdateSpy = jest.spyOn(method as any, 'executeProtocolV2Update');
|
|
5191
|
+
(method as any).executeProtocolV2TransferPhase = jest.fn().mockResolvedValue(undefined);
|
|
5192
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue(undefined);
|
|
5143
5193
|
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
5144
5194
|
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
5145
5195
|
bootloaderVersion: '1.0.0',
|
|
@@ -5151,7 +5201,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5151
5201
|
await method.run();
|
|
5152
5202
|
|
|
5153
5203
|
expect(order).toEqual(['download', 'bootloader']);
|
|
5154
|
-
expect(
|
|
5204
|
+
expect(executeProtocolV2UpdateSpy).toHaveBeenCalledWith({
|
|
5155
5205
|
bootloaderBinary: null,
|
|
5156
5206
|
fwBinaryMap: [],
|
|
5157
5207
|
resourceBundles: [
|
|
@@ -5164,6 +5214,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5164
5214
|
],
|
|
5165
5215
|
});
|
|
5166
5216
|
|
|
5217
|
+
executeProtocolV2UpdateSpy.mockRestore();
|
|
5167
5218
|
getSysResourceBinarySpy.mockRestore();
|
|
5168
5219
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
5169
5220
|
});
|
|
@@ -5175,12 +5226,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5175
5226
|
method: 'firmwareUpdateV4',
|
|
5176
5227
|
},
|
|
5177
5228
|
});
|
|
5229
|
+
method.init();
|
|
5178
5230
|
const getSysResourceBinarySpy = jest
|
|
5179
5231
|
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
5180
5232
|
.mockResolvedValue({ binary: new Uint8Array([1, 2, 3]).buffer });
|
|
5181
5233
|
|
|
5182
5234
|
await expect(
|
|
5183
|
-
(method as any).
|
|
5235
|
+
(method as any).materializeProtocolV2ResourceBundles([
|
|
5184
5236
|
{
|
|
5185
5237
|
name: 'images',
|
|
5186
5238
|
binary: new ArrayBuffer(0),
|
|
@@ -5477,7 +5529,10 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5477
5529
|
|
|
5478
5530
|
method.postTipMessage = jest.fn();
|
|
5479
5531
|
method.postProgressMessage = jest.fn();
|
|
5480
|
-
(method as any).
|
|
5532
|
+
(method as any).protocolV2SourceUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
5533
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(undefined);
|
|
5534
|
+
(method as any).completeProtocolV2FinalVerification = jest.fn().mockResolvedValue({});
|
|
5535
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
5481
5536
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
5482
5537
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
5483
5538
|
|
|
@@ -5493,7 +5548,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5493
5548
|
fwBinaryMap: [],
|
|
5494
5549
|
});
|
|
5495
5550
|
|
|
5496
|
-
expect((method as any).
|
|
5551
|
+
expect((method as any).protocolV2SourceUpdateProcess).toHaveBeenCalledTimes(1);
|
|
5497
5552
|
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
5498
5553
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
5499
5554
|
});
|
|
@@ -5524,12 +5579,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5524
5579
|
});
|
|
5525
5580
|
method.postProgressMessage = jest.fn();
|
|
5526
5581
|
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
filePath: 'vol1:firmware.bin',
|
|
5530
|
-
processedSize: 0,
|
|
5531
|
-
totalSize: 4097,
|
|
5582
|
+
const source = await openFirmwareByteSource({
|
|
5583
|
+
binary: new Uint8Array(4097).buffer,
|
|
5532
5584
|
});
|
|
5585
|
+
try {
|
|
5586
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5587
|
+
source,
|
|
5588
|
+
filePath: 'vol1:firmware.bin',
|
|
5589
|
+
processedSize: 0,
|
|
5590
|
+
totalSize: 4097,
|
|
5591
|
+
});
|
|
5592
|
+
} finally {
|
|
5593
|
+
await source?.close();
|
|
5594
|
+
}
|
|
5533
5595
|
|
|
5534
5596
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5535
5597
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 4000]);
|
|
@@ -5586,12 +5648,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5586
5648
|
method.postProgressMessage = jest.fn();
|
|
5587
5649
|
|
|
5588
5650
|
try {
|
|
5589
|
-
|
|
5590
|
-
|
|
5651
|
+
const source = await openFirmwareByteSource({
|
|
5652
|
+
binary: new Uint8Array(8001).buffer,
|
|
5653
|
+
});
|
|
5654
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5655
|
+
source,
|
|
5591
5656
|
filePath: 'vol0:/firmware.bin',
|
|
5592
5657
|
processedSize: 0,
|
|
5593
5658
|
totalSize: 8001,
|
|
5594
5659
|
});
|
|
5660
|
+
await source?.close();
|
|
5595
5661
|
} finally {
|
|
5596
5662
|
setTimeoutSpy.mockRestore();
|
|
5597
5663
|
}
|
|
@@ -5626,16 +5692,21 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5626
5692
|
});
|
|
5627
5693
|
method.postProgressMessage = jest.fn();
|
|
5628
5694
|
method.postTipMessage = jest.fn();
|
|
5695
|
+
(method as any).recoverProtocolV2FileTransfer = jest.fn().mockResolvedValue(undefined);
|
|
5629
5696
|
|
|
5697
|
+
const source = await openFirmwareByteSource({
|
|
5698
|
+
binary: new Uint8Array(4097).buffer,
|
|
5699
|
+
});
|
|
5630
5700
|
await expect(
|
|
5631
|
-
(method as any).
|
|
5632
|
-
|
|
5701
|
+
(method as any).protocolV2SourceUpdateProcess({
|
|
5702
|
+
source,
|
|
5633
5703
|
filePath: 'vol0:/firmware.bin',
|
|
5634
5704
|
processedSize: 0,
|
|
5635
5705
|
totalSize: 4097,
|
|
5636
5706
|
})
|
|
5637
5707
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.EmmcFileWriteFirmwareError });
|
|
5638
|
-
|
|
5708
|
+
await source?.close();
|
|
5709
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
5639
5710
|
});
|
|
5640
5711
|
|
|
5641
5712
|
test('caps native BLE firmware upload chunks below the WebUSB limit', async () => {
|
|
@@ -5668,12 +5739,19 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5668
5739
|
});
|
|
5669
5740
|
method.postProgressMessage = jest.fn();
|
|
5670
5741
|
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
filePath: 'vol1:ble-firmware.bin',
|
|
5674
|
-
processedSize: 0,
|
|
5675
|
-
totalSize: 1801,
|
|
5742
|
+
const source = await openFirmwareByteSource({
|
|
5743
|
+
binary: new Uint8Array(1801).buffer,
|
|
5676
5744
|
});
|
|
5745
|
+
try {
|
|
5746
|
+
await (method as any).protocolV2SourceUpdateProcess({
|
|
5747
|
+
source,
|
|
5748
|
+
filePath: 'vol1:ble-firmware.bin',
|
|
5749
|
+
processedSize: 0,
|
|
5750
|
+
totalSize: 1801,
|
|
5751
|
+
});
|
|
5752
|
+
} finally {
|
|
5753
|
+
await source?.close();
|
|
5754
|
+
}
|
|
5677
5755
|
|
|
5678
5756
|
const writePayloads = typedCall.mock.calls.map(call => call[2]);
|
|
5679
5757
|
expect(writePayloads.map(payload => payload.file.offset)).toEqual([0, 1800]);
|
|
@@ -5790,9 +5868,9 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5790
5868
|
const typedCall = jest
|
|
5791
5869
|
.fn()
|
|
5792
5870
|
.mockRejectedValue(
|
|
5793
|
-
|
|
5794
|
-
"Failed to execute 'open' on 'USBDevice': The device was disconnected",
|
|
5795
|
-
'NotFoundError'
|
|
5871
|
+
Object.assign(
|
|
5872
|
+
new Error("Failed to execute 'open' on 'USBDevice': The device was disconnected"),
|
|
5873
|
+
{ name: 'NotFoundError' }
|
|
5796
5874
|
)
|
|
5797
5875
|
);
|
|
5798
5876
|
|
|
@@ -5957,6 +6035,21 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
5957
6035
|
});
|
|
5958
6036
|
|
|
5959
6037
|
describe('Protocol V2 firmware reconnect identity', () => {
|
|
6038
|
+
test('keeps the host plan identity as the V4 reconnect anchor', () => {
|
|
6039
|
+
const method = new FirmwareUpdateV4({
|
|
6040
|
+
id: 1,
|
|
6041
|
+
payload: {
|
|
6042
|
+
method: 'firmwareUpdateV4',
|
|
6043
|
+
platform: 'native',
|
|
6044
|
+
expectedDeviceId: 'persisted-device-id',
|
|
6045
|
+
},
|
|
6046
|
+
});
|
|
6047
|
+
|
|
6048
|
+
method.init();
|
|
6049
|
+
|
|
6050
|
+
expect((method as any).params.expectedDeviceId).toBe('persisted-device-id');
|
|
6051
|
+
});
|
|
6052
|
+
|
|
5960
6053
|
test('rejects a different physical serial before firmware transfer resumes', () => {
|
|
5961
6054
|
expect(() => assertProtocolV2ReconnectIdentity('expected-serial', 'other-serial')).toThrow(
|
|
5962
6055
|
'identity mismatch'
|
|
@@ -6193,230 +6286,311 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6193
6286
|
expect(method.unlockPolicy).toBe('none');
|
|
6194
6287
|
});
|
|
6195
6288
|
|
|
6196
|
-
test('
|
|
6289
|
+
test('unlocks and retries an opted-in Protocol V2 method once', async () => {
|
|
6197
6290
|
const calls: string[] = [];
|
|
6198
|
-
const features = { unlocked: true };
|
|
6199
6291
|
const method = {
|
|
6200
|
-
name: '
|
|
6201
|
-
unlockPolicy: '
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6292
|
+
name: 'testBusinessMethod',
|
|
6293
|
+
unlockPolicy: 'retry-on-locked',
|
|
6294
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6295
|
+
run: jest
|
|
6296
|
+
.fn()
|
|
6297
|
+
.mockImplementationOnce(() => {
|
|
6298
|
+
calls.push('run-1');
|
|
6299
|
+
return Promise.reject(deviceLockedError());
|
|
6300
|
+
})
|
|
6301
|
+
.mockImplementationOnce(() => {
|
|
6302
|
+
calls.push('run-2');
|
|
6303
|
+
return Promise.resolve({ message: 'ok' });
|
|
6304
|
+
}),
|
|
6208
6305
|
};
|
|
6209
6306
|
const device = {
|
|
6210
|
-
features,
|
|
6211
|
-
commands: {
|
|
6212
|
-
typedCall: jest.fn(() => {
|
|
6213
|
-
calls.push('status');
|
|
6214
|
-
return Promise.resolve({ message: { unlocked: true } });
|
|
6215
|
-
}),
|
|
6216
|
-
},
|
|
6217
6307
|
isProtocolV2: () => true,
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
features.unlocked = status.unlocked === true;
|
|
6222
|
-
return features;
|
|
6308
|
+
unlockDevice: jest.fn(() => {
|
|
6309
|
+
calls.push('unlock');
|
|
6310
|
+
return Promise.resolve();
|
|
6223
6311
|
}),
|
|
6224
|
-
unlockDevice: jest.fn(),
|
|
6225
6312
|
};
|
|
6226
6313
|
const uiCoordinator = {
|
|
6227
6314
|
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6228
|
-
enterUnlockInteraction: jest.fn(),
|
|
6315
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6316
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6229
6317
|
};
|
|
6230
6318
|
|
|
6231
6319
|
await expect(
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6320
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6321
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6322
|
+
expect(calls).toEqual([
|
|
6323
|
+
'method-prompt',
|
|
6324
|
+
'run-1',
|
|
6325
|
+
'unlock-prompt',
|
|
6326
|
+
'unlock',
|
|
6327
|
+
'method-prompt',
|
|
6328
|
+
'run-2',
|
|
6329
|
+
]);
|
|
6241
6330
|
});
|
|
6242
6331
|
|
|
6243
|
-
test('
|
|
6332
|
+
test('restores the expected hidden-wallet session before retrying after unlock', async () => {
|
|
6244
6333
|
const calls: string[] = [];
|
|
6245
|
-
const identityError = new Error('Unexpected device');
|
|
6246
6334
|
const method = {
|
|
6247
6335
|
name: 'evmSignMessage',
|
|
6248
|
-
|
|
6336
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6249
6337
|
useDevicePassphraseState: true,
|
|
6250
|
-
|
|
6338
|
+
unlockPolicy: 'retry-on-locked',
|
|
6339
|
+
run: jest
|
|
6340
|
+
.fn()
|
|
6341
|
+
.mockImplementationOnce(() => {
|
|
6342
|
+
calls.push('run-1');
|
|
6343
|
+
return Promise.reject(deviceLockedError());
|
|
6344
|
+
})
|
|
6345
|
+
.mockImplementationOnce(() => {
|
|
6346
|
+
calls.push('run-2');
|
|
6347
|
+
return Promise.resolve({ message: 'ok' });
|
|
6348
|
+
}),
|
|
6251
6349
|
};
|
|
6252
|
-
const
|
|
6350
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6351
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6352
|
+
calls.push('negotiate-session');
|
|
6353
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6354
|
+
}
|
|
6355
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6356
|
+
calls.push('resume-hidden-session');
|
|
6357
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6358
|
+
return Promise.resolve({
|
|
6359
|
+
message: {
|
|
6360
|
+
session_id: 'hidden-session',
|
|
6361
|
+
btc_test_address: 'hidden-state',
|
|
6362
|
+
},
|
|
6363
|
+
});
|
|
6364
|
+
}
|
|
6365
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6366
|
+
});
|
|
6253
6367
|
const device = {
|
|
6254
|
-
features,
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
calls.push('status');
|
|
6258
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6259
|
-
}),
|
|
6260
|
-
},
|
|
6368
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
6369
|
+
passphraseState: 'hidden-state',
|
|
6370
|
+
commands: { typedCall },
|
|
6261
6371
|
isProtocolV2: () => true,
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6372
|
+
unlockDevice: jest.fn(() => {
|
|
6373
|
+
calls.push('unlock');
|
|
6374
|
+
return Promise.resolve();
|
|
6375
|
+
}),
|
|
6376
|
+
getCurrentPassphraseProtection: () => true,
|
|
6377
|
+
getInternalState: () => 'hidden-session',
|
|
6378
|
+
clearInternalState: jest.fn(),
|
|
6379
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6380
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6266
6381
|
};
|
|
6267
6382
|
|
|
6268
|
-
await expect(
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6383
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6384
|
+
message: 'ok',
|
|
6385
|
+
});
|
|
6386
|
+
expect(calls).toEqual([
|
|
6387
|
+
'run-1',
|
|
6388
|
+
'unlock',
|
|
6389
|
+
'negotiate-session',
|
|
6390
|
+
'resume-hidden-session',
|
|
6391
|
+
'validate-hidden-session',
|
|
6392
|
+
'run-2',
|
|
6393
|
+
]);
|
|
6394
|
+
});
|
|
6276
6395
|
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6396
|
+
test('does not replay a business method when the hidden-wallet session cannot be restored after unlock', async () => {
|
|
6397
|
+
const initialError = deviceLockedError();
|
|
6398
|
+
const restoreError = new Error('Invalid wallet session');
|
|
6399
|
+
const method = {
|
|
6400
|
+
name: 'evmSignMessage',
|
|
6401
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6402
|
+
useDevicePassphraseState: true,
|
|
6403
|
+
unlockPolicy: 'retry-on-locked',
|
|
6404
|
+
run: jest.fn().mockRejectedValueOnce(initialError),
|
|
6405
|
+
};
|
|
6406
|
+
const typedCall = jest.fn((requestType: string) => {
|
|
6407
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6408
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6409
|
+
}
|
|
6410
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6411
|
+
return Promise.reject(restoreError);
|
|
6412
|
+
}
|
|
6413
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6414
|
+
});
|
|
6415
|
+
const device = {
|
|
6416
|
+
features: { unlocked: true, passphraseProtection: true },
|
|
6417
|
+
passphraseState: 'hidden-state',
|
|
6418
|
+
commands: { typedCall },
|
|
6419
|
+
isProtocolV2: () => true,
|
|
6420
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6421
|
+
getCurrentPassphraseProtection: () => true,
|
|
6422
|
+
getInternalState: () => 'hidden-session',
|
|
6423
|
+
clearInternalState: jest.fn(),
|
|
6424
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6425
|
+
updateInternalState: jest.fn(),
|
|
6426
|
+
};
|
|
6427
|
+
|
|
6428
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(restoreError);
|
|
6429
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6430
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6431
|
+
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
6432
|
+
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
6280
6433
|
});
|
|
6281
6434
|
|
|
6282
|
-
test('
|
|
6435
|
+
test('restores the expected hidden-wallet session after pre-unlock without selecting Attach PIN', async () => {
|
|
6283
6436
|
const calls: string[] = [];
|
|
6284
|
-
const features = { unlocked: true };
|
|
6285
6437
|
const method = {
|
|
6286
6438
|
name: 'evmSignMessage',
|
|
6287
|
-
|
|
6288
|
-
|
|
6439
|
+
payload: { passphraseState: 'hidden-state' },
|
|
6440
|
+
useDevicePassphraseState: true,
|
|
6441
|
+
unlockPolicy: 'retry-on-locked',
|
|
6289
6442
|
run: jest.fn(() => {
|
|
6290
6443
|
calls.push('run');
|
|
6291
|
-
return Promise.resolve({
|
|
6444
|
+
return Promise.resolve({ message: 'ok' });
|
|
6292
6445
|
}),
|
|
6293
6446
|
};
|
|
6447
|
+
const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
|
|
6448
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
6449
|
+
calls.push('negotiate-session');
|
|
6450
|
+
return Promise.resolve({ message: { version: 2 } });
|
|
6451
|
+
}
|
|
6452
|
+
if (requestType === 'DeviceSessionGet') {
|
|
6453
|
+
calls.push('resume-hidden-session');
|
|
6454
|
+
expect(request).toEqual({ session_id: 'hidden-session' });
|
|
6455
|
+
return Promise.resolve({
|
|
6456
|
+
message: {
|
|
6457
|
+
session_id: 'hidden-session',
|
|
6458
|
+
btc_test_address: 'hidden-state',
|
|
6459
|
+
},
|
|
6460
|
+
});
|
|
6461
|
+
}
|
|
6462
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
6463
|
+
});
|
|
6464
|
+
const features = {
|
|
6465
|
+
unlocked: false,
|
|
6466
|
+
unlockedAttachPin: true,
|
|
6467
|
+
passphraseProtection: true,
|
|
6468
|
+
};
|
|
6294
6469
|
const device = {
|
|
6295
6470
|
features,
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
calls.push('status');
|
|
6299
|
-
return Promise.resolve({ message: { unlocked: false } });
|
|
6300
|
-
}),
|
|
6301
|
-
},
|
|
6471
|
+
passphraseState: 'hidden-state',
|
|
6472
|
+
commands: { typedCall },
|
|
6302
6473
|
isProtocolV2: () => true,
|
|
6303
|
-
isBootloader: () => false,
|
|
6304
|
-
isRomloader: () => false,
|
|
6305
|
-
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6306
|
-
features.unlocked = status.unlocked === true;
|
|
6307
|
-
return features;
|
|
6308
|
-
}),
|
|
6309
6474
|
unlockDevice: jest.fn(() => {
|
|
6310
|
-
calls.push('unlock');
|
|
6475
|
+
calls.push('unlock-main');
|
|
6311
6476
|
features.unlocked = true;
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
};
|
|
6315
|
-
const uiCoordinator = {
|
|
6316
|
-
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6317
|
-
enterUnlockInteraction: jest.fn(() => {
|
|
6318
|
-
calls.push('unlock-prompt');
|
|
6319
|
-
return undefined;
|
|
6477
|
+
features.unlockedAttachPin = false;
|
|
6478
|
+
return Promise.resolve();
|
|
6320
6479
|
}),
|
|
6480
|
+
getCurrentPassphraseProtection: () => true,
|
|
6481
|
+
getInternalState: () => 'hidden-session',
|
|
6482
|
+
clearInternalState: jest.fn(),
|
|
6483
|
+
getCurrentDeviceId: () => 'wallet-device-id',
|
|
6484
|
+
updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
|
|
6321
6485
|
};
|
|
6322
6486
|
|
|
6323
|
-
await expect(
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
calls.push('wallet-session');
|
|
6328
|
-
return Promise.resolve();
|
|
6329
|
-
},
|
|
6330
|
-
})
|
|
6331
|
-
).resolves.toEqual({ signature: 'signed' });
|
|
6332
|
-
|
|
6487
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6488
|
+
message: 'ok',
|
|
6489
|
+
});
|
|
6490
|
+
expect(device.unlockDevice).toHaveBeenCalledWith();
|
|
6333
6491
|
expect(calls).toEqual([
|
|
6334
|
-
'
|
|
6335
|
-
'
|
|
6336
|
-
'
|
|
6337
|
-
'
|
|
6338
|
-
'method-prompt',
|
|
6492
|
+
'unlock-main',
|
|
6493
|
+
'negotiate-session',
|
|
6494
|
+
'resume-hidden-session',
|
|
6495
|
+
'validate-hidden-session',
|
|
6339
6496
|
'run',
|
|
6340
6497
|
]);
|
|
6341
|
-
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6342
|
-
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6343
6498
|
});
|
|
6344
6499
|
|
|
6345
|
-
test('
|
|
6500
|
+
test('does not restore a hidden-wallet session for a standard-wallet pre-unlock', async () => {
|
|
6346
6501
|
const method = {
|
|
6347
6502
|
name: 'evmGetAddress',
|
|
6348
|
-
|
|
6349
|
-
|
|
6503
|
+
payload: { useEmptyPassphrase: true },
|
|
6504
|
+
useDevicePassphraseState: true,
|
|
6505
|
+
unlockPolicy: 'retry-on-locked',
|
|
6506
|
+
run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
|
|
6350
6507
|
};
|
|
6351
6508
|
const device = {
|
|
6352
|
-
features: { unlocked:
|
|
6353
|
-
|
|
6509
|
+
features: { unlocked: false },
|
|
6510
|
+
passphraseState: 'stale-hidden-state',
|
|
6354
6511
|
isProtocolV2: () => true,
|
|
6355
|
-
|
|
6356
|
-
isRomloader: () => false,
|
|
6357
|
-
updateProtocolV2Status: jest.fn(() => ({ unlocked: undefined })),
|
|
6358
|
-
unlockDevice: jest.fn(),
|
|
6512
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6359
6513
|
};
|
|
6360
6514
|
|
|
6361
|
-
await expect(
|
|
6362
|
-
|
|
6515
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6516
|
+
address: 'standard-wallet-address',
|
|
6363
6517
|
});
|
|
6364
|
-
expect(device.unlockDevice).
|
|
6365
|
-
expect(method.run).
|
|
6518
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6519
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6366
6520
|
});
|
|
6367
6521
|
|
|
6368
|
-
test('
|
|
6369
|
-
const
|
|
6522
|
+
test('refreshes status and unlocks before a protected Protocol V2 method', async () => {
|
|
6523
|
+
const calls: string[] = [];
|
|
6370
6524
|
const method = {
|
|
6371
|
-
name: '
|
|
6525
|
+
name: 'deviceSettingsPageShow',
|
|
6372
6526
|
unlockPolicy: 'unlock-before-run',
|
|
6373
|
-
|
|
6527
|
+
protocolV2UiInteraction: { reason: 'settings-page' },
|
|
6528
|
+
run: jest.fn(() => {
|
|
6529
|
+
calls.push('run');
|
|
6530
|
+
return Promise.resolve({ message: 'ok' });
|
|
6531
|
+
}),
|
|
6374
6532
|
};
|
|
6375
6533
|
const features = { unlocked: true };
|
|
6376
6534
|
const device = {
|
|
6377
6535
|
features,
|
|
6378
|
-
commands: {
|
|
6536
|
+
commands: {
|
|
6537
|
+
typedCall: jest.fn(() => {
|
|
6538
|
+
calls.push('refresh-status');
|
|
6539
|
+
return Promise.resolve({ message: { unlocked: false } });
|
|
6540
|
+
}),
|
|
6541
|
+
},
|
|
6379
6542
|
isProtocolV2: () => true,
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6543
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6544
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6545
|
+
return features;
|
|
6546
|
+
}),
|
|
6547
|
+
unlockDevice: jest.fn(() => {
|
|
6548
|
+
calls.push('unlock');
|
|
6549
|
+
features.unlocked = true;
|
|
6550
|
+
return Promise.resolve();
|
|
6551
|
+
}),
|
|
6552
|
+
};
|
|
6553
|
+
const uiCoordinator = {
|
|
6554
|
+
enterMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6555
|
+
enterUnlockInteraction: jest.fn(() => calls.push('unlock-prompt')),
|
|
6556
|
+
resumeMethodInteraction: jest.fn(() => calls.push('method-prompt')),
|
|
6384
6557
|
};
|
|
6385
6558
|
|
|
6386
|
-
await expect(
|
|
6387
|
-
|
|
6559
|
+
await expect(
|
|
6560
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6561
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6562
|
+
expect(calls).toEqual(['refresh-status', 'unlock-prompt', 'unlock', 'method-prompt', 'run']);
|
|
6563
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
6388
6564
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6565
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6566
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6389
6567
|
});
|
|
6390
6568
|
|
|
6391
|
-
test('
|
|
6392
|
-
const features = { unlocked:
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
name: 'allNetworkGetAddress',
|
|
6396
|
-
unlockPolicy: 'unlock-before-run',
|
|
6397
|
-
run: jest.fn().mockResolvedValue({ root: true }),
|
|
6398
|
-
};
|
|
6399
|
-
const nestedMethod = {
|
|
6400
|
-
name: 'evmGetAddress',
|
|
6569
|
+
test('uses refreshed unlocked status instead of a stale locked cache', async () => {
|
|
6570
|
+
const features = { unlocked: false };
|
|
6571
|
+
const method = {
|
|
6572
|
+
name: 'deviceSettings',
|
|
6401
6573
|
unlockPolicy: 'unlock-before-run',
|
|
6402
|
-
run: jest.fn().mockResolvedValue({
|
|
6574
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6403
6575
|
};
|
|
6404
6576
|
const device = {
|
|
6405
6577
|
features,
|
|
6406
|
-
commands: {
|
|
6578
|
+
commands: {
|
|
6579
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
6580
|
+
},
|
|
6407
6581
|
isProtocolV2: () => true,
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6582
|
+
updateProtocolV2Status: jest.fn((status: { unlocked?: boolean }) => {
|
|
6583
|
+
features.unlocked = status.unlocked ?? features.unlocked;
|
|
6584
|
+
return features;
|
|
6585
|
+
}),
|
|
6411
6586
|
unlockDevice: jest.fn(),
|
|
6412
6587
|
};
|
|
6413
6588
|
|
|
6414
|
-
await
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
expect(device.
|
|
6418
|
-
expect(
|
|
6419
|
-
expect(nestedMethod.run).toHaveBeenCalledTimes(1);
|
|
6589
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6590
|
+
message: 'ok',
|
|
6591
|
+
});
|
|
6592
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6593
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6420
6594
|
});
|
|
6421
6595
|
|
|
6422
6596
|
test.each(['bootloader', 'romloader'] as const)(
|
|
@@ -6436,7 +6610,7 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6436
6610
|
unlockDevice: jest.fn(),
|
|
6437
6611
|
};
|
|
6438
6612
|
|
|
6439
|
-
await expect(
|
|
6613
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
6440
6614
|
message: 'updating',
|
|
6441
6615
|
});
|
|
6442
6616
|
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
@@ -6445,30 +6619,157 @@ describe('Protocol V2 protected method execution', () => {
|
|
|
6445
6619
|
}
|
|
6446
6620
|
);
|
|
6447
6621
|
|
|
6448
|
-
test('
|
|
6622
|
+
test('infers a signing interaction before running a Protocol V2 business method', async () => {
|
|
6623
|
+
const method = {
|
|
6624
|
+
name: 'evmSignMessage',
|
|
6625
|
+
params: { message: 'not-exposed-in-event' },
|
|
6626
|
+
payload: {},
|
|
6627
|
+
unlockPolicy: 'retry-on-locked',
|
|
6628
|
+
run: jest.fn().mockResolvedValue({ signature: 'test' }),
|
|
6629
|
+
};
|
|
6630
|
+
const device = {
|
|
6631
|
+
isProtocolV2: () => true,
|
|
6632
|
+
unlockDevice: jest.fn(),
|
|
6633
|
+
};
|
|
6634
|
+
const uiCoordinator = {
|
|
6635
|
+
enterMethodInteraction: jest.fn(),
|
|
6636
|
+
enterUnlockInteraction: jest.fn(),
|
|
6637
|
+
resumeMethodInteraction: jest.fn(),
|
|
6638
|
+
};
|
|
6639
|
+
|
|
6640
|
+
await runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any);
|
|
6641
|
+
|
|
6642
|
+
expect(uiCoordinator.enterMethodInteraction).toHaveBeenCalledWith({
|
|
6643
|
+
request: 'button',
|
|
6644
|
+
source: 'method-lifecycle',
|
|
6645
|
+
reason: 'signing-confirmation',
|
|
6646
|
+
completion: 'operation-completed',
|
|
6647
|
+
deviceOnly: true,
|
|
6648
|
+
operation: 'evmSignMessage',
|
|
6649
|
+
});
|
|
6650
|
+
});
|
|
6651
|
+
|
|
6652
|
+
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
6449
6653
|
for (const [isProtocolV2, unlockPolicy] of [
|
|
6450
|
-
[false, '
|
|
6654
|
+
[false, 'retry-on-locked'],
|
|
6451
6655
|
[true, 'none'],
|
|
6452
6656
|
] as const) {
|
|
6657
|
+
const error = deviceLockedError();
|
|
6453
6658
|
const method = {
|
|
6454
6659
|
unlockPolicy,
|
|
6455
|
-
|
|
6456
|
-
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6660
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6457
6661
|
};
|
|
6458
6662
|
const device = {
|
|
6459
|
-
commands: { typedCall: jest.fn() },
|
|
6460
6663
|
isProtocolV2: () => isProtocolV2,
|
|
6461
6664
|
unlockDevice: jest.fn(),
|
|
6462
6665
|
};
|
|
6463
6666
|
|
|
6464
|
-
await expect(
|
|
6465
|
-
message: 'ok',
|
|
6466
|
-
});
|
|
6467
|
-
expect(device.commands.typedCall).not.toHaveBeenCalled();
|
|
6667
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6468
6668
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6469
6669
|
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6470
6670
|
}
|
|
6471
6671
|
});
|
|
6672
|
+
|
|
6673
|
+
test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
|
|
6674
|
+
const method = {
|
|
6675
|
+
name: 'uploadPortfolio',
|
|
6676
|
+
unlockPolicy: 'none',
|
|
6677
|
+
protocolV2UiMode: 'none',
|
|
6678
|
+
run: jest.fn().mockResolvedValue({ message: 'ok' }),
|
|
6679
|
+
};
|
|
6680
|
+
const device = {
|
|
6681
|
+
features: { unlocked: false },
|
|
6682
|
+
isProtocolV2: () => true,
|
|
6683
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6684
|
+
};
|
|
6685
|
+
const uiCoordinator = {
|
|
6686
|
+
enterMethodInteraction: jest.fn(),
|
|
6687
|
+
enterUnlockInteraction: jest.fn(),
|
|
6688
|
+
resumeMethodInteraction: jest.fn(),
|
|
6689
|
+
};
|
|
6690
|
+
|
|
6691
|
+
await expect(
|
|
6692
|
+
runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
|
|
6693
|
+
).resolves.toEqual({ message: 'ok' });
|
|
6694
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6695
|
+
expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
|
|
6696
|
+
expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
|
|
6697
|
+
expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6698
|
+
});
|
|
6699
|
+
|
|
6700
|
+
test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
|
|
6701
|
+
const error = deviceLockedError();
|
|
6702
|
+
const method = {
|
|
6703
|
+
name: 'uploadPortfolio',
|
|
6704
|
+
unlockPolicy: 'none',
|
|
6705
|
+
run: jest.fn().mockRejectedValue(error),
|
|
6706
|
+
};
|
|
6707
|
+
const device = {
|
|
6708
|
+
features: { unlocked: true },
|
|
6709
|
+
isProtocolV2: () => true,
|
|
6710
|
+
unlockDevice: jest.fn(),
|
|
6711
|
+
};
|
|
6712
|
+
|
|
6713
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
6714
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
6715
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
6716
|
+
});
|
|
6717
|
+
|
|
6718
|
+
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
6719
|
+
const initialError = deviceLockedError();
|
|
6720
|
+
const unlockError = new Error('PIN cancelled');
|
|
6721
|
+
const unlockFailMethod = {
|
|
6722
|
+
unlockPolicy: 'retry-on-locked',
|
|
6723
|
+
run: jest.fn().mockRejectedValue(initialError),
|
|
6724
|
+
};
|
|
6725
|
+
const unlockFailDevice = {
|
|
6726
|
+
isProtocolV2: () => true,
|
|
6727
|
+
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
6728
|
+
};
|
|
6729
|
+
const unlockFailCoordinator = {
|
|
6730
|
+
enterMethodInteraction: jest.fn(),
|
|
6731
|
+
enterUnlockInteraction: jest.fn(),
|
|
6732
|
+
resumeMethodInteraction: jest.fn(),
|
|
6733
|
+
};
|
|
6734
|
+
|
|
6735
|
+
await expect(
|
|
6736
|
+
runMethodWithUnlockRetry(
|
|
6737
|
+
unlockFailMethod as any,
|
|
6738
|
+
unlockFailDevice as any,
|
|
6739
|
+
unlockFailCoordinator as any
|
|
6740
|
+
)
|
|
6741
|
+
).rejects.toBe(unlockError);
|
|
6742
|
+
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
6743
|
+
expect(unlockFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6744
|
+
expect(unlockFailCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
|
|
6745
|
+
|
|
6746
|
+
const retryError = deviceLockedError();
|
|
6747
|
+
const retryFailMethod = {
|
|
6748
|
+
unlockPolicy: 'retry-on-locked',
|
|
6749
|
+
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
6750
|
+
};
|
|
6751
|
+
const retryFailDevice = {
|
|
6752
|
+
isProtocolV2: () => true,
|
|
6753
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
6754
|
+
};
|
|
6755
|
+
const retryFailCoordinator = {
|
|
6756
|
+
enterMethodInteraction: jest.fn(),
|
|
6757
|
+
enterUnlockInteraction: jest.fn(),
|
|
6758
|
+
resumeMethodInteraction: jest.fn(),
|
|
6759
|
+
};
|
|
6760
|
+
|
|
6761
|
+
await expect(
|
|
6762
|
+
runMethodWithUnlockRetry(
|
|
6763
|
+
retryFailMethod as any,
|
|
6764
|
+
retryFailDevice as any,
|
|
6765
|
+
retryFailCoordinator as any
|
|
6766
|
+
)
|
|
6767
|
+
).rejects.toBe(retryError);
|
|
6768
|
+
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
6769
|
+
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
6770
|
+
expect(retryFailCoordinator.enterUnlockInteraction).toHaveBeenCalledTimes(1);
|
|
6771
|
+
expect(retryFailCoordinator.resumeMethodInteraction).toHaveBeenCalledTimes(1);
|
|
6772
|
+
});
|
|
6472
6773
|
});
|
|
6473
6774
|
|
|
6474
6775
|
describe('Protocol V2 current low-level methods', () => {
|
|
@@ -6600,9 +6901,7 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
6600
6901
|
|
|
6601
6902
|
await method.run();
|
|
6602
6903
|
|
|
6603
|
-
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
6604
|
-
eventless_wallet_session: true,
|
|
6605
|
-
});
|
|
6904
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {});
|
|
6606
6905
|
});
|
|
6607
6906
|
|
|
6608
6907
|
test('sends DeviceFactoryInfoSet and DeviceFactoryInfoGet', async () => {
|