@onekeyfe/hd-core 1.2.0-alpha.10 → 1.2.0-alpha.12
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__/DeviceCommands.test.ts +25 -0
- package/__tests__/RequestQueue.test.ts +34 -0
- package/__tests__/pro2Wallpaper.test.ts +50 -0
- package/__tests__/protocol-v2.test.ts +511 -61
- package/__tests__/protocolV2FileWrite.test.ts +55 -0
- package/dist/api/BaseMethod.d.ts +4 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +0 -4
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +1 -6
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +18 -0
- package/dist/api/UploadPortfolio.d.ts.map +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts +32 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts +1 -4
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts +16 -0
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts +12 -0
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +26 -0
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -0
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.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.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts +2 -2
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/events/call.d.ts +7 -0
- package/dist/events/call.d.ts.map +1 -1
- package/dist/events/core.d.ts +2 -2
- package/dist/events/core.d.ts.map +1 -1
- package/dist/events/iframe.d.ts +1 -0
- package/dist/events/iframe.d.ts.map +1 -1
- package/dist/index.d.ts +80 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +922 -472
- package/dist/inject.d.ts +3 -2
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts +2 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +1 -0
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +1 -1
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +9 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +2 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/getDeviceInfo.d.ts +1 -1
- package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts +2 -10
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +9 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +20 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +1 -1
- package/dist/types/device.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/index.d.ts +2 -0
- 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 +12 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +18 -0
- package/src/api/FileWrite.ts +18 -178
- package/src/api/FirmwareUpdateV4.ts +21 -91
- package/src/api/GetPassphraseState.ts +9 -18
- package/src/api/UploadPortfolio.ts +37 -0
- package/src/api/helpers/protocolV2FileWrite.ts +164 -0
- package/src/api/index.ts +6 -0
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +18 -0
- package/src/api/protocol-v2/DeviceSessionGet.ts +3 -14
- package/src/api/protocol-v2/DeviceSettingsGet.ts +16 -0
- package/src/api/protocol-v2/DeviceSettingsPageShow.ts +59 -0
- package/src/api/protocol-v2/DeviceSettingsSet.ts +39 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +124 -0
- package/src/core/RequestQueue.ts +19 -0
- package/src/core/index.ts +10 -1
- package/src/data/messages/messages-protocol-v2.json +92 -8
- package/src/device/Device.ts +17 -3
- package/src/device/DeviceCommands.ts +9 -3
- package/src/deviceProfile/buildDeviceFeatures.ts +20 -12
- package/src/deviceProfile/buildDeviceProfile.ts +8 -6
- package/src/events/call.ts +6 -0
- package/src/events/core.ts +2 -0
- package/src/events/iframe.ts +1 -0
- package/src/index.ts +4 -0
- package/src/inject.ts +15 -0
- package/src/lowLevelInject.ts +3 -0
- package/src/protocols/protocol-v2/features.ts +19 -1
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +46 -0
- package/src/protocols/protocol-v2/walletSession.ts +14 -5
- package/src/topLevelInject.ts +2 -0
- package/src/types/api/firmwareUpdate.ts +12 -0
- package/src/types/api/getDeviceInfo.ts +1 -1
- package/src/types/api/getPassphraseState.ts +2 -11
- package/src/types/api/index.ts +14 -1
- package/src/types/api/protocolV2.ts +52 -3
- package/src/types/device.ts +1 -0
- package/src/utils/deviceFeaturesUtils.ts +3 -8
- package/src/utils/index.ts +6 -0
- package/src/utils/pro2Wallpaper.ts +111 -0
|
@@ -7,12 +7,18 @@ import DnxSignTransaction from '../src/api/dynex/DnxSignTransaction';
|
|
|
7
7
|
import DirList from '../src/api/DirList';
|
|
8
8
|
import FileRead from '../src/api/FileRead';
|
|
9
9
|
import FileWrite from '../src/api/FileWrite';
|
|
10
|
+
import UploadPortfolio from '../src/api/UploadPortfolio';
|
|
10
11
|
import DeviceFactoryInfoGet from '../src/api/protocol-v2/DeviceFactoryInfoGet';
|
|
11
12
|
import DeviceFactoryInfoSet from '../src/api/protocol-v2/DeviceFactoryInfoSet';
|
|
12
13
|
import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
|
|
13
14
|
import DeviceGetFirmwareUpdateStatus from '../src/api/protocol-v2/DeviceGetFirmwareUpdateStatus';
|
|
15
|
+
import DeviceGetOnboardingStatus from '../src/api/protocol-v2/DeviceGetOnboardingStatus';
|
|
14
16
|
import DeviceInfoGet from '../src/api/protocol-v2/DeviceInfoGet';
|
|
15
17
|
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
18
|
+
import DeviceSettingsGet from '../src/api/protocol-v2/DeviceSettingsGet';
|
|
19
|
+
import DeviceSettingsPageShow from '../src/api/protocol-v2/DeviceSettingsPageShow';
|
|
20
|
+
import DeviceSettingsSet from '../src/api/protocol-v2/DeviceSettingsSet';
|
|
21
|
+
import DeviceUploadWallpaper from '../src/api/protocol-v2/DeviceUploadWallpaper';
|
|
16
22
|
import DeviceSessionGet from '../src/api/protocol-v2/DeviceSessionGet';
|
|
17
23
|
import DeviceStatusGet from '../src/api/protocol-v2/DeviceStatusGet';
|
|
18
24
|
import FilesystemFormat from '../src/api/protocol-v2/FilesystemFormat';
|
|
@@ -56,6 +62,7 @@ import {
|
|
|
56
62
|
getProtocolV2WalletSession,
|
|
57
63
|
refreshProtocolV2DeviceStatus,
|
|
58
64
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
65
|
+
import { runMethodWithUnlockRetry } from '../src/protocols/protocol-v2/unlockRetry';
|
|
59
66
|
import { buildProfileFromProtocolV2, buildProtocolV2FeaturesPayload } from '../src/deviceProfile';
|
|
60
67
|
import {
|
|
61
68
|
getDeviceType,
|
|
@@ -80,6 +87,175 @@ jest.mock('../src/data/config', () => ({
|
|
|
80
87
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
81
88
|
}));
|
|
82
89
|
|
|
90
|
+
describe('DeviceUploadWallpaper', () => {
|
|
91
|
+
test('encodes, uploads and applies a Pro2 wallpaper', async () => {
|
|
92
|
+
const rgba = new Uint8Array(604 * 1024 * 4).fill(255);
|
|
93
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
94
|
+
if (request === 'FilesystemDirMake') return { message: { message: 'directory ready' } };
|
|
95
|
+
if (request === 'FilesystemFileWrite') {
|
|
96
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
97
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
98
|
+
}
|
|
99
|
+
if (request === 'DeviceSettingsSet') {
|
|
100
|
+
return { message: { message: 'wallpaper applied' } };
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
103
|
+
});
|
|
104
|
+
const method = new DeviceUploadWallpaper({
|
|
105
|
+
id: 1,
|
|
106
|
+
payload: { method: 'deviceUploadWallpaper', width: 604, height: 1024, rgba },
|
|
107
|
+
});
|
|
108
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
109
|
+
|
|
110
|
+
method.init();
|
|
111
|
+
const result = await method.run();
|
|
112
|
+
|
|
113
|
+
expect(method.requireProtocolV2).toBe(true);
|
|
114
|
+
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
115
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'FilesystemDirMake', 'Success', {
|
|
116
|
+
path: 'vol0:/wallpapers/user',
|
|
117
|
+
});
|
|
118
|
+
const fileWriteCall = typedCall.mock.calls.find(call => call[0] === 'FilesystemFileWrite');
|
|
119
|
+
expect(fileWriteCall?.[2]).toMatchObject({
|
|
120
|
+
file: { path: expect.stringMatching(/^vol0:\/wallpapers\/user\/wallpaper-[a-f0-9]+\.bin$/) },
|
|
121
|
+
overwrite: true,
|
|
122
|
+
append: false,
|
|
123
|
+
});
|
|
124
|
+
expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
|
|
125
|
+
settings: { wallpaper_path: result.path },
|
|
126
|
+
});
|
|
127
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'SetWallpaper')).toBe(false);
|
|
128
|
+
expect(result).toMatchObject({ colorFormat: 'RGB565', message: 'wallpaper applied' });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('文件上传失败时不修改 wallpaper_path', async () => {
|
|
132
|
+
const typedCall = jest.fn().mockImplementation(request => {
|
|
133
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
134
|
+
if (request === 'FilesystemFileWrite') throw new Error('write failed');
|
|
135
|
+
return { message: {} };
|
|
136
|
+
});
|
|
137
|
+
const method = new DeviceUploadWallpaper({
|
|
138
|
+
id: 1,
|
|
139
|
+
payload: {
|
|
140
|
+
method: 'deviceUploadWallpaper',
|
|
141
|
+
width: 604,
|
|
142
|
+
height: 1024,
|
|
143
|
+
rgba: new Uint8Array(604 * 1024 * 4),
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
147
|
+
method.init();
|
|
148
|
+
|
|
149
|
+
await expect(method.run()).rejects.toThrow('write failed');
|
|
150
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'DeviceSettingsSet')).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('rejects unsafe filenames before device communication', () => {
|
|
154
|
+
const method = new DeviceUploadWallpaper({
|
|
155
|
+
id: 1,
|
|
156
|
+
payload: {
|
|
157
|
+
method: 'deviceUploadWallpaper',
|
|
158
|
+
width: 604,
|
|
159
|
+
height: 1024,
|
|
160
|
+
rgba: new Uint8Array(604 * 1024 * 4),
|
|
161
|
+
fileName: '../wallpaper.bin',
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(() => method.init()).toThrow('fileName');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('UploadPortfolio', () => {
|
|
170
|
+
test('stages the complete package before applying PortfolioUpdate', async () => {
|
|
171
|
+
const packageBytes = new Uint8Array([1, 2, 3]);
|
|
172
|
+
const typedCall = jest
|
|
173
|
+
.fn()
|
|
174
|
+
.mockResolvedValueOnce({ message: { processed_byte: 3 } })
|
|
175
|
+
.mockResolvedValueOnce({ message: { message: 'Portfolio updated' } });
|
|
176
|
+
const method = new UploadPortfolio({
|
|
177
|
+
id: 1,
|
|
178
|
+
payload: {
|
|
179
|
+
method: 'uploadPortfolio',
|
|
180
|
+
packageBytes,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
184
|
+
method.postMessage = jest.fn();
|
|
185
|
+
|
|
186
|
+
method.init();
|
|
187
|
+
const result = await method.run();
|
|
188
|
+
|
|
189
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
190
|
+
1,
|
|
191
|
+
'FilesystemFileWrite',
|
|
192
|
+
'FilesystemFile',
|
|
193
|
+
{
|
|
194
|
+
file: {
|
|
195
|
+
path: 'vol1:/portfolio/portfolio.pfol.pending',
|
|
196
|
+
offset: 0,
|
|
197
|
+
total_size: 3,
|
|
198
|
+
data: packageBytes,
|
|
199
|
+
},
|
|
200
|
+
overwrite: true,
|
|
201
|
+
append: false,
|
|
202
|
+
ui_percentage: 100,
|
|
203
|
+
},
|
|
204
|
+
{ timeoutMs: undefined }
|
|
205
|
+
);
|
|
206
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'PortfolioUpdate', 'Success', {});
|
|
207
|
+
expect(result).toMatchObject({
|
|
208
|
+
path: 'vol1:/portfolio/portfolio.pfol.pending',
|
|
209
|
+
processed_byte: 3,
|
|
210
|
+
portfolioUpdated: true,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('does not apply PortfolioUpdate when staging fails', async () => {
|
|
215
|
+
const typedCall = jest.fn().mockRejectedValue(new Error('write failed'));
|
|
216
|
+
const method = new UploadPortfolio({
|
|
217
|
+
id: 1,
|
|
218
|
+
payload: {
|
|
219
|
+
method: 'uploadPortfolio',
|
|
220
|
+
packageBytes: new Uint8Array([1]),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
224
|
+
|
|
225
|
+
method.init();
|
|
226
|
+
|
|
227
|
+
await expect(method.run()).rejects.toThrow('write failed');
|
|
228
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('stops after the acknowledged chunk when the operation is aborted', async () => {
|
|
232
|
+
const packageBytes = new Uint8Array(4001);
|
|
233
|
+
const abortController = new AbortController();
|
|
234
|
+
const typedCall = jest.fn().mockImplementationOnce(() => {
|
|
235
|
+
abortController.abort();
|
|
236
|
+
return Promise.resolve({ message: { processed_byte: 2048 } });
|
|
237
|
+
});
|
|
238
|
+
const method = new UploadPortfolio({
|
|
239
|
+
id: 1,
|
|
240
|
+
payload: {
|
|
241
|
+
method: 'uploadPortfolio',
|
|
242
|
+
packageBytes,
|
|
243
|
+
operationId: 'portfolio-1',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
method.abortSignal = abortController.signal;
|
|
247
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
248
|
+
|
|
249
|
+
method.init();
|
|
250
|
+
|
|
251
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
252
|
+
errorCode: HardwareErrorCode.CallQueueActionCancelled,
|
|
253
|
+
});
|
|
254
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
255
|
+
expect(typedCall).not.toHaveBeenCalledWith('PortfolioUpdate', 'Success', {});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
83
259
|
const descriptor = {
|
|
84
260
|
id: 'ble-id',
|
|
85
261
|
path: 'usb-path',
|
|
@@ -151,10 +327,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
151
327
|
serial_no: 'PR2SERIAL',
|
|
152
328
|
},
|
|
153
329
|
fw: {
|
|
154
|
-
|
|
330
|
+
romloader: {
|
|
155
331
|
version: '0.1.0',
|
|
332
|
+
build_id: 'rom-build',
|
|
156
333
|
hash: [1, 2, 255],
|
|
157
334
|
},
|
|
335
|
+
application_data: {
|
|
336
|
+
version: '9.8.7',
|
|
337
|
+
build_id: 'app-data-build',
|
|
338
|
+
hash: [9, 8, 7],
|
|
339
|
+
},
|
|
158
340
|
bootloader: {
|
|
159
341
|
version: '0.2.0',
|
|
160
342
|
build_id: 'boot-build',
|
|
@@ -209,6 +391,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
209
391
|
expect(features.bootloaderVersion).toBe('0.2.0');
|
|
210
392
|
expect(features.verify?.bootloaderBuildId).toBe('boot-build');
|
|
211
393
|
expect(features.verify?.bootloaderHash).toBe('0a0b');
|
|
394
|
+
expect(features.boardVersion).toBe('0.1.0');
|
|
395
|
+
expect(features.verify?.boardBuildId).toBe('rom-build');
|
|
212
396
|
expect(features.verify?.boardHash).toBe('0102ff');
|
|
213
397
|
expect(features.bleName).toBe('Pro2 BLE');
|
|
214
398
|
expect(features.bleVersion).toBe('4.5.6');
|
|
@@ -248,6 +432,32 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
248
432
|
expect(profile.status.bootloaderMode).toBe(true);
|
|
249
433
|
});
|
|
250
434
|
|
|
435
|
+
test('marks current romloader-shaped Protocol V2 DeviceInfo as romloader mode', () => {
|
|
436
|
+
const deviceInfo = {
|
|
437
|
+
protocol_version: 1,
|
|
438
|
+
hw: {
|
|
439
|
+
serial_no: 'PR2ROM',
|
|
440
|
+
},
|
|
441
|
+
fw: {
|
|
442
|
+
romloader: {
|
|
443
|
+
version: '1.0.0',
|
|
444
|
+
},
|
|
445
|
+
bootloader: {
|
|
446
|
+
version: '2.0.0',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
const features = normalizeProtocolV2Features(descriptor as any, deviceInfo);
|
|
451
|
+
const profile = buildProfileFromProtocolV2({ deviceInfo });
|
|
452
|
+
|
|
453
|
+
expect(features.mode).toBe('romloader');
|
|
454
|
+
expect(features.bootloaderMode).toBe(false);
|
|
455
|
+
expect(features.boardVersion).toBe('1.0.0');
|
|
456
|
+
expect(profile.status.mode).toBe('romloader');
|
|
457
|
+
expect(profile.status.bootloaderMode).toBe(false);
|
|
458
|
+
expect(profile.versions.board).toBe('1.0.0');
|
|
459
|
+
});
|
|
460
|
+
|
|
251
461
|
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
252
462
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
253
463
|
features.firmwareVersion = '1.2.3';
|
|
@@ -272,7 +482,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
272
482
|
await expect(
|
|
273
483
|
getPassphraseState(device, {
|
|
274
484
|
expectPassphraseState: 'state-2',
|
|
275
|
-
allowCreateAttachPin: true,
|
|
276
485
|
})
|
|
277
486
|
).rejects.toEqual(
|
|
278
487
|
expect.objectContaining({
|
|
@@ -308,7 +517,33 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
308
517
|
expect(updateProtocolV2Features).not.toHaveBeenCalled();
|
|
309
518
|
});
|
|
310
519
|
|
|
311
|
-
test('
|
|
520
|
+
test('deviceGetOnboardingStatus returns the real Protocol V2 onboarding stage', async () => {
|
|
521
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
522
|
+
type: 'DevOnboardingStatus',
|
|
523
|
+
message: { stage: 2, status_code: 3, detail_code: 4 },
|
|
524
|
+
});
|
|
525
|
+
const method = new DeviceGetOnboardingStatus({
|
|
526
|
+
payload: {
|
|
527
|
+
method: 'deviceGetOnboardingStatus',
|
|
528
|
+
connectId: 'connect-id',
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
method.init();
|
|
532
|
+
method.device = stubDevice({
|
|
533
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
534
|
+
commands: { typedCall },
|
|
535
|
+
}) as any;
|
|
536
|
+
|
|
537
|
+
await expect(method.run()).resolves.toEqual({
|
|
538
|
+
stage: 2,
|
|
539
|
+
status_code: 3,
|
|
540
|
+
detail_code: 4,
|
|
541
|
+
});
|
|
542
|
+
expect(typedCall).toHaveBeenCalledWith('DevGetOnboardingStatus', 'DevOnboardingStatus', {});
|
|
543
|
+
expect(method.requireProtocolV2).toBe(true);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test('deviceSessionGet sends an empty request and does not mutate wallet cache', async () => {
|
|
312
547
|
const typedCall = jest.fn().mockResolvedValue({
|
|
313
548
|
type: 'DeviceSession',
|
|
314
549
|
message: { session_id: 'new-session', btc_test_address: 'state-a' },
|
|
@@ -318,7 +553,6 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
318
553
|
payload: {
|
|
319
554
|
method: 'deviceSessionGet',
|
|
320
555
|
connectId: 'connect-id',
|
|
321
|
-
sessionId: 'cached-session',
|
|
322
556
|
},
|
|
323
557
|
});
|
|
324
558
|
method.init();
|
|
@@ -332,9 +566,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
332
566
|
session_id: 'new-session',
|
|
333
567
|
btc_test_address: 'state-a',
|
|
334
568
|
});
|
|
335
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
336
|
-
session_id: 'cached-session',
|
|
337
|
-
});
|
|
569
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
338
570
|
expect(updateInternalState).not.toHaveBeenCalled();
|
|
339
571
|
});
|
|
340
572
|
|
|
@@ -343,7 +575,8 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
343
575
|
const api = createCoreApi(call as any);
|
|
344
576
|
|
|
345
577
|
await api.deviceStatusGet('connect-id', { retryCount: 1 });
|
|
346
|
-
await api.
|
|
578
|
+
await api.deviceGetOnboardingStatus('connect-id', { retryCount: 1 });
|
|
579
|
+
await api.deviceSessionGet('connect-id', { retryCount: 1 });
|
|
347
580
|
|
|
348
581
|
expect(call).toHaveBeenNthCalledWith(1, {
|
|
349
582
|
method: 'deviceStatusGet',
|
|
@@ -351,9 +584,14 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
351
584
|
retryCount: 1,
|
|
352
585
|
});
|
|
353
586
|
expect(call).toHaveBeenNthCalledWith(2, {
|
|
587
|
+
method: 'deviceGetOnboardingStatus',
|
|
588
|
+
connectId: 'connect-id',
|
|
589
|
+
retryCount: 1,
|
|
590
|
+
});
|
|
591
|
+
expect(call).toHaveBeenNthCalledWith(3, {
|
|
354
592
|
method: 'deviceSessionGet',
|
|
355
593
|
connectId: 'connect-id',
|
|
356
|
-
|
|
594
|
+
retryCount: 1,
|
|
357
595
|
});
|
|
358
596
|
});
|
|
359
597
|
|
|
@@ -415,7 +653,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
415
653
|
expect(device.getInternalState()).toBeUndefined();
|
|
416
654
|
});
|
|
417
655
|
|
|
418
|
-
test('
|
|
656
|
+
test('retries without the selected Pro2 cache entry after invalid session rejection', async () => {
|
|
419
657
|
const device = Device.fromDescriptor({
|
|
420
658
|
id: 'cache-device-3',
|
|
421
659
|
path: 'cache-path-3',
|
|
@@ -433,12 +671,49 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
433
671
|
);
|
|
434
672
|
device.passphraseState = 'state-a';
|
|
435
673
|
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
674
|
+
const typedCall = jest
|
|
675
|
+
.fn()
|
|
676
|
+
.mockRejectedValueOnce(new Error('Failure_ProcessError,Failure_InvalidSession'))
|
|
677
|
+
.mockResolvedValueOnce({
|
|
678
|
+
type: 'DeviceSession',
|
|
679
|
+
message: { session_id: 'session-b', btc_test_address: 'state-a' },
|
|
680
|
+
});
|
|
681
|
+
(device as any).commands = { typedCall };
|
|
682
|
+
|
|
683
|
+
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
684
|
+
passphraseState: 'state-a',
|
|
685
|
+
newSession: 'session-b',
|
|
686
|
+
});
|
|
687
|
+
expect(typedCall.mock.calls).toEqual([
|
|
688
|
+
['DeviceSessionGet', 'DeviceSession', { session_id: 'session-a' }],
|
|
689
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
690
|
+
]);
|
|
691
|
+
expect(device.getInternalState()).toBe('session-b');
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
test('does not retry an invalid Pro2 session when no cached session was sent', async () => {
|
|
695
|
+
const device = Device.fromDescriptor({
|
|
696
|
+
id: 'cache-device-no-session',
|
|
697
|
+
path: 'cache-path-no-session',
|
|
698
|
+
protocolType: 'V2',
|
|
699
|
+
} as any);
|
|
700
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
701
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
702
|
+
{
|
|
703
|
+
status: {
|
|
704
|
+
device_id: 'stable-device-no-session',
|
|
705
|
+
unlocked: true,
|
|
706
|
+
passphrase_enabled: true,
|
|
707
|
+
},
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
const typedCall = jest
|
|
711
|
+
.fn()
|
|
712
|
+
.mockRejectedValue(new Error('Failure_ProcessError,Failure_InvalidSession'));
|
|
713
|
+
(device as any).commands = { typedCall };
|
|
439
714
|
|
|
440
715
|
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Failure_InvalidSession');
|
|
441
|
-
expect(
|
|
716
|
+
expect(typedCall.mock.calls).toEqual([['DeviceSessionGet', 'DeviceSession', {}]]);
|
|
442
717
|
});
|
|
443
718
|
|
|
444
719
|
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
@@ -547,7 +822,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
547
822
|
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
548
823
|
});
|
|
549
824
|
|
|
550
|
-
test('returns
|
|
825
|
+
test('returns passphrase state string for existing Pro devices', async () => {
|
|
551
826
|
const features = {
|
|
552
827
|
deviceId: 'pro-device-id',
|
|
553
828
|
deviceType: 'pro',
|
|
@@ -580,12 +855,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
580
855
|
getCurrentPassphraseProtection: () => true,
|
|
581
856
|
}) as any;
|
|
582
857
|
|
|
583
|
-
await expect(method.run()).resolves.
|
|
584
|
-
passphraseState: 'state-pro',
|
|
585
|
-
sessionId: 'session-pro',
|
|
586
|
-
unlockedAttachPin: false,
|
|
587
|
-
passphraseProtection: true,
|
|
588
|
-
});
|
|
858
|
+
await expect(method.run()).resolves.toBe('state-pro');
|
|
589
859
|
expect(updateInternalState).toHaveBeenCalledWith(
|
|
590
860
|
true,
|
|
591
861
|
'state-pro',
|
|
@@ -595,7 +865,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
595
865
|
);
|
|
596
866
|
});
|
|
597
867
|
|
|
598
|
-
test('
|
|
868
|
+
test('returns passphrase state string for Pro2 devices', async () => {
|
|
599
869
|
const features = {
|
|
600
870
|
deviceId: null,
|
|
601
871
|
deviceType: 'pro2',
|
|
@@ -630,12 +900,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
630
900
|
getCurrentPassphraseProtection: () => true,
|
|
631
901
|
}) as any;
|
|
632
902
|
|
|
633
|
-
await expect(method.run()).resolves.
|
|
634
|
-
passphraseState: 'state-pro2',
|
|
635
|
-
sessionId: 'session-pro2',
|
|
636
|
-
unlockedAttachPin: true,
|
|
637
|
-
passphraseProtection: true,
|
|
638
|
-
});
|
|
903
|
+
await expect(method.run()).resolves.toBe('state-pro2');
|
|
639
904
|
expect(getFeatures).not.toHaveBeenCalled();
|
|
640
905
|
});
|
|
641
906
|
|
|
@@ -673,12 +938,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
673
938
|
getCurrentPassphraseProtection: () => true,
|
|
674
939
|
}) as any;
|
|
675
940
|
|
|
676
|
-
await expect(method.run()).resolves.
|
|
677
|
-
passphraseState: 'state-pro2-new',
|
|
678
|
-
sessionId: undefined,
|
|
679
|
-
unlockedAttachPin: false,
|
|
680
|
-
passphraseProtection: true,
|
|
681
|
-
});
|
|
941
|
+
await expect(method.run()).resolves.toBe('state-pro2-new');
|
|
682
942
|
expect(clearInternalState).toHaveBeenCalledTimes(1);
|
|
683
943
|
expect(updateInternalState).toHaveBeenCalledWith(
|
|
684
944
|
true,
|
|
@@ -1578,16 +1838,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1578
1838
|
if (requestType === 'DeviceSessionAskPin') {
|
|
1579
1839
|
return {
|
|
1580
1840
|
type: 'DeviceSessionPinResult',
|
|
1581
|
-
message: { unlocked: false, passphrase_protection: false },
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
|
-
if (requestType === 'DeviceStatusGet') {
|
|
1585
|
-
return {
|
|
1586
|
-
type: 'DeviceStatus',
|
|
1587
1841
|
message: {
|
|
1588
|
-
device_id: 'PRO2-DEVICE-ID',
|
|
1589
1842
|
unlocked: true,
|
|
1590
|
-
|
|
1843
|
+
unlocked_attach_pin: true,
|
|
1844
|
+
passphrase_protection: true,
|
|
1591
1845
|
},
|
|
1592
1846
|
};
|
|
1593
1847
|
}
|
|
@@ -1608,22 +1862,24 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1608
1862
|
|
|
1609
1863
|
const features = await device.unlockDevice();
|
|
1610
1864
|
|
|
1611
|
-
expect(typedCall.mock.calls).toEqual([
|
|
1612
|
-
['DeviceSessionAskPin', 'DeviceSessionPinResult'],
|
|
1613
|
-
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1614
|
-
]);
|
|
1865
|
+
expect(typedCall.mock.calls).toEqual([['DeviceSessionAskPin', 'DeviceSessionPinResult']]);
|
|
1615
1866
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1616
1867
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1617
1868
|
expect(features).toMatchObject({
|
|
1618
1869
|
deviceType: 'pro2',
|
|
1619
|
-
deviceId: 'PRO2-DEVICE-ID',
|
|
1620
1870
|
firmwareVersion: '1.2.3',
|
|
1621
1871
|
unlocked: true,
|
|
1872
|
+
unlockedAttachPin: true,
|
|
1622
1873
|
passphraseProtection: true,
|
|
1623
1874
|
});
|
|
1875
|
+
expect(features.raw?.protocolV2DeviceInfo?.status).toMatchObject({
|
|
1876
|
+
unlocked: true,
|
|
1877
|
+
unlocked_by_attach_to_pin: true,
|
|
1878
|
+
passphrase_enabled: true,
|
|
1879
|
+
});
|
|
1624
1880
|
});
|
|
1625
1881
|
|
|
1626
|
-
test('syncs Protocol V2 features passphrase state from
|
|
1882
|
+
test('syncs Protocol V2 features passphrase state from DeviceSessionPinResult after unlock', async () => {
|
|
1627
1883
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1628
1884
|
(device as any).features = normalizeProtocolV2Features(
|
|
1629
1885
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1637,20 +1893,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1637
1893
|
if (requestType === 'DeviceSessionAskPin') {
|
|
1638
1894
|
return {
|
|
1639
1895
|
type: 'DeviceSessionPinResult',
|
|
1640
|
-
message: {
|
|
1641
|
-
unlocked: false,
|
|
1642
|
-
unlocked_attach_pin: false,
|
|
1643
|
-
passphrase_protection: false,
|
|
1644
|
-
},
|
|
1645
|
-
};
|
|
1646
|
-
}
|
|
1647
|
-
if (requestType === 'DeviceStatusGet') {
|
|
1648
|
-
return {
|
|
1649
|
-
type: 'DeviceStatus',
|
|
1650
1896
|
message: {
|
|
1651
1897
|
unlocked: true,
|
|
1652
|
-
|
|
1653
|
-
|
|
1898
|
+
unlocked_attach_pin: true,
|
|
1899
|
+
passphrase_protection: true,
|
|
1654
1900
|
},
|
|
1655
1901
|
};
|
|
1656
1902
|
}
|
|
@@ -1660,7 +1906,9 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1660
1906
|
|
|
1661
1907
|
await device.unlockDevice();
|
|
1662
1908
|
|
|
1909
|
+
expect(typedCall.mock.calls).toEqual([['DeviceSessionAskPin', 'DeviceSessionPinResult']]);
|
|
1663
1910
|
expect((device as any).profile).toBeUndefined();
|
|
1911
|
+
expect(device.features?.unlocked).toBe(true);
|
|
1664
1912
|
expect(device.features?.passphraseProtection).toBe(true);
|
|
1665
1913
|
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
1666
1914
|
});
|
|
@@ -2818,6 +3066,16 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2818
3066
|
payload: {
|
|
2819
3067
|
method: 'firmwareUpdateV4',
|
|
2820
3068
|
platform: 'web',
|
|
3069
|
+
targetsToUpdate: [
|
|
3070
|
+
'boot',
|
|
3071
|
+
'app_v1',
|
|
3072
|
+
'app_v2',
|
|
3073
|
+
'coprocessor',
|
|
3074
|
+
'se01',
|
|
3075
|
+
'se02',
|
|
3076
|
+
'se03',
|
|
3077
|
+
'se04',
|
|
3078
|
+
],
|
|
2821
3079
|
},
|
|
2822
3080
|
});
|
|
2823
3081
|
method.init();
|
|
@@ -2931,6 +3189,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2931
3189
|
payload: {
|
|
2932
3190
|
method: 'firmwareUpdateV4',
|
|
2933
3191
|
platform: 'web',
|
|
3192
|
+
targetsToUpdate: ['resource'],
|
|
2934
3193
|
},
|
|
2935
3194
|
});
|
|
2936
3195
|
method.init();
|
|
@@ -2980,7 +3239,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2980
3239
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2981
3240
|
});
|
|
2982
3241
|
|
|
2983
|
-
test('
|
|
3242
|
+
test('does not download Pro2 firmware components that App did not select', async () => {
|
|
2984
3243
|
const method = new FirmwareUpdateV4({
|
|
2985
3244
|
id: 1,
|
|
2986
3245
|
payload: {
|
|
@@ -3671,6 +3930,97 @@ describe('Protocol V2 reboot methods', () => {
|
|
|
3671
3930
|
});
|
|
3672
3931
|
});
|
|
3673
3932
|
|
|
3933
|
+
describe('Protocol V2 protected method execution', () => {
|
|
3934
|
+
const deviceLockedError = () =>
|
|
3935
|
+
Object.assign(new Error('Device locked'), {
|
|
3936
|
+
errorCode: HardwareErrorCode.DeviceLocked,
|
|
3937
|
+
});
|
|
3938
|
+
|
|
3939
|
+
test('unlocks and retries an opted-in Protocol V2 method once', async () => {
|
|
3940
|
+
const calls: string[] = [];
|
|
3941
|
+
const method = {
|
|
3942
|
+
unlockPolicy: 'retry-on-locked',
|
|
3943
|
+
run: jest
|
|
3944
|
+
.fn()
|
|
3945
|
+
.mockImplementationOnce(() => {
|
|
3946
|
+
calls.push('run-1');
|
|
3947
|
+
return Promise.reject(deviceLockedError());
|
|
3948
|
+
})
|
|
3949
|
+
.mockImplementationOnce(() => {
|
|
3950
|
+
calls.push('run-2');
|
|
3951
|
+
return Promise.resolve({ message: 'ok' });
|
|
3952
|
+
}),
|
|
3953
|
+
};
|
|
3954
|
+
const device = {
|
|
3955
|
+
isProtocolV2: () => true,
|
|
3956
|
+
unlockDevice: jest.fn(() => {
|
|
3957
|
+
calls.push('unlock');
|
|
3958
|
+
return Promise.resolve();
|
|
3959
|
+
}),
|
|
3960
|
+
};
|
|
3961
|
+
|
|
3962
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
|
|
3963
|
+
message: 'ok',
|
|
3964
|
+
});
|
|
3965
|
+
expect(calls).toEqual(['run-1', 'unlock', 'run-2']);
|
|
3966
|
+
});
|
|
3967
|
+
|
|
3968
|
+
test('does not unlock a Protocol V1 device or a method without the policy', async () => {
|
|
3969
|
+
for (const [isProtocolV2, unlockPolicy] of [
|
|
3970
|
+
[false, 'retry-on-locked'],
|
|
3971
|
+
[true, 'none'],
|
|
3972
|
+
] as const) {
|
|
3973
|
+
const error = deviceLockedError();
|
|
3974
|
+
const method = {
|
|
3975
|
+
unlockPolicy,
|
|
3976
|
+
run: jest.fn().mockRejectedValue(error),
|
|
3977
|
+
};
|
|
3978
|
+
const device = {
|
|
3979
|
+
isProtocolV2: () => isProtocolV2,
|
|
3980
|
+
unlockDevice: jest.fn(),
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
await expect(runMethodWithUnlockRetry(method as any, device as any)).rejects.toBe(error);
|
|
3984
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
3985
|
+
expect(method.run).toHaveBeenCalledTimes(1);
|
|
3986
|
+
}
|
|
3987
|
+
});
|
|
3988
|
+
|
|
3989
|
+
test('does not retry when unlock fails or when the retry is still locked', async () => {
|
|
3990
|
+
const initialError = deviceLockedError();
|
|
3991
|
+
const unlockError = new Error('PIN cancelled');
|
|
3992
|
+
const unlockFailMethod = {
|
|
3993
|
+
unlockPolicy: 'retry-on-locked',
|
|
3994
|
+
run: jest.fn().mockRejectedValue(initialError),
|
|
3995
|
+
};
|
|
3996
|
+
const unlockFailDevice = {
|
|
3997
|
+
isProtocolV2: () => true,
|
|
3998
|
+
unlockDevice: jest.fn().mockRejectedValue(unlockError),
|
|
3999
|
+
};
|
|
4000
|
+
|
|
4001
|
+
await expect(
|
|
4002
|
+
runMethodWithUnlockRetry(unlockFailMethod as any, unlockFailDevice as any)
|
|
4003
|
+
).rejects.toBe(unlockError);
|
|
4004
|
+
expect(unlockFailMethod.run).toHaveBeenCalledTimes(1);
|
|
4005
|
+
|
|
4006
|
+
const retryError = deviceLockedError();
|
|
4007
|
+
const retryFailMethod = {
|
|
4008
|
+
unlockPolicy: 'retry-on-locked',
|
|
4009
|
+
run: jest.fn().mockRejectedValueOnce(initialError).mockRejectedValueOnce(retryError),
|
|
4010
|
+
};
|
|
4011
|
+
const retryFailDevice = {
|
|
4012
|
+
isProtocolV2: () => true,
|
|
4013
|
+
unlockDevice: jest.fn().mockResolvedValue(undefined),
|
|
4014
|
+
};
|
|
4015
|
+
|
|
4016
|
+
await expect(
|
|
4017
|
+
runMethodWithUnlockRetry(retryFailMethod as any, retryFailDevice as any)
|
|
4018
|
+
).rejects.toBe(retryError);
|
|
4019
|
+
expect(retryFailMethod.run).toHaveBeenCalledTimes(2);
|
|
4020
|
+
expect(retryFailDevice.unlockDevice).toHaveBeenCalledTimes(1);
|
|
4021
|
+
});
|
|
4022
|
+
});
|
|
4023
|
+
|
|
3674
4024
|
describe('Protocol V2 current low-level methods', () => {
|
|
3675
4025
|
test('sends ProtocolInfoRequest from protocolInfoRequest', async () => {
|
|
3676
4026
|
const typedCall = jest.fn().mockResolvedValue({ message: { version: 1 } });
|
|
@@ -3724,6 +4074,106 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
3724
4074
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceFactoryInfoGet', 'DeviceFactoryInfo', {});
|
|
3725
4075
|
});
|
|
3726
4076
|
|
|
4077
|
+
test('gets Protocol V2 device settings', async () => {
|
|
4078
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { brightness: 80 } });
|
|
4079
|
+
const method = new DeviceSettingsGet({
|
|
4080
|
+
id: 1,
|
|
4081
|
+
payload: { method: 'deviceSettingsGet' },
|
|
4082
|
+
});
|
|
4083
|
+
method.init();
|
|
4084
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4085
|
+
|
|
4086
|
+
await expect(method.run()).resolves.toEqual({ brightness: 80 });
|
|
4087
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSettingsGet', 'DeviceSettings', {});
|
|
4088
|
+
});
|
|
4089
|
+
|
|
4090
|
+
test('sets Protocol V2 device settings without passphrase fields', async () => {
|
|
4091
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
4092
|
+
const method = new DeviceSettingsSet({
|
|
4093
|
+
id: 1,
|
|
4094
|
+
payload: {
|
|
4095
|
+
method: 'deviceSettingsSet',
|
|
4096
|
+
settings: {
|
|
4097
|
+
label: 'My Pro 2',
|
|
4098
|
+
brightness: 80,
|
|
4099
|
+
airgap_mode: true,
|
|
4100
|
+
passphrase_enable: true,
|
|
4101
|
+
},
|
|
4102
|
+
},
|
|
4103
|
+
});
|
|
4104
|
+
method.init();
|
|
4105
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4106
|
+
|
|
4107
|
+
await method.run();
|
|
4108
|
+
|
|
4109
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSettingsSet', 'Success', {
|
|
4110
|
+
settings: {
|
|
4111
|
+
label: 'My Pro 2',
|
|
4112
|
+
brightness: 80,
|
|
4113
|
+
},
|
|
4114
|
+
});
|
|
4115
|
+
});
|
|
4116
|
+
|
|
4117
|
+
test('opens non-passphrase Protocol V2 settings pages', async () => {
|
|
4118
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
4119
|
+
const method = new DeviceSettingsPageShow({
|
|
4120
|
+
id: 1,
|
|
4121
|
+
payload: {
|
|
4122
|
+
method: 'deviceSettingsPageShow',
|
|
4123
|
+
page: 'DeviceAirgap',
|
|
4124
|
+
fieldName: 'airgap_mode',
|
|
4125
|
+
},
|
|
4126
|
+
});
|
|
4127
|
+
method.init();
|
|
4128
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4129
|
+
|
|
4130
|
+
await method.run();
|
|
4131
|
+
|
|
4132
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4133
|
+
page: 3,
|
|
4134
|
+
field_name: 'airgap_mode',
|
|
4135
|
+
});
|
|
4136
|
+
});
|
|
4137
|
+
|
|
4138
|
+
test('opens the Protocol V2 passphrase settings page', async () => {
|
|
4139
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'ok' } });
|
|
4140
|
+
const method = new DeviceSettingsPageShow({
|
|
4141
|
+
id: 1,
|
|
4142
|
+
payload: {
|
|
4143
|
+
method: 'deviceSettingsPageShow',
|
|
4144
|
+
page: 'DevicePassphrase',
|
|
4145
|
+
},
|
|
4146
|
+
});
|
|
4147
|
+
method.init();
|
|
4148
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4149
|
+
|
|
4150
|
+
await method.run();
|
|
4151
|
+
|
|
4152
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSettingsPageShow', 'Success', {
|
|
4153
|
+
page: 2,
|
|
4154
|
+
field_name: undefined,
|
|
4155
|
+
});
|
|
4156
|
+
});
|
|
4157
|
+
|
|
4158
|
+
test('marks Protocol V2 settings methods for unlock-on-locked retry', () => {
|
|
4159
|
+
const methods = [
|
|
4160
|
+
new DeviceSettingsGet({ id: 1, payload: { method: 'deviceSettingsGet' } }),
|
|
4161
|
+
new DeviceSettingsSet({
|
|
4162
|
+
id: 2,
|
|
4163
|
+
payload: { method: 'deviceSettingsSet', settings: { brightness: 80 } },
|
|
4164
|
+
}),
|
|
4165
|
+
new DeviceSettingsPageShow({
|
|
4166
|
+
id: 3,
|
|
4167
|
+
payload: { method: 'deviceSettingsPageShow', page: 'DevicePassphrase' },
|
|
4168
|
+
}),
|
|
4169
|
+
];
|
|
4170
|
+
|
|
4171
|
+
methods.forEach(method => {
|
|
4172
|
+
method.init();
|
|
4173
|
+
expect(method.unlockPolicy).toBe('retry-on-locked');
|
|
4174
|
+
});
|
|
4175
|
+
});
|
|
4176
|
+
|
|
3727
4177
|
test('sends FilesystemPermissionFix from filesystemPermissionFix', async () => {
|
|
3728
4178
|
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3729
4179
|
const method = new FilesystemPermissionFix({
|