@onekeyfe/hd-core 1.2.0-alpha.99 → 1.2.0
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 +203 -27
- package/__tests__/base64Data.test.ts +69 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +48 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +82 -13
- package/__tests__/device-connector-protocol.test.ts +21 -0
- package/__tests__/device-lifecycle-events.test.ts +427 -7
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-settings.test.ts +237 -9
- package/__tests__/device-state-events.test.ts +2 -2
- package/__tests__/device-state-mapper.test.ts +61 -5
- package/__tests__/device-utils.test.ts +15 -1
- package/__tests__/device-wallet-session-store.test.ts +147 -21
- package/__tests__/deviceSettings.test.ts +7 -3
- package/__tests__/deviceUploadNft.test.ts +36 -4
- package/__tests__/firmware-update/device-update-bootloader.test.ts +13 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +63 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +59 -55
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
- package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/open-wallet-session.test.ts +127 -1
- package/__tests__/protocol-v2-firmware-targets.test.ts +23 -0
- package/__tests__/protocol-v2-resources.test.ts +185 -88
- package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
- package/__tests__/protocol-v2.test.ts +2198 -486
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +79 -32
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +19 -6
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/firmware/FirmwarePreparedResourceArchive.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/FirmwareUpdatePlan.d.ts +0 -15
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +3 -2
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +16 -3
- 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/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.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 +54 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2135 -1470
- package/dist/protocols/protocol-v2/features.d.ts +13 -5
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +12 -7
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
- 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/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -31
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.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/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +1 -0
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/BaseMethod.ts +8 -1
- package/src/api/CheckAllFirmwareRelease.ts +2 -4
- package/src/api/CheckBLEFirmwareRelease.ts +2 -2
- package/src/api/CheckBootloaderRelease.ts +3 -2
- package/src/api/CheckFirmwareRelease.ts +2 -2
- package/src/api/FirmwareUpdateV2.ts +12 -9
- package/src/api/FirmwareUpdateV3.ts +29 -19
- package/src/api/FirmwareUpdateV4.ts +918 -741
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/PromptWebDeviceAccess.ts +2 -2
- package/src/api/SearchDevices.ts +6 -3
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
- package/src/api/device/DeviceRebootToBootloader.ts +3 -1
- package/src/api/device/DeviceSettings.ts +34 -20
- package/src/api/device/DeviceVerify.ts +25 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/firmware/FirmwareUpdatePlan.ts +37 -77
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +10 -6
- package/src/api/firmware/protocolV2Release.ts +7 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceReboot.ts +5 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +264 -55
- package/src/data/messages/messages-protocol-v2.json +140 -13
- package/src/data-manager/DataManager.ts +46 -39
- package/src/data-manager/TransportManager.ts +3 -1
- package/src/device/Device.ts +210 -44
- package/src/device/DeviceCommands.ts +27 -18
- package/src/device/DeviceConnector.ts +17 -3
- package/src/device/DevicePool.ts +9 -2
- package/src/device/DeviceStateMapper.ts +30 -24
- package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
- package/src/events/logBlockEvent.ts +14 -1
- package/src/events/ui-request.ts +3 -0
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/features.ts +22 -5
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +110 -147
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
- package/src/protocols/protocol-v2/walletSession.ts +65 -22
- package/src/types/api/firmwareUpdate.ts +1 -1
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/device.ts +2 -1
- package/src/types/settings.ts +6 -34
- package/src/utils/deviceFeaturesCompat.ts +9 -4
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/deviceInfoUtils.ts +9 -2
- package/src/utils/deviceSettings.ts +11 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/pro2Nft.ts +31 -8
- package/__tests__/firmware-memory-host.test.ts +0 -126
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -143
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
-
import { TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
|
-
|
|
4
|
-
import {
|
|
1
|
+
import { EDeviceType, ERRORS, HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { DeviceType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
initConnector,
|
|
6
|
+
initCore,
|
|
7
|
+
isMissingDetectedProtocolV2Error,
|
|
8
|
+
isProtocolV2PeerRemovedPairingError,
|
|
9
|
+
isRetryableBleConnectionError,
|
|
10
|
+
isRetryableBleProtocolV2ProbeError,
|
|
11
|
+
} from '../src/core';
|
|
5
12
|
import { DataManager } from '../src/data-manager';
|
|
6
13
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
7
14
|
import { Device } from '../src/device/Device';
|
|
15
|
+
import { cancelDeviceInPrompt, cancelDeviceWithInitialize } from '../src/device/DeviceCommands';
|
|
8
16
|
import { DevicePool } from '../src/device/DevicePool';
|
|
9
17
|
import { CORE_EVENT, DEVICE, IFRAME } from '../src/events';
|
|
18
|
+
import { PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE } from '../src/protocols/protocol-v2';
|
|
10
19
|
|
|
11
20
|
import type Core from '../src/core';
|
|
12
21
|
import type { CoreMessage } from '../src/events';
|
|
@@ -80,6 +89,23 @@ describe('public device lifecycle events', () => {
|
|
|
80
89
|
expect(DevicePool.emitter.listenerCount(DEVICE.DISCONNECT)).toBe(1);
|
|
81
90
|
});
|
|
82
91
|
|
|
92
|
+
test('isolates pending cancellation cleanup by connect id', () => {
|
|
93
|
+
core = initCore();
|
|
94
|
+
const context = (core as any).getCoreContext();
|
|
95
|
+
const firstCleanup = createDeferred<void>();
|
|
96
|
+
const replacementCleanup = createDeferred<void>();
|
|
97
|
+
|
|
98
|
+
context.setPrePendingCallPromise('device-a', firstCleanup.promise);
|
|
99
|
+
|
|
100
|
+
expect(context.getPrePendingCallPromise('device-a')).toBe(firstCleanup.promise);
|
|
101
|
+
expect(context.getPrePendingCallPromise('device-b')).toBeUndefined();
|
|
102
|
+
|
|
103
|
+
context.setPrePendingCallPromise('device-a', replacementCleanup.promise);
|
|
104
|
+
context.removePrePendingCallPromise('device-a', firstCleanup.promise);
|
|
105
|
+
|
|
106
|
+
expect(context.getPrePendingCallPromise('device-a')).toBe(replacementCleanup.promise);
|
|
107
|
+
});
|
|
108
|
+
|
|
83
109
|
test('keeps shared device lifecycle listeners across a device cache reset', () => {
|
|
84
110
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
85
111
|
core = initCore();
|
|
@@ -109,7 +135,7 @@ describe('public device lifecycle events', () => {
|
|
|
109
135
|
|
|
110
136
|
await device.acquire();
|
|
111
137
|
|
|
112
|
-
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined);
|
|
138
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined, undefined);
|
|
113
139
|
expect(device.getProtocol()).toBe('V1');
|
|
114
140
|
expect(device.originalDescriptor.protocolType).toBe('V1');
|
|
115
141
|
});
|
|
@@ -127,7 +153,9 @@ describe('public device lifecycle events', () => {
|
|
|
127
153
|
|
|
128
154
|
await device.acquire(undefined, { forceProtocolDetection: true });
|
|
129
155
|
|
|
130
|
-
|
|
156
|
+
// Explicit active detection forwards forceProtocolDetection so the
|
|
157
|
+
// transport bypasses its protocol cache.
|
|
158
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, undefined, true);
|
|
131
159
|
expect(device.getProtocol()).toBe('V2');
|
|
132
160
|
expect(device.originalDescriptor.protocolType).toBe('V2');
|
|
133
161
|
});
|
|
@@ -228,10 +256,76 @@ describe('public device lifecycle events', () => {
|
|
|
228
256
|
|
|
229
257
|
await device.acquire('V2');
|
|
230
258
|
|
|
231
|
-
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V2', undefined);
|
|
259
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V2', undefined, undefined);
|
|
232
260
|
expect(device.getProtocol()).toBe('V2');
|
|
233
261
|
});
|
|
234
262
|
|
|
263
|
+
test.each([
|
|
264
|
+
['V2', true],
|
|
265
|
+
['V1', false],
|
|
266
|
+
] as const)(
|
|
267
|
+
'retries a missing detected protocol only for an explicit Protocol %s connection',
|
|
268
|
+
(connectProtocol, expected) => {
|
|
269
|
+
const method = { payload: { connectProtocol } } as never;
|
|
270
|
+
const error = {
|
|
271
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
272
|
+
message: 'Device protocol has not been detected for ble-id',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
expect(isMissingDetectedProtocolV2Error(method, error)).toBe(expected);
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
test.each([
|
|
280
|
+
[HardwareErrorCode.RuntimeError, true],
|
|
281
|
+
[HardwareErrorCode.BleDeviceBondError, false],
|
|
282
|
+
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
283
|
+
] as const)(
|
|
284
|
+
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
285
|
+
(errorCode, expected) => {
|
|
286
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
287
|
+
const error = {
|
|
288
|
+
errorCode,
|
|
289
|
+
message:
|
|
290
|
+
'Device protocol mismatch: expected V2, but device did not respond to expected protocol',
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(expected);
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
test.each([
|
|
298
|
+
[HardwareErrorCode.BleConnectedError, true],
|
|
299
|
+
[HardwareErrorCode.BleTimeoutError, true],
|
|
300
|
+
[HardwareErrorCode.PollingTimeout, false],
|
|
301
|
+
[HardwareErrorCode.BleDeviceBondError, false],
|
|
302
|
+
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
303
|
+
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
304
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
305
|
+
const error = {
|
|
306
|
+
errorCode,
|
|
307
|
+
message: 'BLE setup wedged repeatedly',
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
311
|
+
});
|
|
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
|
+
|
|
235
329
|
test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
|
|
236
330
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
237
331
|
core = initCore();
|
|
@@ -271,6 +365,332 @@ describe('public device lifecycle events', () => {
|
|
|
271
365
|
expect(device.isUsedHere()).toBe(false);
|
|
272
366
|
});
|
|
273
367
|
|
|
368
|
+
test.each([
|
|
369
|
+
['V2', 'Cancel', 'webusb'],
|
|
370
|
+
['V2', 'Cancel', 'desktop-web-ble'],
|
|
371
|
+
['V2', 'Cancel', 'react-native'],
|
|
372
|
+
['V1', 'Initialize', 'webusb'],
|
|
373
|
+
['V1', 'Cancel', 'desktop-web-ble'],
|
|
374
|
+
] as const)(
|
|
375
|
+
'sends Protocol %s %s without treating the acquired %s session as disconnected',
|
|
376
|
+
async (protocol, messageType, env) => {
|
|
377
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
378
|
+
const device = createInitializedDevice(protocol);
|
|
379
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
380
|
+
const call = jest.fn().mockResolvedValue(undefined);
|
|
381
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
382
|
+
device.originalDescriptor.session = device.mainId;
|
|
383
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
384
|
+
(device as any).commands = { transport: { post, call }, cancel };
|
|
385
|
+
device.setCancelableAction(() =>
|
|
386
|
+
messageType === 'Cancel'
|
|
387
|
+
? cancelDeviceInPrompt(device, false)
|
|
388
|
+
: cancelDeviceWithInitialize(device)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
await device.interruptionFromUser();
|
|
392
|
+
|
|
393
|
+
if (messageType === 'Cancel') {
|
|
394
|
+
expect(post).toHaveBeenCalledWith(device.mainId, 'Cancel', {});
|
|
395
|
+
expect(call).not.toHaveBeenCalled();
|
|
396
|
+
} else {
|
|
397
|
+
expect(call).toHaveBeenCalledWith(device.mainId, 'Initialize', {});
|
|
398
|
+
expect(post).not.toHaveBeenCalled();
|
|
399
|
+
}
|
|
400
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
401
|
+
expect(device.hasDeviceAcquire()).toBe(true);
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
test.each(['react-native', 'webusb', 'desktop-webusb'] as const)(
|
|
406
|
+
'sends a fallback Cancel for an acquired Protocol V2 %s call with an open UI',
|
|
407
|
+
async env => {
|
|
408
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
409
|
+
const device = createInitializedDevice('V2');
|
|
410
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
411
|
+
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
412
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
413
|
+
device.originalDescriptor.session = device.mainId;
|
|
414
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
415
|
+
device.commands = {
|
|
416
|
+
transport: { post },
|
|
417
|
+
cancelDevice,
|
|
418
|
+
cancel,
|
|
419
|
+
} as never;
|
|
420
|
+
device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
421
|
+
|
|
422
|
+
await device.interruptionFromUser();
|
|
423
|
+
|
|
424
|
+
expect(cancelDevice).toHaveBeenCalledTimes(1);
|
|
425
|
+
expect(post).toHaveBeenCalledWith(device.mainId, 'Cancel', {});
|
|
426
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
test.each(['react-native', 'webusb', 'desktop-webusb'] as const)(
|
|
431
|
+
'does not send Cancel for an acquired Protocol V2 %s connect without an open UI',
|
|
432
|
+
async env => {
|
|
433
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
434
|
+
const device = createInitializedDevice('V2');
|
|
435
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
436
|
+
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
437
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
438
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
439
|
+
device.originalDescriptor.session = device.mainId;
|
|
440
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
441
|
+
device.deviceConnector = { disconnect } as never;
|
|
442
|
+
device.commands = {
|
|
443
|
+
transport: { post },
|
|
444
|
+
cancelDevice,
|
|
445
|
+
cancel,
|
|
446
|
+
} as never;
|
|
447
|
+
|
|
448
|
+
await device.interruptionFromUser();
|
|
449
|
+
|
|
450
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
451
|
+
expect(post).not.toHaveBeenCalled();
|
|
452
|
+
expect(disconnect).not.toHaveBeenCalled();
|
|
453
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
test('disconnects a BLE link without sending Cancel when the session is not acquired', async () => {
|
|
458
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
459
|
+
const device = createInitializedDevice('V2');
|
|
460
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
461
|
+
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
462
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
463
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
464
|
+
device.deviceConnector = { disconnect } as never;
|
|
465
|
+
device.commands = {
|
|
466
|
+
transport: { post },
|
|
467
|
+
cancelDevice,
|
|
468
|
+
cancel,
|
|
469
|
+
} as never;
|
|
470
|
+
|
|
471
|
+
await device.interruptionFromUser();
|
|
472
|
+
|
|
473
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
474
|
+
expect(post).not.toHaveBeenCalled();
|
|
475
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
476
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
477
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
478
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test('does not finish acquire after the user already cancelled', async () => {
|
|
482
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
483
|
+
const device = createInitializedDevice('V2');
|
|
484
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
485
|
+
let resolveAcquire: ((value: { uuid: string; protocolType: 'V2' }) => void) | undefined;
|
|
486
|
+
const acquire = jest.fn(
|
|
487
|
+
() =>
|
|
488
|
+
new Promise<{ uuid: string; protocolType: 'V2' }>(resolve => {
|
|
489
|
+
resolveAcquire = resolve;
|
|
490
|
+
})
|
|
491
|
+
);
|
|
492
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
493
|
+
|
|
494
|
+
const acquirePromise = device.acquire('V2');
|
|
495
|
+
await device.interruptionFromUser();
|
|
496
|
+
resolveAcquire?.({ uuid: 'late-session', protocolType: 'V2' });
|
|
497
|
+
|
|
498
|
+
await expect(acquirePromise).rejects.toMatchObject({
|
|
499
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
500
|
+
});
|
|
501
|
+
expect(disconnect).toHaveBeenCalledWith('late-session');
|
|
502
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('does not retry a BLE connection error after the user cancelled', () => {
|
|
506
|
+
const device = createInitializedDevice('V2');
|
|
507
|
+
device.beginConnectionAttempt();
|
|
508
|
+
(device as unknown as { interruptedAttempt: number }).interruptedAttempt = (
|
|
509
|
+
device as unknown as { connectionAttempt: number }
|
|
510
|
+
).connectionAttempt;
|
|
511
|
+
const method = { payload: { connectProtocol: 'V2' }, device } as never;
|
|
512
|
+
const error = {
|
|
513
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
514
|
+
message: 'BLE setup wedged repeatedly',
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(false);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test('keeps a user cancel across a later BLE poll iteration', async () => {
|
|
521
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
522
|
+
const device = createInitializedDevice('V2');
|
|
523
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
524
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'poll-session', protocolType: 'V2' });
|
|
525
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
526
|
+
|
|
527
|
+
// First ensureConnected poll iteration.
|
|
528
|
+
device.beginConnectionAttempt();
|
|
529
|
+
await device.interruptionFromUser();
|
|
530
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
531
|
+
|
|
532
|
+
// A later poll must not call beginConnectionAttempt() again.
|
|
533
|
+
await expect(device.acquire('V2')).rejects.toMatchObject({
|
|
534
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
535
|
+
});
|
|
536
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
537
|
+
expect(acquire).not.toHaveBeenCalled();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('allows a new BLE connect after the previous attempt was cancelled', async () => {
|
|
541
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
542
|
+
const device = createInitializedDevice('V2');
|
|
543
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
544
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'next-session', protocolType: 'V2' });
|
|
545
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
546
|
+
|
|
547
|
+
device.beginConnectionAttempt();
|
|
548
|
+
await device.interruptionFromUser();
|
|
549
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
550
|
+
|
|
551
|
+
device.beginConnectionAttempt();
|
|
552
|
+
await expect(device.acquire('V2')).resolves.toBeUndefined();
|
|
553
|
+
|
|
554
|
+
expect(device.wasInterruptedByUser()).toBe(false);
|
|
555
|
+
expect(device.hasDeviceAcquire()).toBe(true);
|
|
556
|
+
expect(acquire).toHaveBeenCalledTimes(1);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('discards a late cancelled acquire after a newer connect attempt starts', async () => {
|
|
560
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
561
|
+
const device = createInitializedDevice('V2');
|
|
562
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
563
|
+
let resolveAcquire: ((value: { uuid: string; protocolType: 'V2' }) => void) | undefined;
|
|
564
|
+
const acquire = jest.fn(
|
|
565
|
+
() =>
|
|
566
|
+
new Promise<{ uuid: string; protocolType: 'V2' }>(resolve => {
|
|
567
|
+
resolveAcquire = resolve;
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
571
|
+
|
|
572
|
+
device.beginConnectionAttempt();
|
|
573
|
+
const cancelledAcquire = device.acquire('V2');
|
|
574
|
+
await device.interruptionFromUser();
|
|
575
|
+
device.beginConnectionAttempt();
|
|
576
|
+
resolveAcquire?.({ uuid: 'stale-session', protocolType: 'V2' });
|
|
577
|
+
|
|
578
|
+
await expect(cancelledAcquire).rejects.toMatchObject({
|
|
579
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
580
|
+
});
|
|
581
|
+
expect(disconnect).toHaveBeenCalledWith('stale-session');
|
|
582
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
583
|
+
expect(device.wasInterruptedByUser()).toBe(false);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test('waits for the canceled run to finish releasing before cancellation completes', async () => {
|
|
587
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
588
|
+
const device = createInitializedDevice('V2');
|
|
589
|
+
const operation = createDeferred<void>();
|
|
590
|
+
const releaseGate = createDeferred<void>();
|
|
591
|
+
const cancelError = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
592
|
+
const release = jest.spyOn(device, 'release').mockImplementation(() => releaseGate.promise);
|
|
593
|
+
device.commands = {
|
|
594
|
+
disposed: false,
|
|
595
|
+
cancel: jest.fn(() => {
|
|
596
|
+
operation.reject(cancelError);
|
|
597
|
+
return Promise.resolve();
|
|
598
|
+
}),
|
|
599
|
+
} as never;
|
|
600
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
601
|
+
|
|
602
|
+
const runResult = device.run(() => operation.promise).catch(error => error);
|
|
603
|
+
const cancellation = device.interruptionFromUser();
|
|
604
|
+
let cancellationCompleted = false;
|
|
605
|
+
cancellation.then(() => {
|
|
606
|
+
cancellationCompleted = true;
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
await new Promise(resolve => {
|
|
610
|
+
setImmediate(resolve);
|
|
611
|
+
});
|
|
612
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
613
|
+
expect(cancellationCompleted).toBe(false);
|
|
614
|
+
|
|
615
|
+
releaseGate.resolve();
|
|
616
|
+
await cancellation;
|
|
617
|
+
await expect(runResult).resolves.toMatchObject({
|
|
618
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test.each([
|
|
623
|
+
[EDeviceType.Pro2, 'webusb', false, DeviceType.PRO2],
|
|
624
|
+
[EDeviceType.Neo, 'webusb', false, DeviceType.NEO],
|
|
625
|
+
[EDeviceType.Pro2, 'desktop-web-ble', true, DeviceType.PRO2],
|
|
626
|
+
[EDeviceType.Neo, 'desktop-web-ble', true, DeviceType.NEO],
|
|
627
|
+
[EDeviceType.Pro2, 'react-native', true, DeviceType.PRO2],
|
|
628
|
+
[EDeviceType.Neo, 'react-native', true, DeviceType.NEO],
|
|
629
|
+
[EDeviceType.Pro2, 'lowlevel', false, DeviceType.PRO2],
|
|
630
|
+
] as const)(
|
|
631
|
+
'refreshes %s runtime state after acquiring a fresh %s session',
|
|
632
|
+
async (deviceType, env, isBle, protocolV2DeviceType) => {
|
|
633
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
634
|
+
const device = createInitializedDevice('V2');
|
|
635
|
+
const staleProtocolInfo = {
|
|
636
|
+
version: 1,
|
|
637
|
+
build_fingerprint: 'bootloader__1.0.0__abcdef0__PROD__RELEASE',
|
|
638
|
+
supported_messages: [],
|
|
639
|
+
};
|
|
640
|
+
const freshProtocolInfo = {
|
|
641
|
+
version: 1,
|
|
642
|
+
build_fingerprint: 'application__5.0.0__abcdef0__PROD__RELEASE',
|
|
643
|
+
supported_messages: [PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE],
|
|
644
|
+
};
|
|
645
|
+
device.updateState(
|
|
646
|
+
{
|
|
647
|
+
identity: { deviceType },
|
|
648
|
+
status: { mode: 'bootloader' },
|
|
649
|
+
raw: { protocolV2ProtocolInfo: staleProtocolInfo },
|
|
650
|
+
},
|
|
651
|
+
'initialize'
|
|
652
|
+
);
|
|
653
|
+
const acquire = jest
|
|
654
|
+
.fn()
|
|
655
|
+
.mockResolvedValue(isBle ? { uuid: 'fresh-session', protocolType: 'V2' } : 'fresh-session');
|
|
656
|
+
device.deviceConnector = { acquire } as never;
|
|
657
|
+
|
|
658
|
+
await device.acquire('V2', { throwOnRunPromiseError: true });
|
|
659
|
+
const typedCall = jest.fn().mockImplementation((requestType: string) => {
|
|
660
|
+
if (requestType === 'DeviceInfoGet') {
|
|
661
|
+
return {
|
|
662
|
+
message: {
|
|
663
|
+
protocol_version: 2,
|
|
664
|
+
hw: { Device_type: protocolV2DeviceType, serial_no: 'SERIAL-001' },
|
|
665
|
+
main_mcu: { application: { version: '5.0.0' } },
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
if (requestType === 'ProtocolInfoRequest') {
|
|
670
|
+
return { message: freshProtocolInfo };
|
|
671
|
+
}
|
|
672
|
+
if (requestType === 'DeviceStatusGet') {
|
|
673
|
+
return {
|
|
674
|
+
message: { init_states: true, unlocked: true, device_id: 'wallet-device-id' },
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
678
|
+
});
|
|
679
|
+
device.commands.typedCall = typedCall as never;
|
|
680
|
+
|
|
681
|
+
await device.initialize();
|
|
682
|
+
|
|
683
|
+
expect(typedCall.mock.calls.map(callArgs => callArgs[0])).toEqual([
|
|
684
|
+
'DeviceInfoGet',
|
|
685
|
+
'ProtocolInfoRequest',
|
|
686
|
+
'DeviceStatusGet',
|
|
687
|
+
]);
|
|
688
|
+
expect(device.state?.status.mode).toBe('normal');
|
|
689
|
+
expect(device.state?.identity.deviceType).toBe(deviceType);
|
|
690
|
+
expect(device.hasDeviceAcquire()).toBe(true);
|
|
691
|
+
}
|
|
692
|
+
);
|
|
693
|
+
|
|
274
694
|
test('exposes the current transport usage state in public device snapshots', () => {
|
|
275
695
|
const getSettings = jest
|
|
276
696
|
.spyOn(DataManager, 'getSettings')
|
|
@@ -38,6 +38,31 @@ describe('DevicePool state lifecycle', () => {
|
|
|
38
38
|
expect(getDeviceState).toHaveBeenNthCalledWith(2, { refreshSections: ['settings'] });
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
test('refreshes Protocol V2 discovery state without legacy-specific options', async () => {
|
|
42
|
+
const getDeviceState = jest.fn().mockResolvedValue({ status: { mode: 'bootloader' } });
|
|
43
|
+
const run = jest.fn(async (callback: () => Promise<void>) => callback());
|
|
44
|
+
const device = {
|
|
45
|
+
isProtocolV2: () => true,
|
|
46
|
+
getDeviceState,
|
|
47
|
+
run,
|
|
48
|
+
} as any;
|
|
49
|
+
|
|
50
|
+
await (DevicePool as any)._refreshRuntimeState(device, {
|
|
51
|
+
refreshRuntimeState: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(run).toHaveBeenCalledWith(expect.any(Function), {
|
|
55
|
+
connectProtocol: undefined,
|
|
56
|
+
forceProtocolDetection: undefined,
|
|
57
|
+
});
|
|
58
|
+
expect(getDeviceState).toHaveBeenNthCalledWith(1, {
|
|
59
|
+
refreshSections: ['status'],
|
|
60
|
+
});
|
|
61
|
+
expect(getDeviceState).toHaveBeenNthCalledWith(2, {
|
|
62
|
+
refreshSections: ['settings'],
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
41
66
|
test('actively re-detects a cached device when protocol detection is forced', async () => {
|
|
42
67
|
const descriptor = { path: 'cached-path', protocolType: 'V1' } as any;
|
|
43
68
|
const device = Device.fromDescriptor(descriptor);
|