@onekeyfe/hd-core 1.2.8-alpha.13 → 1.2.8-alpha.131
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__/allNetworkGetAddress-privacy-networks.test.ts +78 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +21 -0
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +118 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +292 -0
- 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-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +845 -3
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +141 -0
- package/__tests__/zcash-sign-pczt.test.ts +446 -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/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- 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 +6 -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/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/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- 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 +183 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5631 -4192
- 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/types/api/allNetworkGetAddress.d.ts +5 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +6 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +12 -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 +22 -0
- package/dist/types/api/zcashGetAddress.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/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 +358 -74
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +38 -1
- 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 +7 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +69 -0
- package/src/api/monero/MoneroKeyImageSync.ts +164 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -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 +119 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/core/index.ts +49 -17
- package/src/data/messages/messages-protocol-v2.json +138 -1
- package/src/device/Device.ts +21 -3
- package/src/device/DeviceConnector.ts +4 -4
- package/src/events/logBlockEvent.ts +55 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +13 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/types/api/allNetworkGetAddress.ts +9 -1
- package/src/types/api/export.ts +15 -0
- package/src/types/api/index.ts +20 -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 +35 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/utils/logger.ts +4 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import AllNetworkGetAddress from '../src/api/allnetwork/AllNetworkGetAddress';
|
|
2
|
+
|
|
3
|
+
import type { AllNetworkAddressParams } from '../src/types/api/allNetworkGetAddress';
|
|
4
|
+
|
|
5
|
+
jest.mock('../src/data/config', () => ({
|
|
6
|
+
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
7
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
// Breaks the api index import cycle, as the all-network tests do.
|
|
11
|
+
jest.mock('../src/api/utils', () => ({
|
|
12
|
+
findMethod: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
function mapItem(payload: AllNetworkAddressParams) {
|
|
16
|
+
const method = new AllNetworkGetAddress({
|
|
17
|
+
id: 1,
|
|
18
|
+
payload: { method: 'allNetworkGetAddress', bundle: [payload] },
|
|
19
|
+
});
|
|
20
|
+
return method.generateMethodName({ network: payload.network, payload, originalIndex: 0 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('allNetworkGetAddress privacy networks', () => {
|
|
24
|
+
test.each([
|
|
25
|
+
[undefined, 0],
|
|
26
|
+
['mainnet', 0],
|
|
27
|
+
['testnet', 1],
|
|
28
|
+
['stagenet', 2],
|
|
29
|
+
])('asks moneroGetAddress on the %s network type', (chainName, networkType) => {
|
|
30
|
+
const payload = {
|
|
31
|
+
network: 'xmr' as const,
|
|
32
|
+
path: "m/44'/128'/0'",
|
|
33
|
+
chainName,
|
|
34
|
+
showOnOneKey: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(mapItem(payload)).toEqual(
|
|
38
|
+
expect.objectContaining({
|
|
39
|
+
methodName: 'moneroGetAddress',
|
|
40
|
+
params: {
|
|
41
|
+
path: "m/44'/128'/0'",
|
|
42
|
+
networkType,
|
|
43
|
+
showOnOneKey: false,
|
|
44
|
+
originPayload: payload,
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test.each(['fakechain', 'constructor'])(
|
|
51
|
+
'rejects the unknown Monero chainName %s instead of using mainnet',
|
|
52
|
+
chainName => {
|
|
53
|
+
expect(() =>
|
|
54
|
+
mapItem({ network: 'xmr', path: "m/44'/128'/0'", chainName, showOnOneKey: false })
|
|
55
|
+
).toThrow(`Unsupported Monero chainName: ${chainName}`);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
test('asks zcashGetAddress for the Orchard address with its viewing key', () => {
|
|
60
|
+
const payload = { network: 'zcash' as const, path: "m/32'/133'/0'", showOnOneKey: false };
|
|
61
|
+
|
|
62
|
+
expect(mapItem(payload)).toEqual(
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
methodName: 'zcashGetAddress',
|
|
65
|
+
params: {
|
|
66
|
+
path: "m/32'/133'/0'",
|
|
67
|
+
showOnOneKey: false,
|
|
68
|
+
addressType: 2,
|
|
69
|
+
scope: 0,
|
|
70
|
+
diversifierIndex: 0,
|
|
71
|
+
includeUfvk: true,
|
|
72
|
+
includeSeedFingerprint: true,
|
|
73
|
+
originPayload: payload,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import Core, { init, initConnector, initCore, switchTransport } from '../src/core';
|
|
4
|
+
import DataManager from '../src/data-manager/DataManager';
|
|
2
5
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
3
6
|
import { DevicePool } from '../src/device/DevicePool';
|
|
4
7
|
|
|
@@ -31,4 +34,114 @@ describe('Core.dispose', () => {
|
|
|
31
34
|
expect(transportStop).toHaveBeenCalledTimes(1);
|
|
32
35
|
expect(disposeDevicePool).toHaveBeenCalledTimes(1);
|
|
33
36
|
});
|
|
37
|
+
|
|
38
|
+
test('disposal rejects a queued switch without recreating a transport or connector', async () => {
|
|
39
|
+
const previous = TransportManager.transport;
|
|
40
|
+
const { settings } = DataManager;
|
|
41
|
+
const active = { stop: jest.fn().mockResolvedValue(undefined) };
|
|
42
|
+
const replacement = jest.fn(() => ({ stop: jest.fn().mockResolvedValue(undefined) }));
|
|
43
|
+
TransportManager.transport = active as never;
|
|
44
|
+
const core = initCore();
|
|
45
|
+
initConnector();
|
|
46
|
+
const switching = switchTransport({ env: 'emulator', Transport: replacement });
|
|
47
|
+
const rejected = expect(switching).rejects.toMatchObject({
|
|
48
|
+
errorCode: HardwareErrorCode.NotInitialized,
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
await core.dispose();
|
|
52
|
+
await rejected;
|
|
53
|
+
expect(replacement).not.toHaveBeenCalled();
|
|
54
|
+
expect(TransportManager.transport).toBe(active);
|
|
55
|
+
expect(DataManager.settings).toBe(settings);
|
|
56
|
+
expect(DevicePool.emitter.eventNames()).toHaveLength(0);
|
|
57
|
+
} finally {
|
|
58
|
+
await Promise.allSettled([switching]);
|
|
59
|
+
await core.dispose();
|
|
60
|
+
TransportManager.transport = previous;
|
|
61
|
+
DataManager.settings = settings;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test.each(['dispose/reinit', 'init', 'initCore'] as const)(
|
|
66
|
+
'%s invalidates running and queued switches without blocking a new lifecycle',
|
|
67
|
+
async lifecycle => {
|
|
68
|
+
const previous = TransportManager.transport;
|
|
69
|
+
const { settings } = DataManager;
|
|
70
|
+
const entered = createDeferred<void>();
|
|
71
|
+
const closing = createDeferred<void>();
|
|
72
|
+
const loading = createDeferred<void>();
|
|
73
|
+
const active = {
|
|
74
|
+
stop: jest.fn(() => {
|
|
75
|
+
entered.resolve();
|
|
76
|
+
return closing.promise;
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
const intermediate = jest.fn(() => ({ stop: jest.fn().mockResolvedValue(undefined) }));
|
|
80
|
+
const stale = jest.fn(() => ({ stop: jest.fn().mockResolvedValue(undefined) }));
|
|
81
|
+
const fresh = { stop: jest.fn().mockResolvedValue(undefined) };
|
|
82
|
+
const current = { stop: jest.fn().mockResolvedValue(undefined) };
|
|
83
|
+
const Fresh = jest.fn(() => fresh);
|
|
84
|
+
jest.spyOn(DataManager, 'load').mockImplementation(async next => {
|
|
85
|
+
await loading.promise;
|
|
86
|
+
DataManager.settings = next;
|
|
87
|
+
});
|
|
88
|
+
TransportManager.transport = active as never;
|
|
89
|
+
let core = initCore();
|
|
90
|
+
initConnector();
|
|
91
|
+
const first = switchTransport({ env: 'emulator', Transport: intermediate });
|
|
92
|
+
await entered.promise;
|
|
93
|
+
const second = switchTransport({ env: 'webusb', Transport: stale });
|
|
94
|
+
const rejected = Promise.all([
|
|
95
|
+
expect(first).rejects.toMatchObject({ errorCode: HardwareErrorCode.NotInitialized }),
|
|
96
|
+
expect(second).rejects.toMatchObject({ errorCode: HardwareErrorCode.NotInitialized }),
|
|
97
|
+
]);
|
|
98
|
+
rejected.catch(() => undefined);
|
|
99
|
+
let initializing: ReturnType<typeof init> | undefined;
|
|
100
|
+
try {
|
|
101
|
+
if (lifecycle === 'dispose/reinit') {
|
|
102
|
+
const disposing = core.dispose();
|
|
103
|
+
closing.resolve();
|
|
104
|
+
await disposing;
|
|
105
|
+
}
|
|
106
|
+
if (lifecycle === 'initCore') {
|
|
107
|
+
TransportManager.transport = fresh as never;
|
|
108
|
+
DataManager.settings = { ...settings, env: 'desktop-web-ble' };
|
|
109
|
+
core = initCore();
|
|
110
|
+
initConnector();
|
|
111
|
+
} else {
|
|
112
|
+
initializing = init({ ...settings, env: 'desktop-web-ble' }, Fresh);
|
|
113
|
+
if (lifecycle === 'init') {
|
|
114
|
+
closing.resolve();
|
|
115
|
+
await rejected;
|
|
116
|
+
}
|
|
117
|
+
loading.resolve();
|
|
118
|
+
const initialized = await initializing;
|
|
119
|
+
if (!initialized) throw new Error('Core initialization failed');
|
|
120
|
+
core = initialized;
|
|
121
|
+
}
|
|
122
|
+
await switchTransport({
|
|
123
|
+
env: 'emulator',
|
|
124
|
+
Transport: function Current() {
|
|
125
|
+
return current;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
closing.resolve();
|
|
129
|
+
await rejected;
|
|
130
|
+
expect(intermediate).not.toHaveBeenCalled();
|
|
131
|
+
expect(stale).not.toHaveBeenCalled();
|
|
132
|
+
expect(TransportManager.transport).toBe(current);
|
|
133
|
+
expect(DataManager.getSettings('env')).toBe('emulator');
|
|
134
|
+
expect(fresh.stop).toHaveBeenCalledTimes(1);
|
|
135
|
+
expect(current.stop).not.toHaveBeenCalled();
|
|
136
|
+
} finally {
|
|
137
|
+
closing.resolve();
|
|
138
|
+
loading.resolve();
|
|
139
|
+
await initializing;
|
|
140
|
+
await Promise.allSettled([first, second, rejected]);
|
|
141
|
+
await core.dispose();
|
|
142
|
+
TransportManager.transport = previous;
|
|
143
|
+
DataManager.settings = settings;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
);
|
|
34
147
|
});
|
|
@@ -45,6 +45,23 @@ describe('Core 错误输出边界', () => {
|
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
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
|
+
|
|
48
65
|
test.each([
|
|
49
66
|
['webusb', false],
|
|
50
67
|
['desktop-webusb', false],
|
|
@@ -129,6 +146,8 @@ describe('Core 错误输出边界', () => {
|
|
|
129
146
|
HardwareErrorCode.BleUnsupported,
|
|
130
147
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
131
148
|
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
149
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
150
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
132
151
|
])(
|
|
133
152
|
'preserves terminal transport error %s in the public response without polling again',
|
|
134
153
|
async errorCode => {
|
|
@@ -138,6 +157,8 @@ describe('Core 错误输出边界', () => {
|
|
|
138
157
|
[
|
|
139
158
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
140
159
|
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
160
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
161
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
141
162
|
].includes(errorCode)
|
|
142
163
|
? ('desktop-webusb' as never)
|
|
143
164
|
: ('react-native' 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
|
|
|
@@ -80,6 +80,102 @@ describe('public device lifecycle events', () => {
|
|
|
80
80
|
jest.restoreAllMocks();
|
|
81
81
|
});
|
|
82
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
|
+
|
|
83
179
|
test.each(['V1', 'V2'] as const)(
|
|
84
180
|
'preserves the %s acknowledged result and waits for release before the next acquire',
|
|
85
181
|
async protocol => {
|
|
@@ -610,7 +706,9 @@ describe('public device lifecycle events', () => {
|
|
|
610
706
|
);
|
|
611
707
|
|
|
612
708
|
test.each([
|
|
613
|
-
[HardwareErrorCode.
|
|
709
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, true],
|
|
710
|
+
[HardwareErrorCode.TransportProtocolMismatch, false],
|
|
711
|
+
[HardwareErrorCode.RuntimeError, false],
|
|
614
712
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
615
713
|
] as const)(
|
|
616
714
|
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
@@ -618,8 +716,8 @@ describe('public device lifecycle events', () => {
|
|
|
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,6 +727,8 @@ 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
734
|
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
@@ -641,6 +741,21 @@ describe('public device lifecycle events', () => {
|
|
|
641
741
|
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
642
742
|
});
|
|
643
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
|
+
|
|
644
759
|
test('does not retry the desktop acquire deadline fallback', () => {
|
|
645
760
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
646
761
|
const error = {
|
|
@@ -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
|
|