@onekeyfe/hd-core 1.2.0-alpha.133 → 1.2.0-alpha.134
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 +0 -7
- package/__tests__/device-lifecycle-events.test.ts +2 -91
- package/__tests__/device-settings.test.ts +9 -167
- package/__tests__/device-wallet-session-store.test.ts +79 -21
- package/__tests__/protocol-v2.test.ts +1 -34
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +0 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.js +206 -268
- package/package.json +4 -4
- package/src/api/device/DeviceSettings.ts +16 -2
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +0 -11
- package/src/core/index.ts +25 -67
- package/src/device/Device.ts +42 -42
- package/src/device/DeviceCommands.ts +1 -14
|
@@ -800,13 +800,7 @@ describe('DeviceCommands cancellation', () => {
|
|
|
800
800
|
try {
|
|
801
801
|
const commands = createCommands();
|
|
802
802
|
commands.disposed = false;
|
|
803
|
-
commands.mainId = 'main-id';
|
|
804
803
|
commands.callPromise = new Promise(() => {});
|
|
805
|
-
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
806
|
-
commands.transport = {
|
|
807
|
-
name: 'ReactNativeBleTransport',
|
|
808
|
-
disconnect,
|
|
809
|
-
} as any;
|
|
810
804
|
const dispose = jest.fn().mockResolvedValue(undefined);
|
|
811
805
|
commands.dispose = dispose;
|
|
812
806
|
|
|
@@ -816,7 +810,6 @@ describe('DeviceCommands cancellation', () => {
|
|
|
816
810
|
|
|
817
811
|
await expect(cancellation).resolves.toBeUndefined();
|
|
818
812
|
expect(dispose).toHaveBeenCalledWith(true);
|
|
819
|
-
expect(disconnect).toHaveBeenCalledWith('main-id');
|
|
820
813
|
expect(commands.callPromise).toBeUndefined();
|
|
821
814
|
} finally {
|
|
822
815
|
jest.useRealTimers();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { EDeviceType,
|
|
1
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
|
-
import { initConnector, initCore
|
|
4
|
+
import { initConnector, initCore } from '../src/core';
|
|
5
5
|
import { DataManager } from '../src/data-manager';
|
|
6
6
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
7
7
|
import { Device } from '../src/device/Device';
|
|
@@ -82,23 +82,6 @@ describe('public device lifecycle events', () => {
|
|
|
82
82
|
expect(DevicePool.emitter.listenerCount(DEVICE.DISCONNECT)).toBe(1);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
test('isolates pending cancellation cleanup by connect id', () => {
|
|
86
|
-
core = initCore();
|
|
87
|
-
const context = (core as any).getCoreContext();
|
|
88
|
-
const firstCleanup = createDeferred<void>();
|
|
89
|
-
const replacementCleanup = createDeferred<void>();
|
|
90
|
-
|
|
91
|
-
context.setPrePendingCallPromise('device-a', firstCleanup.promise);
|
|
92
|
-
|
|
93
|
-
expect(context.getPrePendingCallPromise('device-a')).toBe(firstCleanup.promise);
|
|
94
|
-
expect(context.getPrePendingCallPromise('device-b')).toBeUndefined();
|
|
95
|
-
|
|
96
|
-
context.setPrePendingCallPromise('device-a', replacementCleanup.promise);
|
|
97
|
-
context.removePrePendingCallPromise('device-a', firstCleanup.promise);
|
|
98
|
-
|
|
99
|
-
expect(context.getPrePendingCallPromise('device-a')).toBe(replacementCleanup.promise);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
85
|
test('keeps shared device lifecycle listeners across a device cache reset', () => {
|
|
103
86
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
104
87
|
core = initCore();
|
|
@@ -251,22 +234,6 @@ describe('public device lifecycle events', () => {
|
|
|
251
234
|
expect(device.getProtocol()).toBe('V2');
|
|
252
235
|
});
|
|
253
236
|
|
|
254
|
-
test.each([
|
|
255
|
-
['V2', true],
|
|
256
|
-
['V1', false],
|
|
257
|
-
] as const)(
|
|
258
|
-
'retries a missing detected protocol only for an explicit Protocol %s connection',
|
|
259
|
-
(connectProtocol, expected) => {
|
|
260
|
-
const method = { payload: { connectProtocol } } as never;
|
|
261
|
-
const error = {
|
|
262
|
-
errorCode: HardwareErrorCode.RuntimeError,
|
|
263
|
-
message: 'Device protocol has not been detected for ble-id',
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
expect(isMissingDetectedProtocolV2Error(method, error)).toBe(expected);
|
|
267
|
-
}
|
|
268
|
-
);
|
|
269
|
-
|
|
270
237
|
test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
|
|
271
238
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
272
239
|
core = initCore();
|
|
@@ -343,62 +310,6 @@ describe('public device lifecycle events', () => {
|
|
|
343
310
|
}
|
|
344
311
|
);
|
|
345
312
|
|
|
346
|
-
test('sends a fallback Cancel for an acquired Protocol V2 BLE call without a prompt callback', async () => {
|
|
347
|
-
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
348
|
-
const device = createInitializedDevice('V2');
|
|
349
|
-
const post = jest.fn().mockResolvedValue(undefined);
|
|
350
|
-
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
351
|
-
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
352
|
-
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
353
|
-
device.commands = {
|
|
354
|
-
transport: { post },
|
|
355
|
-
cancelDevice,
|
|
356
|
-
cancel,
|
|
357
|
-
} as never;
|
|
358
|
-
|
|
359
|
-
await device.interruptionFromUser();
|
|
360
|
-
|
|
361
|
-
expect(cancelDevice).toHaveBeenCalledTimes(1);
|
|
362
|
-
expect(post).toHaveBeenCalledWith(device.mainId, 'Cancel', {});
|
|
363
|
-
expect(cancel).toHaveBeenCalledTimes(1);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test('waits for the canceled run to finish releasing before cancellation completes', async () => {
|
|
367
|
-
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
368
|
-
const device = createInitializedDevice('V2');
|
|
369
|
-
const operation = createDeferred<void>();
|
|
370
|
-
const releaseGate = createDeferred<void>();
|
|
371
|
-
const cancelError = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
372
|
-
const release = jest.spyOn(device, 'release').mockImplementation(() => releaseGate.promise);
|
|
373
|
-
device.commands = {
|
|
374
|
-
disposed: false,
|
|
375
|
-
cancel: jest.fn(() => {
|
|
376
|
-
operation.reject(cancelError);
|
|
377
|
-
return Promise.resolve();
|
|
378
|
-
}),
|
|
379
|
-
} as never;
|
|
380
|
-
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
381
|
-
|
|
382
|
-
const runResult = device.run(() => operation.promise).catch(error => error);
|
|
383
|
-
const cancellation = device.interruptionFromUser();
|
|
384
|
-
let cancellationCompleted = false;
|
|
385
|
-
cancellation.then(() => {
|
|
386
|
-
cancellationCompleted = true;
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
await new Promise(resolve => {
|
|
390
|
-
setImmediate(resolve);
|
|
391
|
-
});
|
|
392
|
-
expect(release).toHaveBeenCalledTimes(1);
|
|
393
|
-
expect(cancellationCompleted).toBe(false);
|
|
394
|
-
|
|
395
|
-
releaseGate.resolve();
|
|
396
|
-
await cancellation;
|
|
397
|
-
await expect(runResult).resolves.toMatchObject({
|
|
398
|
-
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
402
313
|
test.each([
|
|
403
314
|
[EDeviceType.Pro2, 'webusb', false, DeviceType.PRO2],
|
|
404
315
|
[EDeviceType.Neo, 'webusb', false, DeviceType.NEO],
|
|
@@ -2,9 +2,6 @@ import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
|
2
2
|
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import DeviceSettings from '../src/api/device/DeviceSettings';
|
|
5
|
-
import { Device } from '../src/device/Device';
|
|
6
|
-
import { DEVICE } from '../src/events';
|
|
7
|
-
import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
|
|
8
5
|
import { DEVICE_SETTINGS_NEVER_TIMEOUT_MS } from '../src/utils/deviceSettings';
|
|
9
6
|
|
|
10
7
|
import type { Features } from '../src/types';
|
|
@@ -24,9 +21,6 @@ function createDevice({ protocol }: { protocol: 'V1' | 'V2' }) {
|
|
|
24
21
|
const typedCall = jest.fn().mockResolvedValue({ message: { message: 'Success' } });
|
|
25
22
|
const updateState = jest.fn();
|
|
26
23
|
const getDeviceState = jest.fn();
|
|
27
|
-
const refreshProtocolV2SettingsAfterMutation = jest.fn(() =>
|
|
28
|
-
getDeviceState({ refreshSections: ['status', 'settings'] })
|
|
29
|
-
);
|
|
30
24
|
return {
|
|
31
25
|
device: {
|
|
32
26
|
features,
|
|
@@ -35,7 +29,6 @@ function createDevice({ protocol }: { protocol: 'V1' | 'V2' }) {
|
|
|
35
29
|
commands: { typedCall },
|
|
36
30
|
updateState,
|
|
37
31
|
getDeviceState,
|
|
38
|
-
refreshProtocolV2SettingsAfterMutation,
|
|
39
32
|
},
|
|
40
33
|
typedCall,
|
|
41
34
|
updateState,
|
|
@@ -188,98 +181,7 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
188
181
|
expect(updateState).not.toHaveBeenCalled();
|
|
189
182
|
});
|
|
190
183
|
|
|
191
|
-
it('
|
|
192
|
-
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
193
|
-
if (requestType === 'DeviceSettingsSet') {
|
|
194
|
-
return { message: { message: 'Success' } };
|
|
195
|
-
}
|
|
196
|
-
if (requestType === 'DeviceStatusGet') {
|
|
197
|
-
return {
|
|
198
|
-
message: {
|
|
199
|
-
init_states: true,
|
|
200
|
-
unlocked: false,
|
|
201
|
-
passphrase_enabled: false,
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
if (requestType === 'DeviceSettingsGet') {
|
|
206
|
-
return {
|
|
207
|
-
message: {
|
|
208
|
-
label: 'Current Label',
|
|
209
|
-
brightness: 80,
|
|
210
|
-
passphrase_enable: false,
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
215
|
-
});
|
|
216
|
-
const device = Device.fromDescriptor({
|
|
217
|
-
id: 'pro2',
|
|
218
|
-
path: 'pro2',
|
|
219
|
-
protocolType: 'V2',
|
|
220
|
-
} as never);
|
|
221
|
-
(device as any).commands = { typedCall };
|
|
222
|
-
device.updateState(
|
|
223
|
-
{
|
|
224
|
-
protocol: 'V2',
|
|
225
|
-
identity: {
|
|
226
|
-
deviceType: EDeviceType.Pro2,
|
|
227
|
-
label: 'Stale Label',
|
|
228
|
-
},
|
|
229
|
-
status: {
|
|
230
|
-
mode: 'normal',
|
|
231
|
-
unlocked: false,
|
|
232
|
-
passphraseProtection: true,
|
|
233
|
-
},
|
|
234
|
-
settings: { brightness: 20 },
|
|
235
|
-
raw: {
|
|
236
|
-
protocolV2ProtocolInfo: {
|
|
237
|
-
version: 1,
|
|
238
|
-
build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
|
|
239
|
-
supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
'initialize'
|
|
244
|
-
);
|
|
245
|
-
const onState = jest.fn();
|
|
246
|
-
device.on(DEVICE.STATE, onState);
|
|
247
|
-
const method = new DeviceSettings({
|
|
248
|
-
id: 3,
|
|
249
|
-
payload: {
|
|
250
|
-
method: 'deviceSettings',
|
|
251
|
-
brightness: 80,
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
method.init();
|
|
255
|
-
(method as any).device = device;
|
|
256
|
-
|
|
257
|
-
await expect(method.run()).resolves.toEqual({ message: 'Success' });
|
|
258
|
-
|
|
259
|
-
expect(typedCall.mock.calls.map(call => call[0])).toEqual([
|
|
260
|
-
'DeviceSettingsSet',
|
|
261
|
-
'DeviceStatusGet',
|
|
262
|
-
'DeviceSettingsGet',
|
|
263
|
-
]);
|
|
264
|
-
expect(device.state).toMatchObject({
|
|
265
|
-
identity: { label: 'Current Label' },
|
|
266
|
-
status: { passphraseProtection: false },
|
|
267
|
-
settings: { brightness: 80 },
|
|
268
|
-
});
|
|
269
|
-
expect(onState).toHaveBeenLastCalledWith(
|
|
270
|
-
device,
|
|
271
|
-
expect.objectContaining({
|
|
272
|
-
source: 'settings-read',
|
|
273
|
-
state: expect.objectContaining({
|
|
274
|
-
identity: expect.objectContaining({ label: 'Current Label' }),
|
|
275
|
-
status: expect.objectContaining({ passphraseProtection: false }),
|
|
276
|
-
settings: expect.objectContaining({ brightness: 80 }),
|
|
277
|
-
}),
|
|
278
|
-
})
|
|
279
|
-
);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('uses the Pro2 passphrase page and refreshes device state after the toggle', async () => {
|
|
184
|
+
it('uses the Pro2 passphrase page as a device-side toggle and verifies the target state', async () => {
|
|
283
185
|
const { device, typedCall, getDeviceState } = createDevice({ protocol: 'V2' });
|
|
284
186
|
getDeviceState
|
|
285
187
|
.mockResolvedValueOnce({
|
|
@@ -361,8 +263,8 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
361
263
|
expect(getDeviceState).toHaveBeenCalledWith({ refreshSections: ['settings'] });
|
|
362
264
|
});
|
|
363
265
|
|
|
364
|
-
it('
|
|
365
|
-
const { device, getDeviceState
|
|
266
|
+
it('rejects a successful Pro2 page response when the hardware did not reach the target', async () => {
|
|
267
|
+
const { device, getDeviceState } = createDevice({ protocol: 'V2' });
|
|
366
268
|
getDeviceState.mockResolvedValue({
|
|
367
269
|
status: { passphraseProtection: false },
|
|
368
270
|
});
|
|
@@ -376,12 +278,14 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
376
278
|
method.init();
|
|
377
279
|
(method as any).device = device;
|
|
378
280
|
|
|
379
|
-
await expect(method.run()).
|
|
380
|
-
|
|
281
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
282
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
283
|
+
message: 'Protocol V2 passphrase setting did not reach the requested value.',
|
|
284
|
+
});
|
|
381
285
|
});
|
|
382
286
|
|
|
383
|
-
it('
|
|
384
|
-
const { device, getDeviceState
|
|
287
|
+
it('accepts a locked Pro2 as confirmation after disabling passphrase', async () => {
|
|
288
|
+
const { device, getDeviceState } = createDevice({ protocol: 'V2' });
|
|
385
289
|
getDeviceState
|
|
386
290
|
.mockResolvedValueOnce({
|
|
387
291
|
status: { unlocked: true, passphraseProtection: true },
|
|
@@ -400,68 +304,6 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
400
304
|
(method as any).device = device;
|
|
401
305
|
|
|
402
306
|
await expect(method.run()).resolves.toEqual({ message: 'Success' });
|
|
403
|
-
expect(updateState).not.toHaveBeenCalled();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it('preserves confirmed Pro2 passphrase state when the post-toggle status is locked', async () => {
|
|
407
|
-
let statusReadCount = 0;
|
|
408
|
-
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
409
|
-
if (requestType === 'DeviceStatusGet') {
|
|
410
|
-
statusReadCount += 1;
|
|
411
|
-
return {
|
|
412
|
-
message:
|
|
413
|
-
statusReadCount === 1
|
|
414
|
-
? { init_states: true, unlocked: true, passphrase_enabled: true }
|
|
415
|
-
: { init_states: true, unlocked: false, passphrase_enabled: false },
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
if (requestType === 'DeviceSettingsPageShow') {
|
|
419
|
-
return { message: { message: 'Success' } };
|
|
420
|
-
}
|
|
421
|
-
if (requestType === 'DeviceSettingsGet') {
|
|
422
|
-
return { message: { label: 'Pro 2' } };
|
|
423
|
-
}
|
|
424
|
-
throw new Error(`Unexpected request: ${requestType}`);
|
|
425
|
-
});
|
|
426
|
-
const device = Device.fromDescriptor({
|
|
427
|
-
id: 'pro2-passphrase',
|
|
428
|
-
path: 'pro2-passphrase',
|
|
429
|
-
protocolType: 'V2',
|
|
430
|
-
} as never);
|
|
431
|
-
(device as any).commands = { typedCall };
|
|
432
|
-
device.updateState(
|
|
433
|
-
{
|
|
434
|
-
protocol: 'V2',
|
|
435
|
-
identity: { deviceType: EDeviceType.Pro2 },
|
|
436
|
-
status: {
|
|
437
|
-
mode: 'normal',
|
|
438
|
-
unlocked: true,
|
|
439
|
-
passphraseProtection: true,
|
|
440
|
-
},
|
|
441
|
-
raw: {
|
|
442
|
-
protocolV2ProtocolInfo: {
|
|
443
|
-
version: 1,
|
|
444
|
-
supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
'initialize'
|
|
449
|
-
);
|
|
450
|
-
const method = new DeviceSettings({
|
|
451
|
-
id: 6,
|
|
452
|
-
payload: {
|
|
453
|
-
method: 'deviceSettings',
|
|
454
|
-
usePassphrase: false,
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
method.init();
|
|
458
|
-
(method as any).device = device;
|
|
459
|
-
|
|
460
|
-
await expect(method.run()).resolves.toEqual({ message: 'Success' });
|
|
461
|
-
expect(device.state?.status).toMatchObject({
|
|
462
|
-
unlocked: false,
|
|
463
|
-
passphraseProtection: true,
|
|
464
|
-
});
|
|
465
307
|
});
|
|
466
308
|
|
|
467
309
|
it('keeps Protocol V1 passphrase settings on ApplySettings', async () => {
|
|
@@ -352,7 +352,7 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
352
352
|
jest.restoreAllMocks();
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
test('
|
|
355
|
+
test('purges cached sessions and rejects when the live device identity changes', async () => {
|
|
356
356
|
const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
|
|
357
357
|
device.features = {
|
|
358
358
|
protocol: 'V1',
|
|
@@ -363,7 +363,7 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
363
363
|
deviceWalletSessionStore.set('cached-device-a', 'hidden-a', 'session-a');
|
|
364
364
|
const typedCall = jest.fn().mockResolvedValue({
|
|
365
365
|
type: 'Features',
|
|
366
|
-
message: { device_id: 'live-device-b' },
|
|
366
|
+
message: { device_id: 'live-device-b', session_id: 'wrong-device-session' },
|
|
367
367
|
});
|
|
368
368
|
device.commands = { typedCall } as never;
|
|
369
369
|
jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
@@ -371,11 +371,76 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
371
371
|
await expect(
|
|
372
372
|
device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
|
|
373
373
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
|
|
374
|
+
// Identity is validated from the single Initialize round trip; no separate
|
|
375
|
+
// GetFeatures preflight.
|
|
374
376
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
375
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
377
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
378
|
+
'Initialize',
|
|
379
|
+
'Features',
|
|
380
|
+
expect.objectContaining({ passphrase_state: 'hidden-a' }),
|
|
381
|
+
expect.any(Object)
|
|
382
|
+
);
|
|
383
|
+
// Identity change purges the previous device's cached sessions (pre-existing
|
|
384
|
+
// reconcileDeviceIdentity behavior — never carry sessions across devices)…
|
|
385
|
+
expect(deviceWalletSessionStore.get('cached-device-a', 'hidden-a')).toBeUndefined();
|
|
386
|
+
// …and the session the mismatched Initialize cached under the wrong
|
|
387
|
+
// device's identity is dropped as well.
|
|
388
|
+
expect(deviceWalletSessionStore.get('live-device-b', 'hidden-a')).toBeUndefined();
|
|
376
389
|
});
|
|
377
390
|
|
|
378
|
-
test('
|
|
391
|
+
test('drops the wrong-device session even when reconfigure fails after Initialize', async () => {
|
|
392
|
+
const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
|
|
393
|
+
device.features = {
|
|
394
|
+
protocol: 'V1',
|
|
395
|
+
deviceId: 'cached-device-a',
|
|
396
|
+
unlocked: true,
|
|
397
|
+
passphraseProtection: true,
|
|
398
|
+
} as never;
|
|
399
|
+
deviceWalletSessionStore.set('cached-device-a', 'hidden-a', 'session-a');
|
|
400
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
401
|
+
type: 'Features',
|
|
402
|
+
message: { device_id: 'live-device-b', session_id: 'wrong-device-session' },
|
|
403
|
+
});
|
|
404
|
+
device.commands = { typedCall } as never;
|
|
405
|
+
// Pre-encode resync succeeds; the post-response reconfigure inside
|
|
406
|
+
// callInitialize rejects — the session write has already happened by then.
|
|
407
|
+
jest
|
|
408
|
+
.spyOn(TransportManager, 'reconfigure')
|
|
409
|
+
.mockResolvedValueOnce(undefined)
|
|
410
|
+
.mockRejectedValueOnce(new Error('configure failed'));
|
|
411
|
+
|
|
412
|
+
await expect(
|
|
413
|
+
device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
|
|
414
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
|
|
415
|
+
// The wrong-device session must not survive the error path either.
|
|
416
|
+
expect(deviceWalletSessionStore.get('live-device-b', 'hidden-a')).toBeUndefined();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('re-syncs the V1 message schema for this device before encoding Initialize', async () => {
|
|
420
|
+
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
421
|
+
device.features = {
|
|
422
|
+
protocol: 'V1',
|
|
423
|
+
deviceId: 'device-a',
|
|
424
|
+
unlocked: true,
|
|
425
|
+
passphraseProtection: true,
|
|
426
|
+
} as never;
|
|
427
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
428
|
+
type: 'Features',
|
|
429
|
+
message: { device_id: 'device-a' },
|
|
430
|
+
});
|
|
431
|
+
device.commands = { typedCall } as never;
|
|
432
|
+
const reconfigure = jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
433
|
+
|
|
434
|
+
await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
|
|
435
|
+
|
|
436
|
+
// A stale process-global schema (from another device) would silently strip
|
|
437
|
+
// passphrase_state from the wire message, so the resync must come first.
|
|
438
|
+
expect(reconfigure.mock.invocationCallOrder[0]).toBeLessThan(
|
|
439
|
+
typedCall.mock.invocationCallOrder[0]
|
|
440
|
+
);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('resumes a cached V1 wallet with a single identity-validated Initialize', async () => {
|
|
379
444
|
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
380
445
|
device.features = {
|
|
381
446
|
protocol: 'V1',
|
|
@@ -384,22 +449,17 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
384
449
|
passphraseProtection: true,
|
|
385
450
|
} as never;
|
|
386
451
|
deviceWalletSessionStore.set('device-a', 'hidden-a', 'session-a');
|
|
387
|
-
const typedCall = jest
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
type: 'Features',
|
|
392
|
-
message: { device_id: 'device-a', session_id: 'session-a' },
|
|
393
|
-
});
|
|
452
|
+
const typedCall = jest.fn().mockResolvedValueOnce({
|
|
453
|
+
type: 'Features',
|
|
454
|
+
message: { device_id: 'device-a', session_id: 'session-a' },
|
|
455
|
+
});
|
|
394
456
|
device.commands = { typedCall } as never;
|
|
395
457
|
jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
396
458
|
|
|
397
459
|
await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
|
|
398
460
|
|
|
399
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
400
|
-
expect(typedCall).
|
|
401
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
402
|
-
2,
|
|
461
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
462
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
403
463
|
'Initialize',
|
|
404
464
|
'Features',
|
|
405
465
|
expect.objectContaining({
|
|
@@ -408,9 +468,10 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
408
468
|
}),
|
|
409
469
|
expect.any(Object)
|
|
410
470
|
);
|
|
471
|
+
expect(deviceWalletSessionStore.get('device-a', 'hidden-a')).toBe('session-a');
|
|
411
472
|
});
|
|
412
473
|
|
|
413
|
-
test('selects the standard V1 wallet
|
|
474
|
+
test('selects the standard V1 wallet with a single identity-validated Initialize', async () => {
|
|
414
475
|
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
415
476
|
device.features = {
|
|
416
477
|
protocol: 'V1',
|
|
@@ -420,17 +481,14 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
420
481
|
} as never;
|
|
421
482
|
const typedCall = jest
|
|
422
483
|
.fn()
|
|
423
|
-
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
|
|
424
484
|
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } });
|
|
425
485
|
device.commands = { typedCall } as never;
|
|
426
486
|
jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
427
487
|
|
|
428
488
|
await device.initialize({ deviceId: 'device-a' });
|
|
429
489
|
|
|
430
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
431
|
-
expect(typedCall).
|
|
432
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
433
|
-
2,
|
|
490
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
491
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
434
492
|
'Initialize',
|
|
435
493
|
'Features',
|
|
436
494
|
{
|
|
@@ -183,7 +183,6 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
183
183
|
stubDevice({
|
|
184
184
|
...device,
|
|
185
185
|
ensureProtocolV2RuntimeContext: jest.fn().mockResolvedValue(wallpaperProtocolInfo),
|
|
186
|
-
refreshProtocolV2SettingsAfterMutation: jest.fn().mockResolvedValue({}),
|
|
187
186
|
});
|
|
188
187
|
|
|
189
188
|
test('encodes, uploads and applies a Pro2 wallpaper', async () => {
|
|
@@ -205,8 +204,7 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
205
204
|
jpegBase64: createJpegBase64(604, 1024),
|
|
206
205
|
},
|
|
207
206
|
});
|
|
208
|
-
|
|
209
|
-
(method as any).device = device;
|
|
207
|
+
(method as any).device = stubWallpaperDevice({ commands: { typedCall } });
|
|
210
208
|
method.postMessage = jest.fn();
|
|
211
209
|
|
|
212
210
|
method.init();
|
|
@@ -226,7 +224,6 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
226
224
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSettingsSet', 'Success', {
|
|
227
225
|
settings: { wallpaper_path: result.path },
|
|
228
226
|
});
|
|
229
|
-
expect(device.refreshProtocolV2SettingsAfterMutation).toHaveBeenCalledTimes(1);
|
|
230
227
|
expect(typedCall.mock.calls.some(call => call[0] === 'SetWallpaper')).toBe(false);
|
|
231
228
|
expect(result).toMatchObject({ colorFormat: 'RGB565', message: 'wallpaper applied' });
|
|
232
229
|
const fileWriteCallCount = typedCall.mock.calls.filter(
|
|
@@ -265,36 +262,6 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
265
262
|
expect(typedCall.mock.calls.some(call => call[0] === 'DeviceSettingsSet')).toBe(false);
|
|
266
263
|
});
|
|
267
264
|
|
|
268
|
-
test('returns success when wallpaper read-back fails after apply', async () => {
|
|
269
|
-
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
270
|
-
if (request === 'FilesystemDirMake') return { message: {} };
|
|
271
|
-
if (request === 'FilesystemFileWrite') {
|
|
272
|
-
const file = params.file as { data: Uint8Array; offset: number };
|
|
273
|
-
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
274
|
-
}
|
|
275
|
-
if (request === 'DeviceSettingsSet') {
|
|
276
|
-
return { message: { message: 'wallpaper applied' } };
|
|
277
|
-
}
|
|
278
|
-
throw new Error(`Unexpected request: ${request}`);
|
|
279
|
-
});
|
|
280
|
-
const method = new DeviceUploadWallpaper({
|
|
281
|
-
id: 1,
|
|
282
|
-
payload: {
|
|
283
|
-
method: 'deviceUploadWallpaper',
|
|
284
|
-
jpegBase64: createJpegBase64(604, 1024),
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
const device = stubWallpaperDevice({ commands: { typedCall } });
|
|
288
|
-
device.refreshProtocolV2SettingsAfterMutation.mockRejectedValueOnce(
|
|
289
|
-
new Error('settings read-back failed')
|
|
290
|
-
);
|
|
291
|
-
(method as any).device = device;
|
|
292
|
-
method.init();
|
|
293
|
-
|
|
294
|
-
await expect(method.run()).resolves.toMatchObject({ message: 'wallpaper applied' });
|
|
295
|
-
expect(device.refreshProtocolV2SettingsAfterMutation).toHaveBeenCalledTimes(1);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
265
|
test('rejects unsafe filenames before device communication', () => {
|
|
299
266
|
const method = new DeviceUploadWallpaper({
|
|
300
267
|
id: 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceSettings.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceSettings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAgB3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AA0D5D,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,UAAU,CAAC,aAAa,CAAC;IACnE,qBAAqB;IAIrB,IAAI;IA0EJ,eAAe;;;;;;;IAWT,GAAG;
|
|
1
|
+
{"version":3,"file":"DeviceSettings.d.ts","sourceRoot":"","sources":["../../../src/api/device/DeviceSettings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAgB3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AA0D5D,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,UAAU,CAAC,aAAa,CAAC;IACnE,qBAAqB;IAIrB,IAAI;IA0EJ,eAAe;;;;;;;IAWT,GAAG;CAyIV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAM3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkBF,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,UAAU,CAAC,2BAA2B,CAAC;IACxF,qBAAqB;IAIrB,OAAO,CAAC,OAAO,CAAC,CAA8D;IAE9E,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,IAAI,CAAM;IAElB,IAAI;YAwBU,kBAAkB;YAgBlB,eAAe;YAaf,MAAM;IAwBd,GAAG,IAAI,OAAO,CAAC,6BAA6B,CAAC;CAgBpD"}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -5,10 +5,8 @@ import DeviceConnector from '../device/DeviceConnector';
|
|
|
5
5
|
import type { ConnectSettings } from '../types';
|
|
6
6
|
import type { CoreMessage } from '../events';
|
|
7
7
|
import type { LowlevelTransportSharedPlugin } from '@onekeyfe/hd-transport';
|
|
8
|
-
import type { BaseMethod } from '../api/BaseMethod';
|
|
9
8
|
export type CoreContext = ReturnType<Core['getCoreContext']>;
|
|
10
9
|
export declare const callAPI: (context: CoreContext, message: CoreMessage) => Promise<any>;
|
|
11
|
-
export declare function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unknown): boolean;
|
|
12
10
|
export declare const cancel: (context: CoreContext, connectId?: string) => void;
|
|
13
11
|
export declare const onDeviceButtonHandler: (__0_0: Device, __0_1: import("../events").DeviceButtonRequestPayload) => void;
|
|
14
12
|
export default class Core extends EventEmitter {
|
|
@@ -16,7 +14,7 @@ export default class Core extends EventEmitter {
|
|
|
16
14
|
readonly sdkInstanceId: string;
|
|
17
15
|
private requestQueue;
|
|
18
16
|
private disposePromise?;
|
|
19
|
-
private
|
|
17
|
+
private prePendingCallPromise;
|
|
20
18
|
private methodSynchronize;
|
|
21
19
|
constructor();
|
|
22
20
|
private getCoreContext;
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAoClC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EACV,6BAA6B,EAG9B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAoClC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EACV,6BAA6B,EAG9B,MAAM,wBAAwB,CAAC;AAWhC,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAmE7D,eAAO,MAAM,OAAO,YAAmB,WAAW,WAAW,WAAW,iBAoFvE,CAAC;AA44BF,eAAO,MAAM,MAAM,YAAa,WAAW,cAAc,MAAM,SAkF9D,CAAC;AAmGF,eAAO,MAAM,qBAAqB,gFAejC,CAAC;AAiLF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,YAAY;IAC5C,OAAO,CAAC,cAAc,CAAoB;IAE1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO,CAAC,cAAc,CAAC,CAAgB;IAGvC,OAAO,CAAC,qBAAqB,CAA4B;IAEzD,OAAO,CAAC,iBAAiB,CAAoB;;IAS7C,OAAO,CAAC,cAAc;IAoBhB,aAAa,CAAC,OAAO,EAAE,WAAW;IAuExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOV,gBAAgB;CAiC/B;AAED,eAAO,MAAM,QAAQ,YAIpB,CAAC;AAEF,eAAO,MAAM,aAAa,uBAYzB,CAAC;AAMF,eAAO,MAAM,IAAI,aACL,eAAe,aACd,GAAG,WACL,6BAA6B,8BAiBvC,CAAC;AAEF,eAAO,MAAM,eAAe;SAKrB,eAAe,CAAC,KAAK,CAAC;eAChB,GAAG;;UASf,CAAC"}
|
package/dist/device/Device.d.ts
CHANGED
|
@@ -80,7 +80,6 @@ export declare class Device extends EventEmitter {
|
|
|
80
80
|
get features(): Features | undefined;
|
|
81
81
|
set features(features: Features | undefined);
|
|
82
82
|
runPromise?: Deferred<void> | null;
|
|
83
|
-
private runCleanupPromise?;
|
|
84
83
|
externalState: string[];
|
|
85
84
|
unavailableCapabilities: UnavailableCapabilities;
|
|
86
85
|
instance: number;
|
|
@@ -149,7 +148,6 @@ export declare class Device extends EventEmitter {
|
|
|
149
148
|
private _initializeProtocolV2;
|
|
150
149
|
getFeatures(): Promise<Features | undefined>;
|
|
151
150
|
getDeviceState(params?: DeviceStateReadOptions): Promise<import("../types").DeviceState>;
|
|
152
|
-
refreshProtocolV2SettingsAfterMutation(): Promise<import("../types").DeviceState>;
|
|
153
151
|
_updateFeatures(protoFeatures: PROTO.Features | Features, initSession?: boolean): void;
|
|
154
152
|
updateState(patch: DeviceStatePatch, source: DeviceStateUpdateSource): import("../types").DeviceState & {
|
|
155
153
|
raw?: import("../types").DeviceFeaturesRaw | undefined;
|