@onekeyfe/hd-core 1.2.0-alpha.35 → 1.2.0-alpha.36
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 +74 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/get-device-state.test.ts +104 -12
- package/__tests__/open-wallet-session.test.ts +131 -7
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
- package/__tests__/protocol-v2.test.ts +290 -39
- 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/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +22 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +27 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +56 -7
- package/dist/index.js +692 -397
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- 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/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +1 -1
- package/src/api/FirmwareUpdateV4.ts +13 -6
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +18 -3
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +46 -4
- package/src/data/messages/messages-protocol-v2.json +15 -0
- package/src/device/Device.ts +221 -24
- package/src/events/device.ts +4 -0
- package/src/events/ui-request.ts +32 -2
- package/src/protocols/protocol-v2/features.ts +9 -2
- package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
- package/src/protocols/protocol-v2/unlockRetry.ts +33 -11
- package/src/protocols/protocol-v2/walletSession.ts +140 -32
- package/src/utils/deviceFeaturesUtils.ts +4 -0
|
@@ -18,6 +18,79 @@ class TestAllNetworkMethod extends AllNetworkGetAddressBase {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe('AllNetworkGetAddressBase tracing', () => {
|
|
21
|
+
test('resumes a Protocol V2 hidden wallet before running a nested chain method', async () => {
|
|
22
|
+
const calls: string[] = [];
|
|
23
|
+
const checkPassphraseStateSafety = jest.fn().mockImplementation(() => {
|
|
24
|
+
calls.push('resume-hidden-session');
|
|
25
|
+
return Promise.resolve(true);
|
|
26
|
+
});
|
|
27
|
+
const innerMethod = {
|
|
28
|
+
checkSafetyLevelOnTestNet: jest.fn().mockResolvedValue(false),
|
|
29
|
+
connectId: 'connect-id',
|
|
30
|
+
deviceId: 'device-id',
|
|
31
|
+
getVersionRange: jest.fn().mockReturnValue({}),
|
|
32
|
+
assertProtocolSupported: jest.fn(),
|
|
33
|
+
init: jest.fn(),
|
|
34
|
+
name: 'evmGetAddress',
|
|
35
|
+
responseID: 43,
|
|
36
|
+
run: jest.fn().mockImplementation(() => {
|
|
37
|
+
calls.push('run-chain-method');
|
|
38
|
+
return Promise.resolve([{ address: '0xhidden' }]);
|
|
39
|
+
}),
|
|
40
|
+
setDevice: jest.fn(),
|
|
41
|
+
strictCheckDeviceSupport: false,
|
|
42
|
+
};
|
|
43
|
+
(findMethod as jest.Mock).mockReturnValue(innerMethod);
|
|
44
|
+
const method = new TestAllNetworkMethod({
|
|
45
|
+
id: 1,
|
|
46
|
+
payload: {
|
|
47
|
+
method: 'allNetworkGetAddress',
|
|
48
|
+
connectId: 'connect-id',
|
|
49
|
+
deviceId: 'device-id',
|
|
50
|
+
passphraseState: 'hidden-state',
|
|
51
|
+
bundle: [],
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
method.device = {
|
|
55
|
+
checkPassphraseStateSafety,
|
|
56
|
+
commands: {
|
|
57
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
58
|
+
},
|
|
59
|
+
getCurrentFirmwareType: jest.fn(),
|
|
60
|
+
getProtocol: jest.fn().mockReturnValue('V2'),
|
|
61
|
+
getCurrentFirmwareVersionString: jest.fn().mockReturnValue('1.0.0'),
|
|
62
|
+
getCurrentMethodVersionRange: jest
|
|
63
|
+
.fn()
|
|
64
|
+
.mockImplementation((getRange: (type: string) => unknown) => getRange('pro2')),
|
|
65
|
+
instanceId: 'device-instance',
|
|
66
|
+
isProtocolV2: jest.fn().mockReturnValue(true),
|
|
67
|
+
isBootloader: jest.fn().mockReturnValue(false),
|
|
68
|
+
isRomloader: jest.fn().mockReturnValue(false),
|
|
69
|
+
off: jest.fn(),
|
|
70
|
+
on: jest.fn(),
|
|
71
|
+
state: { status: { unlocked: true } },
|
|
72
|
+
updateProtocolV2Status: jest.fn(),
|
|
73
|
+
} as any;
|
|
74
|
+
|
|
75
|
+
await method.callMethod(
|
|
76
|
+
'evmGetAddress',
|
|
77
|
+
{
|
|
78
|
+
bundle: [
|
|
79
|
+
{
|
|
80
|
+
_originRequestParams: {
|
|
81
|
+
network: 'evm',
|
|
82
|
+
path: "m/44'/60'/0'/0/0",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
0
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(checkPassphraseStateSafety).toHaveBeenCalledWith('hidden-state', false, undefined);
|
|
91
|
+
expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
|
|
92
|
+
});
|
|
93
|
+
|
|
21
94
|
test('releases the nested request context when an unhandled error escapes', async () => {
|
|
22
95
|
const deviceInstanceId = 'device-instance';
|
|
23
96
|
const innerMethod = {
|
|
@@ -51,6 +124,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
51
124
|
getCurrentMethodVersionRange: jest
|
|
52
125
|
.fn()
|
|
53
126
|
.mockImplementation((getRange: (type: string) => unknown) => getRange('classic')),
|
|
127
|
+
isProtocolV2: jest.fn().mockReturnValue(false),
|
|
54
128
|
off: jest.fn(),
|
|
55
129
|
on: jest.fn(),
|
|
56
130
|
} as any;
|
|
@@ -3,6 +3,8 @@ import { registerHardwareUiEventListeners } from '../src/core/deviceEventRegistr
|
|
|
3
3
|
|
|
4
4
|
const handlers = {
|
|
5
5
|
pin: jest.fn(),
|
|
6
|
+
pinOnDevice: jest.fn(),
|
|
7
|
+
pinOnDeviceComplete: jest.fn(),
|
|
6
8
|
button: jest.fn(),
|
|
7
9
|
passphrase: jest.fn(),
|
|
8
10
|
passphraseOnDevice: jest.fn(),
|
|
@@ -21,6 +23,8 @@ describe('hardware UI event registration', () => {
|
|
|
21
23
|
expect(registerHardwareUiEventListeners(device as any, handlers)).toBe(true);
|
|
22
24
|
expect(device.on.mock.calls.map(([type]) => type)).toEqual([
|
|
23
25
|
DEVICE.PIN,
|
|
26
|
+
DEVICE.PIN_ON_DEVICE,
|
|
27
|
+
DEVICE.PIN_ON_DEVICE_COMPLETE,
|
|
24
28
|
DEVICE.BUTTON,
|
|
25
29
|
DEVICE.PASSPHRASE,
|
|
26
30
|
DEVICE.PASSPHRASE_ON_DEVICE,
|
|
@@ -34,6 +38,8 @@ describe('hardware UI event registration', () => {
|
|
|
34
38
|
expect(registerHardwareUiEventListeners(device as any, handlers)).toBe(true);
|
|
35
39
|
expect(device.on.mock.calls.map(([type]) => type)).toEqual([
|
|
36
40
|
DEVICE.PIN,
|
|
41
|
+
DEVICE.PIN_ON_DEVICE,
|
|
42
|
+
DEVICE.PIN_ON_DEVICE_COMPLETE,
|
|
37
43
|
DEVICE.BUTTON,
|
|
38
44
|
DEVICE.PASSPHRASE,
|
|
39
45
|
DEVICE.PASSPHRASE_ON_DEVICE,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceType } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import { Device } from '../src/device/Device';
|
|
@@ -43,6 +43,70 @@ const getProtocolV2LoaderInfo = (mode: 'bootloader' | 'romloader') => ({
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
describe('getDeviceState', () => {
|
|
46
|
+
test('coalesces concurrent Protocol V2 runtime-context negotiation', async () => {
|
|
47
|
+
let resolveProtocolInfo:
|
|
48
|
+
| ((value: { message: typeof protocolV2ApplicationInfo }) => void)
|
|
49
|
+
| undefined;
|
|
50
|
+
const typedCall = jest.fn(
|
|
51
|
+
() =>
|
|
52
|
+
new Promise<{ message: typeof protocolV2ApplicationInfo }>(resolve => {
|
|
53
|
+
resolveProtocolInfo = resolve;
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
const device = createV2Device(typedCall);
|
|
57
|
+
|
|
58
|
+
const first = device.ensureProtocolV2RuntimeContext();
|
|
59
|
+
const second = device.ensureProtocolV2RuntimeContext();
|
|
60
|
+
resolveProtocolInfo?.({ message: protocolV2ApplicationInfo });
|
|
61
|
+
|
|
62
|
+
await expect(Promise.all([first, second])).resolves.toEqual([
|
|
63
|
+
protocolV2ApplicationInfo,
|
|
64
|
+
protocolV2ApplicationInfo,
|
|
65
|
+
]);
|
|
66
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
68
|
+
eventless_wallet_session: true,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('renegotiates Protocol V2 runtime context after transport disconnect', async () => {
|
|
73
|
+
const typedCall = jest.fn().mockResolvedValue({ message: protocolV2ApplicationInfo });
|
|
74
|
+
const device = createV2Device(typedCall);
|
|
75
|
+
|
|
76
|
+
await device.ensureProtocolV2RuntimeContext();
|
|
77
|
+
await device.ensureProtocolV2RuntimeContext();
|
|
78
|
+
device.markTransportDisconnected();
|
|
79
|
+
await device.ensureProtocolV2RuntimeContext();
|
|
80
|
+
|
|
81
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('rejects every waiter when runtime-context negotiation is invalidated', async () => {
|
|
85
|
+
let resolveProtocolInfo:
|
|
86
|
+
| ((value: { message: typeof protocolV2ApplicationInfo }) => void)
|
|
87
|
+
| undefined;
|
|
88
|
+
const typedCall = jest.fn(
|
|
89
|
+
() =>
|
|
90
|
+
new Promise<{ message: typeof protocolV2ApplicationInfo }>(resolve => {
|
|
91
|
+
resolveProtocolInfo = resolve;
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
const device = createV2Device(typedCall);
|
|
95
|
+
|
|
96
|
+
const first = device.ensureProtocolV2RuntimeContext();
|
|
97
|
+
const second = device.ensureProtocolV2RuntimeContext();
|
|
98
|
+
const firstExpectation = expect(first).rejects.toMatchObject({
|
|
99
|
+
errorCode: HardwareErrorCode.DeviceInitializeFailed,
|
|
100
|
+
});
|
|
101
|
+
const secondExpectation = expect(second).rejects.toMatchObject({
|
|
102
|
+
errorCode: HardwareErrorCode.DeviceInitializeFailed,
|
|
103
|
+
});
|
|
104
|
+
device.markTransportDisconnected();
|
|
105
|
+
resolveProtocolInfo?.({ message: protocolV2ApplicationInfo });
|
|
106
|
+
|
|
107
|
+
await Promise.all([firstExpectation, secondExpectation]);
|
|
108
|
+
});
|
|
109
|
+
|
|
46
110
|
test('does not expose the internal wallet session', async () => {
|
|
47
111
|
const device = createV2Device(jest.fn());
|
|
48
112
|
device.updateState({ protocol: 'V2' }, 'initialize');
|
|
@@ -159,15 +223,20 @@ describe('getDeviceState', () => {
|
|
|
159
223
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
160
224
|
});
|
|
161
225
|
const device = createV2Device(typedCall);
|
|
162
|
-
device.updateState(
|
|
226
|
+
device.updateState(
|
|
227
|
+
{
|
|
228
|
+
protocol: 'V2',
|
|
229
|
+
identity: { deviceType: EDeviceType.Pro2 },
|
|
230
|
+
status: { mode },
|
|
231
|
+
raw: { protocolV2ProtocolInfo: getProtocolV2LoaderInfo(mode) },
|
|
232
|
+
},
|
|
233
|
+
'initialize'
|
|
234
|
+
);
|
|
163
235
|
|
|
164
236
|
const state = await device.getDeviceState({ refreshSections: ['status'] });
|
|
165
237
|
|
|
166
238
|
expect(state.status.mode).toBe(mode);
|
|
167
|
-
expect(typedCall.
|
|
168
|
-
'DeviceInfoGet',
|
|
169
|
-
'ProtocolInfoRequest',
|
|
170
|
-
]);
|
|
239
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
171
240
|
}
|
|
172
241
|
);
|
|
173
242
|
|
|
@@ -187,7 +256,14 @@ describe('getDeviceState', () => {
|
|
|
187
256
|
return { message: { init_states: true, unlocked: true, device_id: 'device-1' } };
|
|
188
257
|
});
|
|
189
258
|
const device = createV2Device(typedCall);
|
|
190
|
-
device.updateState(
|
|
259
|
+
device.updateState(
|
|
260
|
+
{
|
|
261
|
+
protocol: 'V2',
|
|
262
|
+
status: { mode: 'normal' },
|
|
263
|
+
raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
|
|
264
|
+
},
|
|
265
|
+
'initialize'
|
|
266
|
+
);
|
|
191
267
|
|
|
192
268
|
const state = await device.getDeviceState({ refreshSections: ['status'] });
|
|
193
269
|
|
|
@@ -205,7 +281,14 @@ describe('getDeviceState', () => {
|
|
|
205
281
|
},
|
|
206
282
|
});
|
|
207
283
|
const device = createV2Device(typedCall);
|
|
208
|
-
device.updateState(
|
|
284
|
+
device.updateState(
|
|
285
|
+
{
|
|
286
|
+
protocol: 'V2',
|
|
287
|
+
status: { mode: 'normal' },
|
|
288
|
+
raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
|
|
289
|
+
},
|
|
290
|
+
'initialize'
|
|
291
|
+
);
|
|
209
292
|
const onState = jest.fn();
|
|
210
293
|
device.on(DEVICE.STATE, onState);
|
|
211
294
|
|
|
@@ -250,13 +333,18 @@ describe('getDeviceState', () => {
|
|
|
250
333
|
throw new Error(`Unexpected request: ${requestType}`);
|
|
251
334
|
});
|
|
252
335
|
const device = createV2Device(typedCall);
|
|
253
|
-
device.updateState(
|
|
336
|
+
device.updateState(
|
|
337
|
+
{
|
|
338
|
+
protocol: 'V2',
|
|
339
|
+
status: { mode: 'normal' },
|
|
340
|
+
raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
|
|
341
|
+
},
|
|
342
|
+
'initialize'
|
|
343
|
+
);
|
|
254
344
|
|
|
255
345
|
const state = await device.getDeviceState({ refreshSections: ['status', 'settings'] });
|
|
256
346
|
|
|
257
347
|
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
258
|
-
'DeviceInfoGet',
|
|
259
|
-
'ProtocolInfoRequest',
|
|
260
348
|
'DeviceStatusGet',
|
|
261
349
|
'DeviceSettingsGet',
|
|
262
350
|
]);
|
|
@@ -288,7 +376,11 @@ describe('getDeviceState', () => {
|
|
|
288
376
|
});
|
|
289
377
|
const device = createV2Device(typedCall);
|
|
290
378
|
device.updateState(
|
|
291
|
-
{
|
|
379
|
+
{
|
|
380
|
+
protocol: 'V2',
|
|
381
|
+
status: { mode: 'normal', unlocked: true },
|
|
382
|
+
raw: { protocolV2ProtocolInfo: protocolV2ApplicationInfo },
|
|
383
|
+
},
|
|
292
384
|
'initialize'
|
|
293
385
|
);
|
|
294
386
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
|
+
import { DeviceSessionPinType, DeviceSessionSeedDomain } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
5
5
|
import OpenWalletSession from '../src/api/OpenWalletSession';
|
|
6
6
|
import { Device } from '../src/device/Device';
|
|
7
7
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
8
|
+
import { ensureProtocolV2WalletSessionUnlocked } from '../src/protocols/protocol-v2/walletSession';
|
|
8
9
|
|
|
9
10
|
jest.mock('../src/data/config', () => ({
|
|
10
11
|
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
@@ -43,6 +44,11 @@ const createDevice = ({
|
|
|
43
44
|
}),
|
|
44
45
|
promptPassphrase,
|
|
45
46
|
},
|
|
47
|
+
ensureProtocolV2RuntimeContext: jest.fn(() =>
|
|
48
|
+
device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
49
|
+
eventless_wallet_session: true,
|
|
50
|
+
})
|
|
51
|
+
),
|
|
46
52
|
getDeviceState: jest.fn().mockResolvedValue({
|
|
47
53
|
identity: { deviceId: 'device-1' },
|
|
48
54
|
status: { passphraseProtection },
|
|
@@ -82,6 +88,50 @@ describe('openWalletSession', () => {
|
|
|
82
88
|
deviceWalletSessionStore.clear();
|
|
83
89
|
});
|
|
84
90
|
|
|
91
|
+
test('unlocks a locked Protocol V2 device before restoring a wallet session', async () => {
|
|
92
|
+
const device = {
|
|
93
|
+
commands: {
|
|
94
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
|
|
95
|
+
},
|
|
96
|
+
isBootloader: jest.fn().mockReturnValue(false),
|
|
97
|
+
isProtocolV2: jest.fn().mockReturnValue(true),
|
|
98
|
+
isRomloader: jest.fn().mockReturnValue(false),
|
|
99
|
+
state: { status: { unlocked: false } },
|
|
100
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
101
|
+
updateProtocolV2Status: jest.fn(function updateProtocolV2Status(
|
|
102
|
+
this: { state: { status: { unlocked: boolean } } },
|
|
103
|
+
status: { unlocked?: boolean }
|
|
104
|
+
) {
|
|
105
|
+
this.state.status.unlocked = status.unlocked ?? this.state.status.unlocked;
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await expect(ensureProtocolV2WalletSessionUnlocked(device as any)).resolves.toBe(true);
|
|
110
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
111
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
112
|
+
source: 'unlock-coordinator',
|
|
113
|
+
reason: 'device-locked',
|
|
114
|
+
deviceOnly: true,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('does not request PIN when wallet-session recovery finds the device unlocked', async () => {
|
|
119
|
+
const device = {
|
|
120
|
+
commands: {
|
|
121
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
|
|
122
|
+
},
|
|
123
|
+
isBootloader: jest.fn().mockReturnValue(false),
|
|
124
|
+
isProtocolV2: jest.fn().mockReturnValue(true),
|
|
125
|
+
isRomloader: jest.fn().mockReturnValue(false),
|
|
126
|
+
state: { status: { unlocked: true } },
|
|
127
|
+
unlockDevice: jest.fn(),
|
|
128
|
+
updateProtocolV2Status: jest.fn(),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await expect(ensureProtocolV2WalletSessionUnlocked(device as any)).resolves.toBe(false);
|
|
132
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
85
135
|
test.each([{ useEmptyPassphrase: true }, { initSession: true }, {}])(
|
|
86
136
|
'requires the explicit mode in the new public API: %p',
|
|
87
137
|
legacyParams => {
|
|
@@ -479,6 +529,7 @@ describe('openWalletSession', () => {
|
|
|
479
529
|
});
|
|
480
530
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
481
531
|
session_id: 'known-session',
|
|
532
|
+
btc_test_address: 'hidden-state',
|
|
482
533
|
});
|
|
483
534
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
484
535
|
});
|
|
@@ -656,7 +707,11 @@ describe('openWalletSession', () => {
|
|
|
656
707
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
657
708
|
eventless_wallet_session: true,
|
|
658
709
|
});
|
|
659
|
-
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main
|
|
710
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
711
|
+
source: 'wallet-session-coordinator',
|
|
712
|
+
reason: 'open-wallet',
|
|
713
|
+
deviceOnly: true,
|
|
714
|
+
});
|
|
660
715
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
661
716
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
662
717
|
expect(device.passphraseState).toBeUndefined();
|
|
@@ -701,7 +756,11 @@ describe('openWalletSession', () => {
|
|
|
701
756
|
passphraseState: null,
|
|
702
757
|
resumed: false,
|
|
703
758
|
});
|
|
704
|
-
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main
|
|
759
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
760
|
+
source: 'wallet-session-coordinator',
|
|
761
|
+
reason: 'open-wallet',
|
|
762
|
+
deviceOnly: true,
|
|
763
|
+
});
|
|
705
764
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
706
765
|
});
|
|
707
766
|
|
|
@@ -721,6 +780,14 @@ describe('openWalletSession', () => {
|
|
|
721
780
|
method.init();
|
|
722
781
|
const device = createDevice({ typedCall });
|
|
723
782
|
device.features.unlockedAttachPin = true;
|
|
783
|
+
device.getDeviceState = jest.fn().mockResolvedValue({
|
|
784
|
+
identity: { deviceId: 'device-1' },
|
|
785
|
+
status: {
|
|
786
|
+
unlocked: true,
|
|
787
|
+
unlockedAttachPin: true,
|
|
788
|
+
passphraseProtection: true,
|
|
789
|
+
},
|
|
790
|
+
});
|
|
724
791
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
725
792
|
device.features.unlockedAttachPin = false;
|
|
726
793
|
return Promise.resolve(device.features);
|
|
@@ -731,7 +798,11 @@ describe('openWalletSession', () => {
|
|
|
731
798
|
walletType: 'standard',
|
|
732
799
|
passphraseState: null,
|
|
733
800
|
});
|
|
734
|
-
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main
|
|
801
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
802
|
+
source: 'wallet-session-coordinator',
|
|
803
|
+
reason: 'open-wallet',
|
|
804
|
+
deviceOnly: true,
|
|
805
|
+
});
|
|
735
806
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
736
807
|
});
|
|
737
808
|
|
|
@@ -939,7 +1010,9 @@ describe('openWalletSession', () => {
|
|
|
939
1010
|
},
|
|
940
1011
|
{ cancelDeviceOnReject: false }
|
|
941
1012
|
);
|
|
942
|
-
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin
|
|
1013
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin, {
|
|
1014
|
+
emitUiEvent: false,
|
|
1015
|
+
});
|
|
943
1016
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
944
1017
|
});
|
|
945
1018
|
|
|
@@ -1180,6 +1253,7 @@ describe('openWalletSession', () => {
|
|
|
1180
1253
|
});
|
|
1181
1254
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1182
1255
|
session_id: 'known-session',
|
|
1256
|
+
btc_test_address: 'hidden-state',
|
|
1183
1257
|
});
|
|
1184
1258
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1185
1259
|
});
|
|
@@ -1234,6 +1308,7 @@ describe('openWalletSession', () => {
|
|
|
1234
1308
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
1235
1309
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1236
1310
|
session_id: 'known-session',
|
|
1311
|
+
btc_test_address: 'hidden-state',
|
|
1237
1312
|
});
|
|
1238
1313
|
const sessionGetCall = typedCall.mock.calls.findIndex(
|
|
1239
1314
|
([requestName]) => requestName === 'DeviceSessionGet'
|
|
@@ -1326,6 +1401,7 @@ describe('openWalletSession', () => {
|
|
|
1326
1401
|
});
|
|
1327
1402
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1328
1403
|
session_id: 'expired-session',
|
|
1404
|
+
btc_test_address: 'hidden-state',
|
|
1329
1405
|
});
|
|
1330
1406
|
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1331
1407
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
|
|
@@ -1335,7 +1411,6 @@ describe('openWalletSession', () => {
|
|
|
1335
1411
|
const typedCall = jest
|
|
1336
1412
|
.fn()
|
|
1337
1413
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1338
|
-
.mockResolvedValueOnce({ message: {} })
|
|
1339
1414
|
.mockResolvedValueOnce({
|
|
1340
1415
|
message: {
|
|
1341
1416
|
btc_test_address: 'hidden-state',
|
|
@@ -1362,7 +1437,56 @@ describe('openWalletSession', () => {
|
|
|
1362
1437
|
passphraseState: 'hidden-state',
|
|
1363
1438
|
resumed: false,
|
|
1364
1439
|
});
|
|
1365
|
-
expect(
|
|
1440
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1441
|
+
btc_test_address: 'hidden-state',
|
|
1442
|
+
});
|
|
1443
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1366
1444
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
|
|
1367
1445
|
});
|
|
1446
|
+
|
|
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 }) => {
|
|
1460
|
+
const typedCall = jest
|
|
1461
|
+
.fn()
|
|
1462
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1463
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1464
|
+
.mockResolvedValueOnce({
|
|
1465
|
+
message: {
|
|
1466
|
+
btc_test_address: 'hidden-state',
|
|
1467
|
+
session_id: 'new-session',
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
const method = new OpenWalletSession({
|
|
1471
|
+
payload: {
|
|
1472
|
+
method: 'openWalletSession',
|
|
1473
|
+
connectId: 'connect-id',
|
|
1474
|
+
mode: 'select-hidden',
|
|
1475
|
+
deriveCardano,
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
method.init();
|
|
1479
|
+
method.device = createDevice({
|
|
1480
|
+
typedCall,
|
|
1481
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
1482
|
+
}) as any;
|
|
1483
|
+
|
|
1484
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
1485
|
+
walletType: 'hidden',
|
|
1486
|
+
passphraseState: 'hidden-state',
|
|
1487
|
+
});
|
|
1488
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1489
|
+
seed_domains: seedDomains,
|
|
1490
|
+
});
|
|
1491
|
+
});
|
|
1368
1492
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
|
+
|
|
3
|
+
import { DEVICE } from '../src/events';
|
|
4
|
+
import { Device } from '../src/device/Device';
|
|
5
|
+
|
|
6
|
+
jest.mock('../src/data/config', () => ({
|
|
7
|
+
getSDKVersion: jest.fn(() => '1.0.0-test'),
|
|
8
|
+
DEFAULT_DOMAIN: 'https://example.com/',
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('Protocol V2 UI interaction lifecycle', () => {
|
|
12
|
+
test('emits a matching PIN phase completion after DeviceSessionAskPin succeeds', async () => {
|
|
13
|
+
const device = new Device({ id: 'connect-1' } as any);
|
|
14
|
+
device.isProtocolV2 = jest.fn(() => true);
|
|
15
|
+
device.updateProtocolV2Status = jest.fn(() => undefined as any);
|
|
16
|
+
device.commands = {
|
|
17
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
18
|
+
message: { unlocked: true },
|
|
19
|
+
}),
|
|
20
|
+
} as any;
|
|
21
|
+
device.beginProtocolV2UiInteraction();
|
|
22
|
+
|
|
23
|
+
const starts: unknown[] = [];
|
|
24
|
+
const completions: unknown[] = [];
|
|
25
|
+
device.on(DEVICE.PIN_ON_DEVICE, (...args) => starts.push(args));
|
|
26
|
+
device.on(DEVICE.PIN_ON_DEVICE_COMPLETE, (...args) => completions.push(args));
|
|
27
|
+
|
|
28
|
+
await device.unlockDevice(DeviceSessionPinType.Main, {
|
|
29
|
+
source: 'unlock-coordinator',
|
|
30
|
+
reason: 'device-locked',
|
|
31
|
+
deviceOnly: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const startMetadata = (starts[0] as unknown[])[2] as Record<string, unknown>;
|
|
35
|
+
const completionMetadata = (completions[0] as unknown[])[1] as Record<string, unknown>;
|
|
36
|
+
expect(startMetadata.interaction).toMatchObject({
|
|
37
|
+
phase: 'pin',
|
|
38
|
+
transition: 'start',
|
|
39
|
+
protocol: 'V2',
|
|
40
|
+
});
|
|
41
|
+
expect(completionMetadata).toMatchObject({
|
|
42
|
+
interactionId: (startMetadata.interaction as { interactionId: string }).interactionId,
|
|
43
|
+
phaseId: (startMetadata.interaction as { phaseId: string }).phaseId,
|
|
44
|
+
phase: 'pin',
|
|
45
|
+
transition: 'complete',
|
|
46
|
+
outcome: 'succeeded',
|
|
47
|
+
protocol: 'V2',
|
|
48
|
+
});
|
|
49
|
+
expect(completionMetadata.sequence).toBeGreaterThan(
|
|
50
|
+
(startMetadata.interaction as { sequence: number }).sequence
|
|
51
|
+
);
|
|
52
|
+
expect(completionMetadata.phaseId).toBe(
|
|
53
|
+
(startMetadata.interaction as { phaseId: string }).phaseId
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|