@onekeyfe/hd-core 1.2.0-alpha.36 → 1.2.0-alpha.37
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 +5 -1
- package/__tests__/deviceUploadNft.test.ts +187 -0
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +161 -44
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
- package/__tests__/protocol-v2.test.ts +168 -444
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.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/helpers/protocolV2FileWrite.d.ts +2 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +34 -2
- package/dist/index.js +500 -462
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- 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/pro2Nft.d.ts +29 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +7 -9
- package/src/api/OpenWalletSession.ts +37 -15
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +29 -30
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +1 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +166 -0
- package/src/core/index.ts +76 -81
- package/src/data/messages/messages-protocol-v2.json +10 -15
- package/src/events/logBlockEvent.ts +1 -0
- package/src/inject.ts +1 -0
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
- package/src/protocols/protocol-v2/walletSession.ts +18 -51
- package/src/types/api/index.ts +2 -0
- package/src/types/api/protocolV2.ts +13 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +91 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -131
|
@@ -33,6 +33,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
33
33
|
init: jest.fn(),
|
|
34
34
|
name: 'evmGetAddress',
|
|
35
35
|
responseID: 43,
|
|
36
|
+
unlockPolicy: 'unlock-before-run',
|
|
36
37
|
run: jest.fn().mockImplementation(() => {
|
|
37
38
|
calls.push('run-chain-method');
|
|
38
39
|
return Promise.resolve([{ address: '0xhidden' }]);
|
|
@@ -51,10 +52,12 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
51
52
|
bundle: [],
|
|
52
53
|
},
|
|
53
54
|
});
|
|
55
|
+
method.protocolV2UnlockContext = { preflightCompleted: true };
|
|
56
|
+
const typedCall = jest.fn();
|
|
54
57
|
method.device = {
|
|
55
58
|
checkPassphraseStateSafety,
|
|
56
59
|
commands: {
|
|
57
|
-
typedCall
|
|
60
|
+
typedCall,
|
|
58
61
|
},
|
|
59
62
|
getCurrentFirmwareType: jest.fn(),
|
|
60
63
|
getProtocol: jest.fn().mockReturnValue('V2'),
|
|
@@ -89,6 +92,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
89
92
|
|
|
90
93
|
expect(checkPassphraseStateSafety).toHaveBeenCalledWith('hidden-state', false, undefined);
|
|
91
94
|
expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
|
|
95
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
test('releases the nested request context when an unhandled error escapes', async () => {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import DeviceUploadNft from '../src/api/protocol-v2/DeviceUploadNft';
|
|
2
|
+
|
|
3
|
+
jest.mock('../src/data/config', () => ({
|
|
4
|
+
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
5
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
const createRgba = (width: number, height: number) => {
|
|
9
|
+
const data = new Uint8Array(width * height * 4);
|
|
10
|
+
for (let index = 3; index < data.length; index += 4) data[index] = 0xff;
|
|
11
|
+
return data;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const createMethod = ({
|
|
15
|
+
typedCall,
|
|
16
|
+
supportedMessages = [60805, 61500],
|
|
17
|
+
useFullBundle = false,
|
|
18
|
+
}: {
|
|
19
|
+
typedCall: jest.Mock;
|
|
20
|
+
supportedMessages?: number[];
|
|
21
|
+
useFullBundle?: boolean;
|
|
22
|
+
}) => {
|
|
23
|
+
const method = new DeviceUploadNft({
|
|
24
|
+
id: 1,
|
|
25
|
+
payload: {
|
|
26
|
+
method: 'deviceUploadNft',
|
|
27
|
+
image: { width: 540, height: 540, rgba: createRgba(540, 540) },
|
|
28
|
+
thumbnail: { width: 263, height: 263, rgba: createRgba(263, 263) },
|
|
29
|
+
title: 'CryptoPunk #3100',
|
|
30
|
+
subtitle: 'CryptoPunks',
|
|
31
|
+
timestampMs: 1_760_000_000_000,
|
|
32
|
+
chunkSize: 2048,
|
|
33
|
+
paceMs: 0,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
(method as any).device = {
|
|
37
|
+
commands: { typedCall },
|
|
38
|
+
ensureProtocolV2RuntimeContext: jest.fn(() =>
|
|
39
|
+
Promise.resolve({
|
|
40
|
+
version: 2,
|
|
41
|
+
build_fingerprint: 'application__1.0.0__test__DEV__DEBUG',
|
|
42
|
+
supported_messages: supportedMessages,
|
|
43
|
+
})
|
|
44
|
+
),
|
|
45
|
+
getCurrentFirmwareType: jest.fn(),
|
|
46
|
+
};
|
|
47
|
+
method.postMessage = jest.fn();
|
|
48
|
+
|
|
49
|
+
if (useFullBundle) {
|
|
50
|
+
method.init();
|
|
51
|
+
} else {
|
|
52
|
+
(method as any).params = { chunkSize: 2048, paceMs: 0, timeoutMs: 15_000 };
|
|
53
|
+
(method as any).bundle = {
|
|
54
|
+
basename: 'nft-deadbeef-1760000000000',
|
|
55
|
+
image: new Uint8Array([1]),
|
|
56
|
+
thumbnail: new Uint8Array([2]),
|
|
57
|
+
metadata: new TextEncoder().encode('{"title":"NFT","subtitle":""}'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return method;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const fileWriteSuccess = (params: { file: { offset: number; data: Uint8Array } }) => ({
|
|
65
|
+
message: {
|
|
66
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('DeviceUploadNft', () => {
|
|
71
|
+
test('uploads the triplet in order without creating the firmware-owned directory', async () => {
|
|
72
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
73
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
74
|
+
if (request === 'NftUpdate') return { message: { message: 'NFT updated' } };
|
|
75
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
76
|
+
});
|
|
77
|
+
const method = createMethod({ typedCall, useFullBundle: true });
|
|
78
|
+
|
|
79
|
+
const result = await method.run();
|
|
80
|
+
|
|
81
|
+
const requests = typedCall.mock.calls.map(call => call[0]);
|
|
82
|
+
expect(requests[0]).toBe('FilesystemFileWrite');
|
|
83
|
+
expect(requests).not.toContain('FilesystemDirMake');
|
|
84
|
+
expect(requests.at(-1)).toBe('NftUpdate');
|
|
85
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
86
|
+
const paths = fileWrites.map(call => call[2].file.path as string);
|
|
87
|
+
const imagePath = paths[0];
|
|
88
|
+
const thumbnailStart = paths.findIndex(path => path.endsWith('_m.bin'));
|
|
89
|
+
const metadataStart = paths.findIndex(path => path.endsWith('.json'));
|
|
90
|
+
expect(imagePath).toMatch(/^vol1:\/nft\/nft-[a-f0-9]{8}-1760000000000\.bin$/);
|
|
91
|
+
expect(thumbnailStart).toBeGreaterThan(0);
|
|
92
|
+
expect(metadataStart).toBeGreaterThan(thumbnailStart);
|
|
93
|
+
expect(new Set(paths.slice(0, thumbnailStart))).toEqual(new Set([imagePath]));
|
|
94
|
+
expect(new Set(paths.slice(thumbnailStart, metadataStart))).toEqual(
|
|
95
|
+
new Set([result.thumbnailPath])
|
|
96
|
+
);
|
|
97
|
+
expect(typedCall).toHaveBeenLastCalledWith(
|
|
98
|
+
'NftUpdate',
|
|
99
|
+
'Success',
|
|
100
|
+
{ file_name_no_ext: result.basename },
|
|
101
|
+
{ timeoutMs: 15_000 }
|
|
102
|
+
);
|
|
103
|
+
expect(result).toMatchObject({
|
|
104
|
+
nftUpdated: true,
|
|
105
|
+
message: 'NFT updated',
|
|
106
|
+
totalSize: 583_212 + 138_876 + 53,
|
|
107
|
+
});
|
|
108
|
+
expect(method.postMessage).toHaveBeenLastCalledWith({
|
|
109
|
+
event: 'UI_EVENT',
|
|
110
|
+
type: 'ui-device_progress',
|
|
111
|
+
payload: expect.objectContaining({
|
|
112
|
+
progress: 100,
|
|
113
|
+
transferredBytes: result.totalSize,
|
|
114
|
+
totalBytes: result.totalSize,
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('retries one timed-out NftUpdate with the same basename', async () => {
|
|
120
|
+
const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
|
|
121
|
+
code: 'response-timeout',
|
|
122
|
+
});
|
|
123
|
+
let updateCalls = 0;
|
|
124
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
125
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
126
|
+
if (request === 'NftUpdate') {
|
|
127
|
+
updateCalls += 1;
|
|
128
|
+
if (updateCalls === 1) throw timeout;
|
|
129
|
+
return { message: { message: 'NFT updated' } };
|
|
130
|
+
}
|
|
131
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
132
|
+
});
|
|
133
|
+
const method = createMethod({ typedCall });
|
|
134
|
+
|
|
135
|
+
await expect(method.run()).resolves.toMatchObject({ nftUpdated: true });
|
|
136
|
+
|
|
137
|
+
const updateRequests = typedCall.mock.calls.filter(call => call[0] === 'NftUpdate');
|
|
138
|
+
expect(updateRequests).toHaveLength(2);
|
|
139
|
+
expect(updateRequests[1]).toEqual(updateRequests[0]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('does not retry a non-timeout NftUpdate failure', async () => {
|
|
143
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
144
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
145
|
+
if (request === 'NftUpdate') throw new Error('Invalid NFT metadata');
|
|
146
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
147
|
+
});
|
|
148
|
+
const method = createMethod({ typedCall });
|
|
149
|
+
|
|
150
|
+
await expect(method.run()).rejects.toThrow('Invalid NFT metadata');
|
|
151
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('propagates a second NftUpdate timeout after one retry', async () => {
|
|
155
|
+
const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
|
|
156
|
+
code: 'response-timeout',
|
|
157
|
+
});
|
|
158
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
159
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
160
|
+
if (request === 'NftUpdate') throw timeout;
|
|
161
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
162
|
+
});
|
|
163
|
+
const method = createMethod({ typedCall });
|
|
164
|
+
|
|
165
|
+
await expect(method.run()).rejects.toBe(timeout);
|
|
166
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(2);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('does not publish when a file write fails', async () => {
|
|
170
|
+
const typedCall = jest.fn((request: string) => {
|
|
171
|
+
if (request === 'FilesystemFileWrite') throw new Error('Filesystem full');
|
|
172
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
173
|
+
});
|
|
174
|
+
const method = createMethod({ typedCall });
|
|
175
|
+
|
|
176
|
+
await expect(method.run()).rejects.toThrow('Filesystem full');
|
|
177
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'NftUpdate')).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('rejects unsupported firmware before writing files', async () => {
|
|
181
|
+
const typedCall = jest.fn();
|
|
182
|
+
const method = createMethod({ typedCall, supportedMessages: [60805] });
|
|
183
|
+
|
|
184
|
+
await expect(method.run()).rejects.toBeDefined();
|
|
185
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
|
-
import { getHomeScreenSize } from '../src/utils/homescreen';
|
|
3
|
+
import { getHomeScreenSize, getNftSize } from '../src/utils/homescreen';
|
|
4
4
|
|
|
5
5
|
describe('getHomeScreenSize', () => {
|
|
6
6
|
it('returns the Pro 2 wallpaper dimensions', () => {
|
|
@@ -21,4 +21,44 @@ describe('getHomeScreenSize', () => {
|
|
|
21
21
|
})
|
|
22
22
|
).toBeUndefined();
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
it('does not reuse the wallpaper homeScreenType for Pro 2 NFT dimensions', () => {
|
|
26
|
+
expect(
|
|
27
|
+
getHomeScreenSize({
|
|
28
|
+
deviceType: EDeviceType.Pro2,
|
|
29
|
+
homeScreenType: 'Nft',
|
|
30
|
+
thumbnail: false,
|
|
31
|
+
})
|
|
32
|
+
).toEqual({ width: 604, height: 1024 });
|
|
33
|
+
expect(
|
|
34
|
+
getHomeScreenSize({
|
|
35
|
+
deviceType: EDeviceType.Pro2,
|
|
36
|
+
homeScreenType: 'Nft',
|
|
37
|
+
thumbnail: true,
|
|
38
|
+
})
|
|
39
|
+
).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getNftSize', () => {
|
|
44
|
+
it('returns the Pro 2 NFT image and thumbnail dimensions independently', () => {
|
|
45
|
+
expect(getNftSize({ deviceType: EDeviceType.Pro2 })).toEqual({ width: 540, height: 540 });
|
|
46
|
+
expect(getNftSize({ deviceType: EDeviceType.Pro2, thumbnail: true })).toEqual({
|
|
47
|
+
width: 263,
|
|
48
|
+
height: 263,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('preserves the legacy NFT dimensions', () => {
|
|
53
|
+
expect(getNftSize({ deviceType: EDeviceType.Touch })).toEqual({ width: 480, height: 800 });
|
|
54
|
+
expect(getNftSize({ deviceType: EDeviceType.Touch, thumbnail: true })).toEqual({
|
|
55
|
+
width: 238,
|
|
56
|
+
height: 238,
|
|
57
|
+
});
|
|
58
|
+
expect(getNftSize({ deviceType: EDeviceType.Pro, thumbnail: true })).toEqual({
|
|
59
|
+
width: 226,
|
|
60
|
+
height: 226,
|
|
61
|
+
radius: 40,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
24
64
|
});
|
|
@@ -104,7 +104,7 @@ describe('getLogBlockLabel', () => {
|
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
it.each(['deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
|
|
107
|
+
it.each(['deviceUploadNft', 'deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
|
|
108
108
|
'keeps metadata and replaces binary payloads with their size for %s',
|
|
109
109
|
method => {
|
|
110
110
|
const request = { method, path: 'resource.bin', data: new Uint8Array(1024) };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { DeviceSessionPinType
|
|
2
|
+
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
5
5
|
import OpenWalletSession from '../src/api/OpenWalletSession';
|
|
@@ -27,6 +27,7 @@ const createDevice = ({
|
|
|
27
27
|
unlocked: true,
|
|
28
28
|
passphraseProtection,
|
|
29
29
|
attachToPinEnabled: false,
|
|
30
|
+
unlockedAttachPin: false,
|
|
30
31
|
},
|
|
31
32
|
commands: {
|
|
32
33
|
typedCall: jest.fn((request: string, ...args: unknown[]) => {
|
|
@@ -49,10 +50,16 @@ const createDevice = ({
|
|
|
49
50
|
eventless_wallet_session: true,
|
|
50
51
|
})
|
|
51
52
|
),
|
|
52
|
-
getDeviceState: jest.fn()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
getDeviceState: jest.fn(() =>
|
|
54
|
+
Promise.resolve({
|
|
55
|
+
identity: { deviceId: 'device-1' },
|
|
56
|
+
status: {
|
|
57
|
+
unlocked: device.features.unlocked,
|
|
58
|
+
passphraseProtection: device.features.passphraseProtection,
|
|
59
|
+
unlockedAttachPin: device.features.unlockedAttachPin,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
),
|
|
56
63
|
getCurrentDeviceId: () => 'device-1',
|
|
57
64
|
getCurrentPassphraseProtection: () => passphraseProtection,
|
|
58
65
|
getInternalState: () =>
|
|
@@ -323,10 +330,24 @@ describe('openWalletSession', () => {
|
|
|
323
330
|
typedCall,
|
|
324
331
|
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
325
332
|
});
|
|
326
|
-
device.getDeviceState = jest
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
333
|
+
device.getDeviceState = jest
|
|
334
|
+
.fn()
|
|
335
|
+
.mockResolvedValueOnce({
|
|
336
|
+
identity: { deviceId: 'device-1' },
|
|
337
|
+
status: {
|
|
338
|
+
unlocked: false,
|
|
339
|
+
passphraseProtection: null,
|
|
340
|
+
unlockedAttachPin: null,
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
.mockResolvedValue({
|
|
344
|
+
identity: { deviceId: 'device-1' },
|
|
345
|
+
status: {
|
|
346
|
+
unlocked: true,
|
|
347
|
+
passphraseProtection: true,
|
|
348
|
+
unlockedAttachPin: false,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
330
351
|
method.device = device as any;
|
|
331
352
|
|
|
332
353
|
await expect(method.run()).resolves.toMatchObject({
|
|
@@ -380,6 +401,12 @@ describe('openWalletSession', () => {
|
|
|
380
401
|
status.passphrase_enabled ?? device.features.passphraseProtection;
|
|
381
402
|
return device.features;
|
|
382
403
|
});
|
|
404
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
405
|
+
device.features.unlocked = true;
|
|
406
|
+
device.features.passphraseProtection = true;
|
|
407
|
+
device.features.unlockedAttachPin = false;
|
|
408
|
+
return Promise.resolve(device.features);
|
|
409
|
+
});
|
|
383
410
|
method.device = device as any;
|
|
384
411
|
|
|
385
412
|
await expect(method.run()).resolves.toEqual({
|
|
@@ -389,10 +416,98 @@ describe('openWalletSession', () => {
|
|
|
389
416
|
passphraseState: 'hidden-state-after-unlock',
|
|
390
417
|
resumed: false,
|
|
391
418
|
});
|
|
392
|
-
expect(device.unlockDevice).
|
|
419
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
393
420
|
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
394
421
|
});
|
|
395
422
|
|
|
423
|
+
test('unlocks before wallet selection when the Protocol V2 wallet status is unknown', async () => {
|
|
424
|
+
const typedCall = jest
|
|
425
|
+
.fn()
|
|
426
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
427
|
+
.mockResolvedValueOnce({ message: {} })
|
|
428
|
+
.mockResolvedValueOnce({
|
|
429
|
+
message: {
|
|
430
|
+
btc_test_address: 'hidden-state-after-unknown-status',
|
|
431
|
+
session_id: 'hidden-session-after-unknown-status',
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
435
|
+
const method = new OpenWalletSession({
|
|
436
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
437
|
+
});
|
|
438
|
+
method.init();
|
|
439
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
440
|
+
device.getDeviceState = jest
|
|
441
|
+
.fn()
|
|
442
|
+
.mockResolvedValueOnce({
|
|
443
|
+
identity: { deviceId: 'device-1' },
|
|
444
|
+
status: {
|
|
445
|
+
unlocked: undefined,
|
|
446
|
+
passphraseProtection: null,
|
|
447
|
+
unlockedAttachPin: null,
|
|
448
|
+
},
|
|
449
|
+
})
|
|
450
|
+
.mockResolvedValue({
|
|
451
|
+
identity: { deviceId: 'device-1' },
|
|
452
|
+
status: {
|
|
453
|
+
unlocked: true,
|
|
454
|
+
passphraseProtection: true,
|
|
455
|
+
unlockedAttachPin: false,
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
459
|
+
device.features.unlocked = true;
|
|
460
|
+
device.features.passphraseProtection = true;
|
|
461
|
+
device.features.unlockedAttachPin = false;
|
|
462
|
+
return Promise.resolve(device.features);
|
|
463
|
+
});
|
|
464
|
+
method.device = device as any;
|
|
465
|
+
|
|
466
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
467
|
+
walletType: 'hidden',
|
|
468
|
+
passphraseState: 'hidden-state-after-unknown-status',
|
|
469
|
+
});
|
|
470
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
471
|
+
expect(device.unlockDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
472
|
+
promptPassphrase.mock.invocationCallOrder[0]
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('rejects wallet selection when the post-unlock Protocol V2 status remains incomplete', async () => {
|
|
477
|
+
const typedCall = jest
|
|
478
|
+
.fn()
|
|
479
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
480
|
+
.mockResolvedValueOnce({ message: {} })
|
|
481
|
+
.mockResolvedValueOnce({
|
|
482
|
+
message: {
|
|
483
|
+
btc_test_address: 'hidden-state-from-incomplete-status',
|
|
484
|
+
session_id: 'hidden-session-from-incomplete-status',
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
488
|
+
const method = new OpenWalletSession({
|
|
489
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
490
|
+
});
|
|
491
|
+
method.init();
|
|
492
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
493
|
+
device.getDeviceState = jest.fn().mockResolvedValue({
|
|
494
|
+
identity: { deviceId: 'device-1' },
|
|
495
|
+
status: {
|
|
496
|
+
unlocked: true,
|
|
497
|
+
passphraseProtection: null,
|
|
498
|
+
unlockedAttachPin: null,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
device.unlockDevice = jest.fn().mockResolvedValue(device.features);
|
|
502
|
+
method.device = device as any;
|
|
503
|
+
|
|
504
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
505
|
+
errorCode: HardwareErrorCode.DeviceInitializeFailed,
|
|
506
|
+
});
|
|
507
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
508
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
509
|
+
});
|
|
510
|
+
|
|
396
511
|
test('rejects a Pro2 standard-wallet fallback after selecting a hidden wallet', async () => {
|
|
397
512
|
const typedCall = jest
|
|
398
513
|
.fn()
|
|
@@ -529,7 +644,6 @@ describe('openWalletSession', () => {
|
|
|
529
644
|
});
|
|
530
645
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
531
646
|
session_id: 'known-session',
|
|
532
|
-
btc_test_address: 'hidden-state',
|
|
533
647
|
});
|
|
534
648
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
535
649
|
});
|
|
@@ -741,7 +855,11 @@ describe('openWalletSession', () => {
|
|
|
741
855
|
})
|
|
742
856
|
.mockResolvedValueOnce({
|
|
743
857
|
identity: { deviceId: 'device-1' },
|
|
744
|
-
status: {
|
|
858
|
+
status: {
|
|
859
|
+
unlocked: true,
|
|
860
|
+
passphraseProtection: true,
|
|
861
|
+
unlockedAttachPin: false,
|
|
862
|
+
},
|
|
745
863
|
});
|
|
746
864
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
747
865
|
device.features.unlocked = true;
|
|
@@ -780,14 +898,24 @@ describe('openWalletSession', () => {
|
|
|
780
898
|
method.init();
|
|
781
899
|
const device = createDevice({ typedCall });
|
|
782
900
|
device.features.unlockedAttachPin = true;
|
|
783
|
-
device.getDeviceState = jest
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
901
|
+
device.getDeviceState = jest
|
|
902
|
+
.fn()
|
|
903
|
+
.mockResolvedValueOnce({
|
|
904
|
+
identity: { deviceId: 'device-1' },
|
|
905
|
+
status: {
|
|
906
|
+
unlocked: true,
|
|
907
|
+
unlockedAttachPin: true,
|
|
908
|
+
passphraseProtection: true,
|
|
909
|
+
},
|
|
910
|
+
})
|
|
911
|
+
.mockResolvedValue({
|
|
912
|
+
identity: { deviceId: 'device-1' },
|
|
913
|
+
status: {
|
|
914
|
+
unlocked: true,
|
|
915
|
+
unlockedAttachPin: false,
|
|
916
|
+
passphraseProtection: true,
|
|
917
|
+
},
|
|
918
|
+
});
|
|
791
919
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
792
920
|
device.features.unlockedAttachPin = false;
|
|
793
921
|
return Promise.resolve(device.features);
|
|
@@ -1253,7 +1381,6 @@ describe('openWalletSession', () => {
|
|
|
1253
1381
|
});
|
|
1254
1382
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1255
1383
|
session_id: 'known-session',
|
|
1256
|
-
btc_test_address: 'hidden-state',
|
|
1257
1384
|
});
|
|
1258
1385
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1259
1386
|
});
|
|
@@ -1289,7 +1416,11 @@ describe('openWalletSession', () => {
|
|
|
1289
1416
|
})
|
|
1290
1417
|
.mockResolvedValueOnce({
|
|
1291
1418
|
identity: { deviceId: 'device-1' },
|
|
1292
|
-
status: {
|
|
1419
|
+
status: {
|
|
1420
|
+
unlocked: true,
|
|
1421
|
+
passphraseProtection: true,
|
|
1422
|
+
unlockedAttachPin: false,
|
|
1423
|
+
},
|
|
1293
1424
|
});
|
|
1294
1425
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
1295
1426
|
device.features.unlocked = true;
|
|
@@ -1308,7 +1439,6 @@ describe('openWalletSession', () => {
|
|
|
1308
1439
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
1309
1440
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1310
1441
|
session_id: 'known-session',
|
|
1311
|
-
btc_test_address: 'hidden-state',
|
|
1312
1442
|
});
|
|
1313
1443
|
const sessionGetCall = typedCall.mock.calls.findIndex(
|
|
1314
1444
|
([requestName]) => requestName === 'DeviceSessionGet'
|
|
@@ -1346,7 +1476,11 @@ describe('openWalletSession', () => {
|
|
|
1346
1476
|
})
|
|
1347
1477
|
.mockResolvedValueOnce({
|
|
1348
1478
|
identity: { deviceId: 'other-device' },
|
|
1349
|
-
status: {
|
|
1479
|
+
status: {
|
|
1480
|
+
unlocked: true,
|
|
1481
|
+
passphraseProtection: true,
|
|
1482
|
+
unlockedAttachPin: false,
|
|
1483
|
+
},
|
|
1350
1484
|
});
|
|
1351
1485
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
1352
1486
|
device.features.unlocked = true;
|
|
@@ -1401,7 +1535,6 @@ describe('openWalletSession', () => {
|
|
|
1401
1535
|
});
|
|
1402
1536
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1403
1537
|
session_id: 'expired-session',
|
|
1404
|
-
btc_test_address: 'hidden-state',
|
|
1405
1538
|
});
|
|
1406
1539
|
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1407
1540
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
|
|
@@ -1437,26 +1570,12 @@ describe('openWalletSession', () => {
|
|
|
1437
1570
|
passphraseState: 'hidden-state',
|
|
1438
1571
|
resumed: false,
|
|
1439
1572
|
});
|
|
1440
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1441
|
-
btc_test_address: 'hidden-state',
|
|
1442
|
-
});
|
|
1573
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1443
1574
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1444
1575
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
|
|
1445
1576
|
});
|
|
1446
1577
|
|
|
1447
|
-
test.each([
|
|
1448
|
-
{
|
|
1449
|
-
deriveCardano: false,
|
|
1450
|
-
seedDomains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1451
|
-
},
|
|
1452
|
-
{
|
|
1453
|
-
deriveCardano: true,
|
|
1454
|
-
seedDomains: [
|
|
1455
|
-
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
1456
|
-
DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
1457
|
-
],
|
|
1458
|
-
},
|
|
1459
|
-
])('requests the selected seed domains: $seedDomains', async ({ deriveCardano, seedDomains }) => {
|
|
1578
|
+
test.each([false, true])('keeps deriveCardano API compatibility: %s', async deriveCardano => {
|
|
1460
1579
|
const typedCall = jest
|
|
1461
1580
|
.fn()
|
|
1462
1581
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
@@ -1485,8 +1604,6 @@ describe('openWalletSession', () => {
|
|
|
1485
1604
|
walletType: 'hidden',
|
|
1486
1605
|
passphraseState: 'hidden-state',
|
|
1487
1606
|
});
|
|
1488
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1489
|
-
seed_domains: seedDomains,
|
|
1490
|
-
});
|
|
1607
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1491
1608
|
});
|
|
1492
1609
|
});
|