@onekeyfe/hd-core 1.2.0-alpha.183 → 1.2.0-alpha.185
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 +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +19 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +102 -2
- package/__tests__/open-wallet-session.test.ts +127 -1
- package/__tests__/protocol-v2-resources.test.ts +0 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +155 -2
- package/dist/api/FirmwareUpdateV4.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/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.js +644 -347
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +193 -21
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/core/index.ts +16 -2
- package/src/data/messages/messages-protocol-v2.json +111 -1
- package/src/device/Device.ts +3 -1
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +65 -22
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
|
@@ -95,7 +95,8 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
95
95
|
'hidden-state',
|
|
96
96
|
false,
|
|
97
97
|
undefined,
|
|
98
|
-
false
|
|
98
|
+
false,
|
|
99
|
+
undefined
|
|
99
100
|
);
|
|
100
101
|
expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
|
|
101
102
|
expect(typedCall).not.toHaveBeenCalled();
|
|
@@ -173,7 +174,13 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
173
174
|
0
|
|
174
175
|
);
|
|
175
176
|
|
|
176
|
-
expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
|
|
177
|
+
expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
|
|
178
|
+
'hidden-state',
|
|
179
|
+
false,
|
|
180
|
+
undefined,
|
|
181
|
+
true,
|
|
182
|
+
undefined
|
|
183
|
+
);
|
|
177
184
|
expect(calls).toEqual(['resume-cardano-session', 'run-cardano-method']);
|
|
178
185
|
expect(typedCall).not.toHaveBeenCalled();
|
|
179
186
|
});
|
|
@@ -212,7 +219,10 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
212
219
|
bundle: [],
|
|
213
220
|
},
|
|
214
221
|
});
|
|
215
|
-
method.protocolV2UnlockContext = {
|
|
222
|
+
method.protocolV2UnlockContext = {
|
|
223
|
+
preflightCompleted: true,
|
|
224
|
+
preflightMainPinSelected: true,
|
|
225
|
+
};
|
|
216
226
|
const typedCall = jest.fn();
|
|
217
227
|
method.device = {
|
|
218
228
|
checkPassphraseStateSafety,
|
|
@@ -250,7 +260,13 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
250
260
|
0
|
|
251
261
|
);
|
|
252
262
|
|
|
253
|
-
expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
|
|
263
|
+
expect(checkPassphraseStateSafety).toHaveBeenCalledWith(
|
|
264
|
+
undefined,
|
|
265
|
+
true,
|
|
266
|
+
undefined,
|
|
267
|
+
false,
|
|
268
|
+
true
|
|
269
|
+
);
|
|
254
270
|
expect(calls).toEqual(['resume-standard-session', 'run-chain-method']);
|
|
255
271
|
expect(typedCall).not.toHaveBeenCalled();
|
|
256
272
|
});
|
|
@@ -126,6 +126,19 @@ describe('DeviceCommands failure mapping', () => {
|
|
|
126
126
|
);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
it('preserves the Protocol V2 peer-removed pairing error code', async () => {
|
|
130
|
+
const commands = createCommands();
|
|
131
|
+
const transportError = Object.assign(new Error('Peer removed pairing information'), {
|
|
132
|
+
errorCode: HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
133
|
+
});
|
|
134
|
+
commands.mainId = 'main-id';
|
|
135
|
+
commands.transport = {
|
|
136
|
+
call: jest.fn().mockRejectedValue(transportError),
|
|
137
|
+
} as any;
|
|
138
|
+
|
|
139
|
+
await expect(commands._commonCall('DeviceInfoGet', {})).rejects.toBe(transportError);
|
|
140
|
+
});
|
|
141
|
+
|
|
129
142
|
it('logs canonical DeviceStatus response fields without exposing the device ID', async () => {
|
|
130
143
|
const commands = createCommands();
|
|
131
144
|
const log = getLogger(LoggerNames.DeviceCommands);
|
|
@@ -33,8 +33,6 @@ const validComponentIntegrity = {
|
|
|
33
33
|
const release: IFirmwareReleaseInfo = {
|
|
34
34
|
required: true,
|
|
35
35
|
version: [2, 0, 0],
|
|
36
|
-
url: 'https://example.com/application-p1.bin',
|
|
37
|
-
fingerprint: 'release-fingerprint',
|
|
38
36
|
changelog: {
|
|
39
37
|
'zh-CN': '更新',
|
|
40
38
|
'en-US': 'Update',
|
|
@@ -16,8 +16,6 @@ jest.mock('../src/data/config', () => ({
|
|
|
16
16
|
const release: IFirmwareReleaseInfo = {
|
|
17
17
|
required: false,
|
|
18
18
|
version: [2, 0, 0],
|
|
19
|
-
url: 'https://example.com/application-p1.okpkg',
|
|
20
|
-
fingerprint: 'release-fingerprint',
|
|
21
19
|
changelog: {
|
|
22
20
|
'zh-CN': '更新',
|
|
23
21
|
'en-US': 'Update',
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
initConnector,
|
|
6
6
|
initCore,
|
|
7
7
|
isMissingDetectedProtocolV2Error,
|
|
8
|
+
isProtocolV2PeerRemovedPairingError,
|
|
8
9
|
isRetryableBleConnectionError,
|
|
9
10
|
isRetryableBleProtocolV2ProbeError,
|
|
10
11
|
} from '../src/core';
|
|
@@ -278,6 +279,7 @@ describe('public device lifecycle events', () => {
|
|
|
278
279
|
test.each([
|
|
279
280
|
[HardwareErrorCode.RuntimeError, true],
|
|
280
281
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
282
|
+
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
281
283
|
] as const)(
|
|
282
284
|
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
283
285
|
(errorCode, expected) => {
|
|
@@ -297,6 +299,7 @@ describe('public device lifecycle events', () => {
|
|
|
297
299
|
[HardwareErrorCode.BleTimeoutError, true],
|
|
298
300
|
[HardwareErrorCode.PollingTimeout, false],
|
|
299
301
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
302
|
+
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
300
303
|
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
301
304
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
302
305
|
const error = {
|
|
@@ -307,6 +310,22 @@ describe('public device lifecycle events', () => {
|
|
|
307
310
|
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
308
311
|
});
|
|
309
312
|
|
|
313
|
+
test.each([
|
|
314
|
+
['V2', true],
|
|
315
|
+
['V1', false],
|
|
316
|
+
[undefined, false],
|
|
317
|
+
] as const)(
|
|
318
|
+
'treats peer-removed pairing as terminal only for Protocol %s: %s',
|
|
319
|
+
(connectProtocol, expected) => {
|
|
320
|
+
const method = { payload: { connectProtocol } } as never;
|
|
321
|
+
const error = {
|
|
322
|
+
errorCode: HardwareErrorCode.BlePeerRemovedPairingInformation,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
expect(isProtocolV2PeerRemovedPairingError(method, error)).toBe(expected);
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
|
|
310
329
|
test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
|
|
311
330
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
312
331
|
core = initCore();
|
|
@@ -36,7 +36,12 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
36
36
|
records: [
|
|
37
37
|
{
|
|
38
38
|
target_id: 4,
|
|
39
|
-
status: '
|
|
39
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
|
|
40
|
+
progress_percent: 42,
|
|
41
|
+
phase_info: {
|
|
42
|
+
phase: 'FW_MGMT_UPDATER_PHASE_INSTALL',
|
|
43
|
+
progress_percent: 60,
|
|
44
|
+
},
|
|
40
45
|
path: 'vol0:/application_p1.bin',
|
|
41
46
|
},
|
|
42
47
|
],
|
|
@@ -101,7 +106,20 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
101
106
|
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
102
107
|
|
|
103
108
|
expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
|
|
104
|
-
expect(
|
|
109
|
+
expect(typedCall.mock.calls[1]?.[2]).toEqual({
|
|
110
|
+
fields: {
|
|
111
|
+
status: true,
|
|
112
|
+
progress_percent: true,
|
|
113
|
+
phase_info: true,
|
|
114
|
+
payload_version: true,
|
|
115
|
+
path: true,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(42, 'installingFirmware', {
|
|
119
|
+
installTargetId: 4,
|
|
120
|
+
installPhase: 'install',
|
|
121
|
+
installPhaseProgress: 60,
|
|
122
|
+
});
|
|
105
123
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
106
124
|
expect(call.mock.invocationCallOrder[0]).toBeLessThan(typedCall.mock.invocationCallOrder[1]);
|
|
107
125
|
expect(setCancelableAction).toHaveBeenCalledTimes(2);
|
|
@@ -318,6 +336,88 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
318
336
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
319
337
|
});
|
|
320
338
|
|
|
339
|
+
test.each([
|
|
340
|
+
['disconnect', new Error('device was disconnected')],
|
|
341
|
+
[
|
|
342
|
+
'response timeout',
|
|
343
|
+
ERRORS.TypedError(
|
|
344
|
+
HardwareErrorCode.BleTimeoutError,
|
|
345
|
+
'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
|
|
346
|
+
),
|
|
347
|
+
],
|
|
348
|
+
])(
|
|
349
|
+
'accepts stable finished BLE status after install %s hides in-progress',
|
|
350
|
+
async (_, error) => {
|
|
351
|
+
const method = new FirmwareUpdateV4({
|
|
352
|
+
id: 1,
|
|
353
|
+
payload: {
|
|
354
|
+
method: 'firmwareUpdateV4',
|
|
355
|
+
connectId: 'pro2-ble',
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
const targets = [{ target_id: 6, path: 'vol0:/coprocessor.bin' }];
|
|
359
|
+
const finishedStatus = {
|
|
360
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
361
|
+
message: {
|
|
362
|
+
records: [
|
|
363
|
+
{
|
|
364
|
+
target_id: 6,
|
|
365
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
366
|
+
payload_version: 65_556,
|
|
367
|
+
path: 'vol0:/coprocessor.bin',
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
const typedCall = jest
|
|
373
|
+
.fn()
|
|
374
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
375
|
+
.mockImplementationOnce(() => {
|
|
376
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
377
|
+
throw error;
|
|
378
|
+
})
|
|
379
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
380
|
+
.mockImplementationOnce(() => {
|
|
381
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
382
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
383
|
+
return finishedStatus;
|
|
384
|
+
})
|
|
385
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
386
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
387
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
388
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
389
|
+
|
|
390
|
+
(method as any).params = {
|
|
391
|
+
expectedTargetVersions: { coprocessor: '1.0.20' },
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
method.device = {
|
|
395
|
+
getCommands: () => ({ typedCall }),
|
|
396
|
+
setCancelableAction: jest.fn(),
|
|
397
|
+
} as unknown as Device;
|
|
398
|
+
method.postProgressMessage = jest.fn();
|
|
399
|
+
|
|
400
|
+
const firmwareUpdate = method as unknown as {
|
|
401
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
402
|
+
value: typeof targets,
|
|
403
|
+
requireCurrentInstallStatus: boolean
|
|
404
|
+
) => Promise<void>;
|
|
405
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
406
|
+
};
|
|
407
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
408
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
409
|
+
|
|
410
|
+
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
411
|
+
|
|
412
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
413
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
414
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
415
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
416
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
|
|
417
|
+
},
|
|
418
|
+
10_000
|
|
419
|
+
);
|
|
420
|
+
|
|
321
421
|
test('confirms App mode when BLE status becomes empty after current install progress', async () => {
|
|
322
422
|
const method = new FirmwareUpdateV4({
|
|
323
423
|
id: 1,
|
|
@@ -148,7 +148,7 @@ describe('openWalletSession', () => {
|
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test('
|
|
151
|
+
test('selects the Main PIN before opening the standard wallet when passphrase is disabled', async () => {
|
|
152
152
|
const typedCall = jest.fn((request: string) => {
|
|
153
153
|
if (request === 'ProtocolInfoRequest') {
|
|
154
154
|
return { message: { version: 2 } };
|
|
@@ -167,6 +167,11 @@ describe('openWalletSession', () => {
|
|
|
167
167
|
|
|
168
168
|
await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
|
|
169
169
|
|
|
170
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
171
|
+
source: 'wallet-session-coordinator',
|
|
172
|
+
reason: 'open-wallet',
|
|
173
|
+
deviceOnly: true,
|
|
174
|
+
});
|
|
170
175
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
171
176
|
'DeviceSessionAskPassphrase',
|
|
172
177
|
'Success',
|
|
@@ -177,6 +182,34 @@ describe('openWalletSession', () => {
|
|
|
177
182
|
});
|
|
178
183
|
});
|
|
179
184
|
|
|
185
|
+
test('reuses a Main PIN selected by the current preflight when passphrase is disabled', async () => {
|
|
186
|
+
const typedCall = jest.fn((request: string) => {
|
|
187
|
+
if (request === 'ProtocolInfoRequest') {
|
|
188
|
+
return { message: { version: 2 } };
|
|
189
|
+
}
|
|
190
|
+
if (request === 'DeviceSessionGet') {
|
|
191
|
+
return {
|
|
192
|
+
message: {
|
|
193
|
+
btc_test_address: 'standard-state',
|
|
194
|
+
session_id: 'standard-session',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
199
|
+
});
|
|
200
|
+
const device = createDevice({ passphraseProtection: false, typedCall });
|
|
201
|
+
|
|
202
|
+
await getProtocolV2WalletSession(device as any, {
|
|
203
|
+
onlyMainPin: true,
|
|
204
|
+
mainPinSelected: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
208
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
209
|
+
seed_domains: [],
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
180
213
|
test('does not treat an Attach PIN DeviceStatus as the Main wallet', async () => {
|
|
181
214
|
const typedCall = jest.fn((request: string) => {
|
|
182
215
|
if (request === 'ProtocolInfoRequest') {
|
|
@@ -1114,6 +1147,68 @@ describe('openWalletSession', () => {
|
|
|
1114
1147
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
1115
1148
|
});
|
|
1116
1149
|
|
|
1150
|
+
test('does not restrict a locked hidden-wallet selection to the Main PIN', async () => {
|
|
1151
|
+
const typedCall = jest
|
|
1152
|
+
.fn()
|
|
1153
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1154
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1155
|
+
.mockResolvedValueOnce({
|
|
1156
|
+
message: {
|
|
1157
|
+
btc_test_address: 'hidden-state',
|
|
1158
|
+
session_id: 'hidden-session',
|
|
1159
|
+
},
|
|
1160
|
+
});
|
|
1161
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1162
|
+
const method = new OpenWalletSession({
|
|
1163
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1164
|
+
});
|
|
1165
|
+
method.init();
|
|
1166
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1167
|
+
device.features.unlocked = false;
|
|
1168
|
+
device.getDeviceState = jest
|
|
1169
|
+
.fn()
|
|
1170
|
+
.mockResolvedValueOnce({
|
|
1171
|
+
identity: { deviceId: undefined },
|
|
1172
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
1173
|
+
})
|
|
1174
|
+
.mockResolvedValueOnce({
|
|
1175
|
+
identity: { deviceId: 'device-1' },
|
|
1176
|
+
status: {
|
|
1177
|
+
unlocked: true,
|
|
1178
|
+
passphraseProtection: true,
|
|
1179
|
+
unlockedAttachPin: false,
|
|
1180
|
+
},
|
|
1181
|
+
})
|
|
1182
|
+
.mockResolvedValueOnce({
|
|
1183
|
+
identity: { deviceId: 'device-1' },
|
|
1184
|
+
status: {
|
|
1185
|
+
unlocked: true,
|
|
1186
|
+
passphraseProtection: true,
|
|
1187
|
+
unlockedAttachPin: false,
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
1191
|
+
device.features.unlocked = true;
|
|
1192
|
+
return Promise.resolve(device.features);
|
|
1193
|
+
});
|
|
1194
|
+
method.device = device as any;
|
|
1195
|
+
|
|
1196
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
1197
|
+
walletType: 'hidden',
|
|
1198
|
+
passphraseState: 'hidden-state',
|
|
1199
|
+
});
|
|
1200
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
1201
|
+
source: 'unlock-coordinator',
|
|
1202
|
+
reason: 'device-locked',
|
|
1203
|
+
deviceOnly: true,
|
|
1204
|
+
method: 'openWalletSession',
|
|
1205
|
+
});
|
|
1206
|
+
expect(device.unlockDevice).not.toHaveBeenCalledWith(
|
|
1207
|
+
DeviceSessionPinType.Main,
|
|
1208
|
+
expect.anything()
|
|
1209
|
+
);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1117
1212
|
test('switches from Attach PIN to Main PIN before opening the standard wallet', async () => {
|
|
1118
1213
|
const typedCall = jest
|
|
1119
1214
|
.fn()
|
|
@@ -1208,6 +1303,37 @@ describe('openWalletSession', () => {
|
|
|
1208
1303
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
|
|
1209
1304
|
});
|
|
1210
1305
|
|
|
1306
|
+
test('reuses the fresh unlock and wallet-selection statuses', async () => {
|
|
1307
|
+
const typedCall = jest
|
|
1308
|
+
.fn()
|
|
1309
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1310
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1311
|
+
.mockResolvedValueOnce({
|
|
1312
|
+
message: {
|
|
1313
|
+
btc_test_address: 'hidden-state',
|
|
1314
|
+
session_id: 'hidden-session',
|
|
1315
|
+
},
|
|
1316
|
+
});
|
|
1317
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1318
|
+
const method = new OpenWalletSession({
|
|
1319
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1320
|
+
});
|
|
1321
|
+
method.init();
|
|
1322
|
+
method.protocolV2UnlockContext = {
|
|
1323
|
+
preflightCompleted: true,
|
|
1324
|
+
preflightStatusRefreshed: true,
|
|
1325
|
+
};
|
|
1326
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1327
|
+
method.device = device as any;
|
|
1328
|
+
|
|
1329
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
1330
|
+
walletType: 'hidden',
|
|
1331
|
+
passphraseState: 'hidden-state',
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
expect(device.getDeviceState.mock.calls).toEqual([[], []]);
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1211
1337
|
test('gets the prepared session after host passphrase entry', async () => {
|
|
1212
1338
|
const typedCall = jest
|
|
1213
1339
|
.fn()
|
|
@@ -66,8 +66,6 @@ const createRemoteConfig = (): RemoteConfigResponse =>
|
|
|
66
66
|
{
|
|
67
67
|
required: false,
|
|
68
68
|
version: [1, 0, 0],
|
|
69
|
-
url: 'https://example.com/pro2.okpkg',
|
|
70
|
-
fingerprint: 'a'.repeat(64),
|
|
71
69
|
changelog: { 'zh-CN': 'Pro2', 'en-US': 'Pro2' },
|
|
72
70
|
resources: { source: resourceSource },
|
|
73
71
|
},
|
|
@@ -79,8 +77,6 @@ const createRemoteConfig = (): RemoteConfigResponse =>
|
|
|
79
77
|
{
|
|
80
78
|
required: false,
|
|
81
79
|
version: [1, 0, 0],
|
|
82
|
-
url: 'https://example.com/neo.okpkg',
|
|
83
|
-
fingerprint: 'b'.repeat(64),
|
|
84
80
|
changelog: { 'zh-CN': 'Neo', 'en-US': 'Neo' },
|
|
85
81
|
resources: { source: neoResourceSource },
|
|
86
82
|
},
|
|
@@ -151,8 +151,42 @@ describe('Protocol V2 unlock semantics', () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
154
|
-
expect(device.unlockDevice).
|
|
154
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, expect.any(Object));
|
|
155
155
|
expect(run).toHaveBeenCalledTimes(1);
|
|
156
|
+
expect(method.protocolV2UnlockContext).toMatchObject({
|
|
157
|
+
preflightCompleted: true,
|
|
158
|
+
preflightStatusRefreshed: true,
|
|
159
|
+
preflightMainPinSelected: true,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('allows either PIN type when pre-unlocking hidden-wallet selection', async () => {
|
|
164
|
+
const method = new OpenWalletSession({
|
|
165
|
+
id: 1,
|
|
166
|
+
payload: { method: 'openWalletSession', mode: 'select-hidden' },
|
|
167
|
+
});
|
|
168
|
+
method.init();
|
|
169
|
+
method.run = jest.fn().mockResolvedValue({ walletType: 'hidden' }) as any;
|
|
170
|
+
const features = { unlocked: false };
|
|
171
|
+
const device = {
|
|
172
|
+
features,
|
|
173
|
+
commands: {
|
|
174
|
+
typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
|
|
175
|
+
},
|
|
176
|
+
isProtocolV2: () => true,
|
|
177
|
+
isBootloader: () => false,
|
|
178
|
+
isRomloader: () => false,
|
|
179
|
+
updateProtocolV2Status: jest.fn(() => features),
|
|
180
|
+
unlockDevice: jest.fn().mockImplementation(() => {
|
|
181
|
+
features.unlocked = true;
|
|
182
|
+
return Promise.resolve(features);
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
await runMethodWithUnlockPolicy(method, device as any);
|
|
187
|
+
|
|
188
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
|
|
189
|
+
expect(method.protocolV2UnlockContext?.preflightMainPinSelected).toBe(false);
|
|
156
190
|
});
|
|
157
191
|
|
|
158
192
|
test('pre-unlocks an unregistered wallet business method', async () => {
|