@onekeyfe/hd-core 1.2.8 → 1.2.9
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/README.md +27 -0
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +13 -4
- package/__tests__/DeviceCommands.test.ts +91 -13
- package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +77 -0
- package/__tests__/allNetworkGetAddress-skippable.test.ts +49 -0
- package/__tests__/btc-derive-context-hash.test.ts +200 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +55 -3
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +191 -27
- package/__tests__/device-pool-state.test.ts +50 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +331 -9
- package/__tests__/logger.test.ts +18 -0
- package/__tests__/monero-get-address.test.ts +147 -0
- package/__tests__/monero-key-image-sync.test.ts +272 -0
- package/__tests__/monero-sign-transaction.test.ts +436 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
- package/__tests__/protocol-v2-key-export-confirmation.test.ts +75 -0
- package/__tests__/protocol-v2-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +841 -46
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/sol-sign-offchain-message.test.ts +8 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +152 -0
- package/__tests__/zcash-sign-pczt.test.ts +525 -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 +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +3 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
- package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/helpers/pathUtils.d.ts +1 -0
- package/dist/api/helpers/pathUtils.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
- package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
- package/dist/api/monero/helpers.d.ts +5 -0
- package/dist/api/monero/helpers.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
- package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts +12 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/api/zcash/helpers.d.ts +2 -0
- package/dist/api/zcash/helpers.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -0
- 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/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +10 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +206 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4456 -2801
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/types/api/allNetworkGetAddress.d.ts +4 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
- package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
- package/dist/types/api/export.d.ts +8 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +16 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/moneroGetAddress.d.ts +15 -0
- package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
- package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
- package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
- package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
- package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
- package/dist/types/api/moneroSignTransaction.d.ts +32 -0
- package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
- package/dist/types/api/zcashGetAddress.d.ts +20 -0
- package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts +13 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/types/api/zcashSignPczt.d.ts +9 -0
- package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
- package/dist/types/device.d.ts +10 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +2 -0
- package/src/api/FileWrite.ts +7 -1
- package/src/api/FirmwareUpdateV4.ts +366 -99
- package/src/api/GetDeviceState.ts +2 -5
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +55 -15
- package/src/api/btc/BTCDeriveContextHash.ts +90 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
- package/src/api/helpers/pathUtils.ts +23 -0
- package/src/api/helpers/protocolV2FileWrite.ts +382 -111
- package/src/api/index.ts +9 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +75 -0
- package/src/api/monero/MoneroKeyImageSync.ts +170 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -0
- package/src/api/protocol-v2/DeviceReboot.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
- package/src/api/zcash/ZcashGetAddress.ts +108 -0
- package/src/api/zcash/ZcashGetFullViewingKey.ts +78 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/api/zcash/helpers.ts +16 -0
- package/src/core/index.ts +52 -24
- package/src/data/messages/messages-protocol-v2.json +152 -0
- package/src/data/messages/messages.json +51 -0
- package/src/device/Device.ts +32 -4
- package/src/device/DeviceCommands.ts +32 -1
- package/src/device/DeviceConnector.ts +4 -4
- package/src/device/DevicePool.ts +12 -2
- package/src/events/core.ts +2 -2
- package/src/events/logBlockEvent.ts +58 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +17 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/protocols/protocol-v2/uiInteraction.ts +1 -1
- package/src/types/api/allNetworkGetAddress.ts +8 -1
- package/src/types/api/btcDeriveContextHash.ts +15 -0
- package/src/types/api/export.ts +17 -0
- package/src/types/api/index.ts +24 -0
- package/src/types/api/moneroGetAddress.ts +27 -0
- package/src/types/api/moneroGetWatchKey.ts +28 -0
- package/src/types/api/moneroKeyImageSync.ts +45 -0
- package/src/types/api/moneroSignTransaction.ts +54 -0
- package/src/types/api/zcashGetAddress.ts +32 -0
- package/src/types/api/zcashGetFullViewingKey.ts +24 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/types/params.ts +1 -1
- package/src/utils/logger.ts +4 -0
|
@@ -3,7 +3,7 @@ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
|
3
3
|
import { initConnector, initCore } from '../src/core';
|
|
4
4
|
import { DataManager } from '../src/data-manager';
|
|
5
5
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
6
|
-
import { IFRAME } from '../src/events';
|
|
6
|
+
import { IFRAME, createErrorMessage } from '../src/events';
|
|
7
7
|
import SearchDevices from '../src/api/SearchDevices';
|
|
8
8
|
import { DeviceList } from '../src/device/DeviceList';
|
|
9
9
|
import { Device } from '../src/device/Device';
|
|
@@ -28,6 +28,40 @@ describe('Core 错误输出边界', () => {
|
|
|
28
28
|
jest.restoreAllMocks();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
test('entry-point errors preserve USB recovery context across JSON serialization', () => {
|
|
32
|
+
const params = {
|
|
33
|
+
operation: 'open',
|
|
34
|
+
nativeErrorName: 'SecurityError',
|
|
35
|
+
nativeErrorMessage: 'Access denied',
|
|
36
|
+
};
|
|
37
|
+
const error = ERRORS.TypedError(
|
|
38
|
+
HardwareErrorCode.BridgeNeedsPermission,
|
|
39
|
+
'Access denied',
|
|
40
|
+
params
|
|
41
|
+
);
|
|
42
|
+
expect(JSON.parse(JSON.stringify(createErrorMessage(error)))).toEqual({
|
|
43
|
+
success: false,
|
|
44
|
+
payload: { code: HardwareErrorCode.BridgeNeedsPermission, error: 'Access denied', params },
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test.each([
|
|
49
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, { expectedProtocol: 'V1' }],
|
|
50
|
+
[
|
|
51
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
52
|
+
{ expectedProtocol: 'V2', detectedProtocol: 'V1' },
|
|
53
|
+
],
|
|
54
|
+
] as const)(
|
|
55
|
+
'preserves protocol error %s and structured context across the public boundary',
|
|
56
|
+
(code, params) => {
|
|
57
|
+
const error = ERRORS.TypedError(code, undefined, params);
|
|
58
|
+
expect(JSON.parse(JSON.stringify(createErrorMessage(error)))).toEqual({
|
|
59
|
+
success: false,
|
|
60
|
+
payload: { code, error: error.message, params },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
31
65
|
test.each([
|
|
32
66
|
['webusb', false],
|
|
33
67
|
['desktop-webusb', false],
|
|
@@ -108,14 +142,32 @@ describe('Core 错误输出边界', () => {
|
|
|
108
142
|
HardwareErrorCode.BleDeviceBondedCanceled,
|
|
109
143
|
HardwareErrorCode.BleDeviceDisconnected,
|
|
110
144
|
HardwareErrorCode.PollingTimeout,
|
|
145
|
+
HardwareErrorCode.BlePoweredOff,
|
|
146
|
+
HardwareErrorCode.BleUnsupported,
|
|
147
|
+
HardwareErrorCode.BridgeNeedsPermission,
|
|
148
|
+
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
149
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
150
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
111
151
|
])(
|
|
112
|
-
'preserves terminal
|
|
152
|
+
'preserves terminal transport error %s in the public response without polling again',
|
|
113
153
|
async errorCode => {
|
|
114
|
-
jest
|
|
154
|
+
jest
|
|
155
|
+
.spyOn(DataManager, 'getSettings')
|
|
156
|
+
.mockReturnValue(
|
|
157
|
+
[
|
|
158
|
+
HardwareErrorCode.BridgeNeedsPermission,
|
|
159
|
+
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
160
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
161
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
162
|
+
].includes(errorCode)
|
|
163
|
+
? ('desktop-webusb' as never)
|
|
164
|
+
: ('react-native' as never)
|
|
165
|
+
);
|
|
115
166
|
const error = ERRORS.TypedError(errorCode);
|
|
116
167
|
const acquire = jest.fn().mockRejectedValue(error);
|
|
117
168
|
jest.spyOn(TransportManager, 'getTransport').mockReturnValue({
|
|
118
169
|
acquire,
|
|
170
|
+
enumerate: jest.fn().mockResolvedValue([{ path: 'ble-pairing-test' }]),
|
|
119
171
|
release: jest.fn().mockResolvedValue(true),
|
|
120
172
|
stop: jest.fn().mockResolvedValue(undefined),
|
|
121
173
|
} as never);
|
|
@@ -75,8 +75,11 @@ describe('DeviceConnector protocol validation', () => {
|
|
|
75
75
|
});
|
|
76
76
|
const connector = new DeviceConnector();
|
|
77
77
|
|
|
78
|
-
await expect(connector.acquire('classic-id', undefined, undefined, 'V2')).rejects.
|
|
79
|
-
|
|
78
|
+
await expect(connector.acquire('classic-id', undefined, undefined, 'V2')).rejects.toMatchObject(
|
|
79
|
+
{
|
|
80
|
+
errorCode: HardwareErrorCode.TransportProtocolMismatch,
|
|
81
|
+
params: { expectedProtocol: 'V2', detectedProtocol: 'V1' },
|
|
82
|
+
}
|
|
80
83
|
);
|
|
81
84
|
});
|
|
82
85
|
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
isMissingDetectedProtocolV2Error,
|
|
11
11
|
isRetryableBleConnectionError,
|
|
12
12
|
isRetryableBleProtocolV2ProbeError,
|
|
13
|
-
isTerminalBleStaleBondError,
|
|
14
13
|
resolveBleConnectProtocol,
|
|
15
14
|
} from '../src/core';
|
|
16
15
|
import { DataManager } from '../src/data-manager';
|
|
@@ -81,6 +80,102 @@ describe('public device lifecycle events', () => {
|
|
|
81
80
|
jest.restoreAllMocks();
|
|
82
81
|
});
|
|
83
82
|
|
|
83
|
+
test.each([
|
|
84
|
+
['react-native', 'V2', EDeviceType.Pro2, true],
|
|
85
|
+
['react-native', 'V2', EDeviceType.Neo, true],
|
|
86
|
+
['desktop-web-ble', 'V2', EDeviceType.Pro2, true],
|
|
87
|
+
['desktop-web-ble', 'V2', EDeviceType.Neo, true],
|
|
88
|
+
['react-native', 'V1', EDeviceType.Pro, false],
|
|
89
|
+
['desktop-web-ble', 'V1', EDeviceType.Touch, false],
|
|
90
|
+
['react-native', 'V1', EDeviceType.Pro2, false],
|
|
91
|
+
['react-native', 'V2', EDeviceType.Unknown, false],
|
|
92
|
+
['desktop-webusb', 'V2', EDeviceType.Pro2, false],
|
|
93
|
+
['lowlevel', 'V2', EDeviceType.Neo, false],
|
|
94
|
+
] as const)(
|
|
95
|
+
'releases the task and keeps only the intended BLE links: %s / %s / %s',
|
|
96
|
+
async (env, protocol, deviceType, keepConnection) => {
|
|
97
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
98
|
+
const device = createInitializedDevice(protocol);
|
|
99
|
+
device.features = { ...device.features, deviceType } as never;
|
|
100
|
+
device.originalDescriptor.session = device.mainId;
|
|
101
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
102
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
103
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
104
|
+
device.deviceConnector = { release, disconnect } as never;
|
|
105
|
+
const commands = {
|
|
106
|
+
disposed: false,
|
|
107
|
+
dispose: jest.fn(() => {
|
|
108
|
+
commands.disposed = true;
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
}),
|
|
111
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
112
|
+
};
|
|
113
|
+
device.commands = commands as never;
|
|
114
|
+
|
|
115
|
+
await device.release();
|
|
116
|
+
|
|
117
|
+
expect(release).toHaveBeenCalledWith(device.mainId, false, keepConnection);
|
|
118
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
119
|
+
expect(commands.disposed).toBe(true);
|
|
120
|
+
expect(device.keepSession).toBe(false);
|
|
121
|
+
|
|
122
|
+
await device.interruptionFromUser();
|
|
123
|
+
expect(disconnect).toHaveBeenCalledTimes(keepConnection ? 0 : 1);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
test('still disconnects when setup is cancelled after a kept-alive release', async () => {
|
|
128
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
129
|
+
const device = createInitializedDevice('V2');
|
|
130
|
+
const pendingAcquire = createDeferred<{ uuid: string; protocolType: 'V2' }>();
|
|
131
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
132
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
133
|
+
device.deviceConnector = {
|
|
134
|
+
release,
|
|
135
|
+
disconnect,
|
|
136
|
+
acquire: jest.fn(() => pendingAcquire.promise),
|
|
137
|
+
} as never;
|
|
138
|
+
device.commands = {
|
|
139
|
+
disposed: true,
|
|
140
|
+
dispose: jest.fn().mockResolvedValue(undefined),
|
|
141
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
142
|
+
} as never;
|
|
143
|
+
await device.release();
|
|
144
|
+
device.beginConnectionAttempt();
|
|
145
|
+
const acquiring = device.acquire('V2');
|
|
146
|
+
|
|
147
|
+
await device.interruptionFromUser();
|
|
148
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
149
|
+
pendingAcquire.resolve({ uuid: 'late-kept-alive-session', protocolType: 'V2' });
|
|
150
|
+
await expect(acquiring).rejects.toMatchObject({
|
|
151
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
152
|
+
});
|
|
153
|
+
expect(disconnect).toHaveBeenLastCalledWith('late-kept-alive-session');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test.each(['release failure', 'disconnect event'])(
|
|
157
|
+
'does not treat %s as a kept-alive connection',
|
|
158
|
+
async reason => {
|
|
159
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
160
|
+
const device = createInitializedDevice('V2');
|
|
161
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
162
|
+
if (reason === 'release failure') release.mockRejectedValueOnce(new Error('release failed'));
|
|
163
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
164
|
+
device.deviceConnector = { release, disconnect } as never;
|
|
165
|
+
device.commands = {
|
|
166
|
+
disposed: true,
|
|
167
|
+
dispose: jest.fn().mockResolvedValue(undefined),
|
|
168
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
169
|
+
} as never;
|
|
170
|
+
await device.release();
|
|
171
|
+
if (reason === 'disconnect event') device.markTransportDisconnected();
|
|
172
|
+
|
|
173
|
+
await device.interruptionFromUser();
|
|
174
|
+
|
|
175
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
84
179
|
test.each(['V1', 'V2'] as const)(
|
|
85
180
|
'preserves the %s acknowledged result and waits for release before the next acquire',
|
|
86
181
|
async protocol => {
|
|
@@ -364,11 +459,11 @@ describe('public device lifecycle events', () => {
|
|
|
364
459
|
|
|
365
460
|
test('clears the cleanup barrier when its deadline expires', async () => {
|
|
366
461
|
const realSetTimeout = setTimeout;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.
|
|
370
|
-
|
|
371
|
-
|
|
462
|
+
const cleanupDeadline = createDeferred<() => void>();
|
|
463
|
+
jest.spyOn(global, 'setTimeout').mockImplementation((callback, delay, ...args) => {
|
|
464
|
+
if (delay === 5_000) cleanupDeadline.resolve(() => callback(...args));
|
|
465
|
+
return realSetTimeout(callback, delay, ...args);
|
|
466
|
+
});
|
|
372
467
|
{
|
|
373
468
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
374
469
|
const acquire = jest.spyOn(Device.prototype, 'acquire').mockResolvedValue(undefined);
|
|
@@ -382,13 +477,15 @@ describe('public device lifecycle events', () => {
|
|
|
382
477
|
type: IFRAME.CALL,
|
|
383
478
|
payload: { method: 'getDeviceState', connectId: 'draining-device', connectProtocol: 'V2' },
|
|
384
479
|
} as CoreMessage);
|
|
385
|
-
|
|
480
|
+
const expireCleanupDeadline = await cleanupDeadline.promise;
|
|
481
|
+
cancel(context, 'draining-device');
|
|
482
|
+
expireCleanupDeadline();
|
|
386
483
|
await expect(result).resolves.toBeDefined();
|
|
387
|
-
gate.resolve();
|
|
388
484
|
await new Promise(resolve => {
|
|
389
485
|
setImmediate(resolve);
|
|
390
486
|
});
|
|
391
487
|
expect(context.getPrePendingCallPromise('draining-device')).toBeUndefined();
|
|
488
|
+
gate.resolve();
|
|
392
489
|
}
|
|
393
490
|
});
|
|
394
491
|
|
|
@@ -609,17 +706,18 @@ describe('public device lifecycle events', () => {
|
|
|
609
706
|
);
|
|
610
707
|
|
|
611
708
|
test.each([
|
|
612
|
-
[HardwareErrorCode.
|
|
709
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, true],
|
|
710
|
+
[HardwareErrorCode.TransportProtocolMismatch, false],
|
|
711
|
+
[HardwareErrorCode.RuntimeError, false],
|
|
613
712
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
614
|
-
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
615
713
|
] as const)(
|
|
616
714
|
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
617
715
|
(errorCode, expected) => {
|
|
618
716
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
619
717
|
const error = {
|
|
620
718
|
errorCode,
|
|
621
|
-
message:
|
|
622
|
-
|
|
719
|
+
message: 'diagnostic wording is not used for classification',
|
|
720
|
+
params: { expectedProtocol: 'V2' },
|
|
623
721
|
};
|
|
624
722
|
|
|
625
723
|
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(expected);
|
|
@@ -629,10 +727,10 @@ describe('public device lifecycle events', () => {
|
|
|
629
727
|
test.each([
|
|
630
728
|
[HardwareErrorCode.BleConnectedError, true],
|
|
631
729
|
[HardwareErrorCode.BleTimeoutError, true],
|
|
730
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, true],
|
|
731
|
+
[HardwareErrorCode.TransportProtocolMismatch, false],
|
|
632
732
|
[HardwareErrorCode.PollingTimeout, false],
|
|
633
733
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
634
|
-
[HardwareErrorCode.BlePeerRemovedPairingInformation, false],
|
|
635
|
-
[HardwareErrorCode.BleBondInvalid, false],
|
|
636
734
|
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
637
735
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
638
736
|
const error = {
|
|
@@ -643,6 +741,21 @@ describe('public device lifecycle events', () => {
|
|
|
643
741
|
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
644
742
|
});
|
|
645
743
|
|
|
744
|
+
test('does not retry a failed strict V1 probe as a V2 probe or after user cancellation', () => {
|
|
745
|
+
const error = ERRORS.TypedError(HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
746
|
+
expectedProtocol: 'V1',
|
|
747
|
+
});
|
|
748
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
749
|
+
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(false);
|
|
750
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(false);
|
|
751
|
+
expect(
|
|
752
|
+
isRetryableBleConnectionError(
|
|
753
|
+
{ payload: {}, device: { wasInterruptedByUser: () => true } } as never,
|
|
754
|
+
ERRORS.TypedError(HardwareErrorCode.TransportProtocolProbeFailed)
|
|
755
|
+
)
|
|
756
|
+
).toBe(false);
|
|
757
|
+
});
|
|
758
|
+
|
|
646
759
|
test('does not retry the desktop acquire deadline fallback', () => {
|
|
647
760
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
648
761
|
const error = {
|
|
@@ -685,19 +798,6 @@ describe('public device lifecycle events', () => {
|
|
|
685
798
|
}
|
|
686
799
|
);
|
|
687
800
|
|
|
688
|
-
test.each([
|
|
689
|
-
[HardwareErrorCode.BleDeviceBondError, true],
|
|
690
|
-
[HardwareErrorCode.BlePeerRemovedPairingInformation, true],
|
|
691
|
-
[HardwareErrorCode.BleBondInvalid, true],
|
|
692
|
-
[HardwareErrorCode.DeviceNotFound, false],
|
|
693
|
-
] as const)('treats BLE stale-bond error code %s as terminal: %s', (errorCode, expected) => {
|
|
694
|
-
const error = {
|
|
695
|
-
errorCode,
|
|
696
|
-
};
|
|
697
|
-
|
|
698
|
-
expect(isTerminalBleStaleBondError(error)).toBe(expected);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
801
|
test.each([
|
|
702
802
|
[HardwareErrorCode.DeviceCheckDeviceIdError, true],
|
|
703
803
|
[HardwareErrorCode.DeviceNotFound, false],
|
|
@@ -729,6 +829,48 @@ describe('public device lifecycle events', () => {
|
|
|
729
829
|
expect(() => JSON.stringify(disconnectMessage?.payload.device)).not.toThrow();
|
|
730
830
|
});
|
|
731
831
|
|
|
832
|
+
test.each(['V1', 'V2'] as const)(
|
|
833
|
+
'preserves the %s connection identity for a transport disconnect after wipe',
|
|
834
|
+
protocol => {
|
|
835
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
|
|
836
|
+
core = initCore();
|
|
837
|
+
initConnector();
|
|
838
|
+
const messages: CoreMessage[] = [];
|
|
839
|
+
core.on(CORE_EVENT, message => messages.push(message));
|
|
840
|
+
const device = createInitializedDevice(protocol);
|
|
841
|
+
const connectId = device.getConnectId();
|
|
842
|
+
DevicePool.devicesCache[connectId] = device;
|
|
843
|
+
|
|
844
|
+
device.invalidateAfterWipe();
|
|
845
|
+
expect(device.toMessageObject()).toBeNull();
|
|
846
|
+
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
847
|
+
expect(messages.some(message => message.type === DEVICE.CONNECT)).toBe(false);
|
|
848
|
+
DevicePool.emitter.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, { connectId });
|
|
849
|
+
|
|
850
|
+
const disconnects = messages.filter(message => message.type === DEVICE.DISCONNECT);
|
|
851
|
+
expect(disconnects).toHaveLength(1);
|
|
852
|
+
expect(disconnects[0].payload.device).toMatchObject({
|
|
853
|
+
connectId,
|
|
854
|
+
serialNo: 'SERIAL-001',
|
|
855
|
+
deviceId: 'wallet-device-id',
|
|
856
|
+
});
|
|
857
|
+
expect(() => JSON.stringify(disconnects[0].payload.device)).not.toThrow();
|
|
858
|
+
|
|
859
|
+
device.features = {
|
|
860
|
+
...createInitializedDevice(protocol).features,
|
|
861
|
+
deviceId: 'new-device-id',
|
|
862
|
+
initialized: false,
|
|
863
|
+
mode: 'notInitialized',
|
|
864
|
+
} as never;
|
|
865
|
+
DevicePool.emitter.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, { connectId });
|
|
866
|
+
const nextDisconnects = messages.filter(message => message.type === DEVICE.DISCONNECT);
|
|
867
|
+
expect(nextDisconnects[1].payload.device).toMatchObject({
|
|
868
|
+
deviceId: 'new-device-id',
|
|
869
|
+
mode: 'notInitialized',
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
);
|
|
873
|
+
|
|
732
874
|
test('treats an acquired BLE device as reusable until the transport disconnects', async () => {
|
|
733
875
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('desktop-web-ble' as never);
|
|
734
876
|
const device = createInitializedDevice('V2');
|
|
@@ -1014,6 +1156,28 @@ describe('public device lifecycle events', () => {
|
|
|
1014
1156
|
expect(device.wasInterruptedByUser()).toBe(false);
|
|
1015
1157
|
});
|
|
1016
1158
|
|
|
1159
|
+
test.each(['V1', 'V2'] as const)(
|
|
1160
|
+
'retires a failed USB %s session before the next user attempt',
|
|
1161
|
+
async protocol => {
|
|
1162
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('desktop-webusb' as never);
|
|
1163
|
+
const device = createInitializedDevice(protocol);
|
|
1164
|
+
device.originalDescriptor = { ...device.originalDescriptor, session: device.mainId };
|
|
1165
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
1166
|
+
const release = jest.fn().mockResolvedValue(undefined);
|
|
1167
|
+
device.deviceConnector = { disconnect, release } as never;
|
|
1168
|
+
device.commands = { disposed: false, dispose: jest.fn() } as never;
|
|
1169
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
1170
|
+
const error = ERRORS.TypedError(HardwareErrorCode.WebUsbDeviceAccessError);
|
|
1171
|
+
await expect(device.run(() => Promise.reject(error))).rejects.toBe(error);
|
|
1172
|
+
await new Promise(resolve => {
|
|
1173
|
+
setImmediate(resolve);
|
|
1174
|
+
});
|
|
1175
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
1176
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
1177
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
|
|
1017
1181
|
test('waits for the canceled run to finish releasing before cancellation completes', async () => {
|
|
1018
1182
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
1019
1183
|
const device = createInitializedDevice('V2');
|
|
@@ -85,6 +85,56 @@ describe('DevicePool state lifecycle', () => {
|
|
|
85
85
|
expect(result.deviceList).toEqual([device]);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
test.each([
|
|
89
|
+
['V1', undefined],
|
|
90
|
+
['V1', 'wipe-path'],
|
|
91
|
+
['V2', undefined],
|
|
92
|
+
['V2', 'wipe-path'],
|
|
93
|
+
] as const)('rediscovers a wiped %s device with connect id %s', async (protocol, connectId) => {
|
|
94
|
+
const descriptor = { path: 'wipe-path', protocolType: protocol } as never;
|
|
95
|
+
const device = Device.fromDescriptor(descriptor);
|
|
96
|
+
device.features = {
|
|
97
|
+
protocol,
|
|
98
|
+
deviceType: protocol === 'V2' ? 'pro2' : 'classic',
|
|
99
|
+
deviceId: 'old-device-id',
|
|
100
|
+
serialNo: 'wipe-path',
|
|
101
|
+
initialized: true,
|
|
102
|
+
mode: 'normal',
|
|
103
|
+
} as never;
|
|
104
|
+
DevicePool.devicesCache = { 'wipe-path': device };
|
|
105
|
+
device.invalidateAfterWipe();
|
|
106
|
+
expect(device.toMessageObject()).toBeNull();
|
|
107
|
+
const connect = jest.spyOn(device, 'connect').mockResolvedValue(true);
|
|
108
|
+
const initialize = jest.spyOn(device, 'initialize').mockImplementation(() => {
|
|
109
|
+
device.features = {
|
|
110
|
+
protocol,
|
|
111
|
+
deviceType: protocol === 'V2' ? 'pro2' : 'classic',
|
|
112
|
+
deviceId: 'new-device-id',
|
|
113
|
+
serialNo: 'wipe-path',
|
|
114
|
+
initialized: false,
|
|
115
|
+
mode: 'notInitialized',
|
|
116
|
+
} as never;
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
});
|
|
119
|
+
const release = jest.spyOn(device, 'release').mockResolvedValue(undefined);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const result = await DevicePool.getDevices([descriptor], connectId);
|
|
123
|
+
expect(initialize).toHaveBeenCalledTimes(1);
|
|
124
|
+
expect(connect).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(release).toHaveBeenCalledTimes(1);
|
|
126
|
+
expect(result.deviceList[0].toMessageObject()).toMatchObject({
|
|
127
|
+
deviceId: 'new-device-id',
|
|
128
|
+
mode: 'notInitialized',
|
|
129
|
+
features: { initialized: false },
|
|
130
|
+
});
|
|
131
|
+
await DevicePool.getDevices([descriptor], connectId);
|
|
132
|
+
expect(initialize).toHaveBeenCalledTimes(1);
|
|
133
|
+
} finally {
|
|
134
|
+
jest.restoreAllMocks();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
88
138
|
test('keeps a discovered device when its Protocol V2 label cannot be read', async () => {
|
|
89
139
|
const labelError = new Error('settings unavailable');
|
|
90
140
|
const getDeviceState = jest
|
|
@@ -31,6 +31,10 @@ const createReader = (bytes = artifactBytes) => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
describe('FirmwareArtifactSource', () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.restoreAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
34
38
|
it('materializes verified bytes before exposing the source', async () => {
|
|
35
39
|
const backingBytes = Uint8Array.from(artifactBytes);
|
|
36
40
|
const { reader, read, close } = createReader(backingBytes);
|
|
@@ -86,6 +90,10 @@ describe('FirmwareArtifactSource', () => {
|
|
|
86
90
|
sha256: bytesToHex(sha256(bytes)),
|
|
87
91
|
};
|
|
88
92
|
const { reader, read } = createReader(bytes);
|
|
93
|
+
const hasher = sha256.create();
|
|
94
|
+
const update = jest.spyOn(hasher, 'update');
|
|
95
|
+
const digest = jest.spyOn(hasher, 'digest');
|
|
96
|
+
jest.spyOn(sha256, 'create').mockReturnValue(hasher);
|
|
89
97
|
|
|
90
98
|
const source = await openFirmwareByteSource({ artifact: largeArtifact, reader });
|
|
91
99
|
if (!source) {
|
|
@@ -103,6 +111,9 @@ describe('FirmwareArtifactSource', () => {
|
|
|
103
111
|
length: 3,
|
|
104
112
|
},
|
|
105
113
|
]);
|
|
114
|
+
expect(update).toHaveBeenCalledTimes(2);
|
|
115
|
+
expect(update.mock.invocationCallOrder[0]).toBeLessThan(read.mock.invocationCallOrder[1]);
|
|
116
|
+
expect(digest.mock.invocationCallOrder[0]).toBeGreaterThan(update.mock.invocationCallOrder[1]);
|
|
106
117
|
await source.close();
|
|
107
118
|
});
|
|
108
119
|
|