@onekeyfe/hd-core 1.2.2-alpha.9 → 1.2.2
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 +1 -1
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +503 -10
- package/__tests__/DeviceCommands.test.ts +254 -1
- package/__tests__/core-error-output.test.ts +169 -1
- package/__tests__/device-lifecycle-events.test.ts +395 -15
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session-error-response.test.ts +2 -2
- package/__tests__/open-wallet-session.test.ts +8 -358
- package/__tests__/protocol-v2.test.ts +130 -0
- package/__tests__/public-device-state-api.test.ts +2 -7
- package/__tests__/search-devices.test.ts +196 -8
- package/__tests__/sol-sign-offchain-message.test.ts +64 -0
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetFeatures.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +2 -15
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -0
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddress.d.ts +2 -0
- package/dist/api/allnetwork/AllNetworkGetAddress.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +7 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressByLoop.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +1 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +1 -1
- package/dist/data-manager/TransportManager.d.ts +2 -0
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +2 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +5 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +35 -30
- package/dist/index.js +587 -314
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -10
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
- package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
- package/dist/types/params.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/FirmwareUpdateV4.ts +9 -4
- package/src/api/GetFeatures.ts +1 -0
- package/src/api/GetPassphraseState.ts +1 -0
- package/src/api/OpenWalletSession.ts +7 -77
- package/src/api/SearchDevices.ts +121 -27
- package/src/api/UploadPortfolio.ts +5 -4
- package/src/api/allnetwork/AllNetworkGetAddress.ts +79 -45
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +95 -24
- package/src/api/allnetwork/AllNetworkGetAddressByLoop.ts +3 -0
- package/src/api/device/DeviceVerify.ts +8 -0
- package/src/api/solana/SolSignOffchainMessage.ts +44 -4
- package/src/core/RequestQueue.ts +20 -0
- package/src/core/index.ts +159 -52
- package/src/data/messages/messages-protocol-v2.json +49 -33
- package/src/data/messages/messages.json +8 -5
- package/src/data-manager/TransportManager.ts +14 -3
- package/src/device/Device.ts +58 -27
- package/src/device/DeviceCommands.ts +29 -2
- package/src/events/logBlockEvent.ts +6 -75
- package/src/protocols/protocol-v2/walletSession.ts +14 -2
- package/src/types/api/getFeatures.ts +2 -1
- package/src/types/api/getPassphraseState.ts +2 -5
- package/src/types/api/openWalletSession.ts +7 -19
- package/src/types/api/protocolV2.ts +3 -4
- package/src/types/api/solSignOffchainMessage.ts +2 -0
- package/src/types/params.ts +7 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
|
-
import { deviceWalletSessionStore } from '../device/DeviceWalletSessionStore';
|
|
5
4
|
import { getProtocolV2WalletSession } from '../protocols/protocol-v2/walletSession';
|
|
6
5
|
import { getPassphraseStateWithRefreshDeviceInfo } from '../utils/deviceFeaturesUtils';
|
|
7
6
|
import { BaseMethod } from './BaseMethod';
|
|
@@ -13,16 +12,6 @@ import type {
|
|
|
13
12
|
OpenWalletSessionPayload,
|
|
14
13
|
} from '../types/api/openWalletSession';
|
|
15
14
|
|
|
16
|
-
const requiredString = (value: unknown, name: string) => {
|
|
17
|
-
if (value === undefined || value === null) {
|
|
18
|
-
throw invalidParameter(`Missing required parameter: ${name}`);
|
|
19
|
-
}
|
|
20
|
-
if (typeof value !== 'string' || !value.trim()) {
|
|
21
|
-
throw invalidParameter(`Parameter [${name}] must be a non-empty string.`);
|
|
22
|
-
}
|
|
23
|
-
return value.trim();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
15
|
const wasResumed = (session: unknown) =>
|
|
27
16
|
!!session &&
|
|
28
17
|
typeof session === 'object' &&
|
|
@@ -53,34 +42,21 @@ const normalizeParams = (payload: Record<string, unknown>): OpenWalletSessionPar
|
|
|
53
42
|
}
|
|
54
43
|
if (
|
|
55
44
|
payload.mode !== OpenWalletSessionMode.Standard &&
|
|
56
|
-
payload.mode !== OpenWalletSessionMode.SelectHidden
|
|
57
|
-
payload.mode !== OpenWalletSessionMode.ResumeHidden
|
|
45
|
+
payload.mode !== OpenWalletSessionMode.SelectHidden
|
|
58
46
|
) {
|
|
59
|
-
throw invalidParameter(
|
|
60
|
-
'Parameter [mode] must be one of standard, select-hidden, or resume-hidden.'
|
|
61
|
-
);
|
|
47
|
+
throw invalidParameter('Parameter [mode] must be one of standard or select-hidden.');
|
|
62
48
|
}
|
|
63
49
|
if (payload.useEmptyPassphrase !== undefined || payload.initSession !== undefined) {
|
|
64
50
|
throw invalidParameter(
|
|
65
51
|
'Legacy parameters [useEmptyPassphrase] and [initSession] are not supported by openWalletSession.'
|
|
66
52
|
);
|
|
67
53
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (payload.deviceId !== undefined || payload.passphraseState !== undefined) {
|
|
73
|
-
throw invalidParameter(
|
|
74
|
-
'Parameters [deviceId] and [passphraseState] are only allowed with mode [resume-hidden].'
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return { mode: payload.mode };
|
|
54
|
+
if (payload.deviceId !== undefined || payload.passphraseState !== undefined) {
|
|
55
|
+
throw invalidParameter(
|
|
56
|
+
'Parameters [deviceId] and [passphraseState] are not supported by openWalletSession. Pass passphraseState on later address and signing calls.'
|
|
57
|
+
);
|
|
78
58
|
}
|
|
79
|
-
return {
|
|
80
|
-
mode: OpenWalletSessionMode.ResumeHidden,
|
|
81
|
-
deviceId: requiredString(payload.deviceId, 'deviceId'),
|
|
82
|
-
passphraseState: requiredString(payload.passphraseState, 'passphraseState'),
|
|
83
|
-
};
|
|
59
|
+
return { mode: payload.mode };
|
|
84
60
|
};
|
|
85
61
|
|
|
86
62
|
export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParams> {
|
|
@@ -196,52 +172,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
196
172
|
};
|
|
197
173
|
}
|
|
198
174
|
|
|
199
|
-
if (this.params.mode === OpenWalletSessionMode.ResumeHidden) {
|
|
200
|
-
if (isProtocolV2) {
|
|
201
|
-
await ensureProtocolV2WalletStatus();
|
|
202
|
-
const refreshedDeviceId = requireDeviceId();
|
|
203
|
-
if (refreshedDeviceId !== this.params.deviceId) {
|
|
204
|
-
deviceWalletSessionStore.delete(this.params.deviceId, this.params.passphraseState);
|
|
205
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
206
|
-
}
|
|
207
|
-
} else if (requireDeviceId() !== this.params.deviceId) {
|
|
208
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
209
|
-
}
|
|
210
|
-
this.device.passphraseState = this.params.passphraseState;
|
|
211
|
-
const cachedSessionId = deviceWalletSessionStore.get(
|
|
212
|
-
this.params.deviceId,
|
|
213
|
-
this.params.passphraseState
|
|
214
|
-
);
|
|
215
|
-
if (!cachedSessionId && !isProtocolV2) {
|
|
216
|
-
throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
|
|
217
|
-
}
|
|
218
|
-
if (!isProtocolV2) {
|
|
219
|
-
await this.device.initialize({
|
|
220
|
-
deviceId: this.params.deviceId,
|
|
221
|
-
passphraseState: this.params.passphraseState,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
const session = isProtocolV2
|
|
225
|
-
? await getProtocolV2WalletSession(this.device, {
|
|
226
|
-
expectedPassphraseState: this.params.passphraseState,
|
|
227
|
-
})
|
|
228
|
-
: await getPassphraseStateWithRefreshDeviceInfo(this.device, {
|
|
229
|
-
expectPassphraseState: this.params.passphraseState,
|
|
230
|
-
});
|
|
231
|
-
const deviceId = requireDeviceId();
|
|
232
|
-
if (session.passphraseState !== this.params.passphraseState) {
|
|
233
|
-
this.device.clearInternalState();
|
|
234
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
protocol,
|
|
238
|
-
walletType: 'hidden',
|
|
239
|
-
deviceId,
|
|
240
|
-
...requireHiddenWalletResponse(session),
|
|
241
|
-
resumed: wasResumed(session) || (!isProtocolV2 && session.newSession === cachedSessionId),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
175
|
this.device.passphraseState = undefined;
|
|
246
176
|
const walletStatus = await ensureProtocolV2WalletStatus();
|
|
247
177
|
if (isProtocolV2 && walletStatus.status.passphraseProtection !== true) {
|
package/src/api/SearchDevices.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalizePro2BleAdvertisementName } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, canonicalizePro2BleAdvertisementName } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import { BaseMethod } from './BaseMethod';
|
|
4
4
|
import TransportManager from '../data-manager/TransportManager';
|
|
@@ -6,10 +6,80 @@ import { DataManager } from '../data-manager';
|
|
|
6
6
|
import { LoggerNames, getDeviceTypeByBleName, getLogger } from '../utils';
|
|
7
7
|
import { DevicePool } from '../device/DevicePool';
|
|
8
8
|
|
|
9
|
+
import type { SearchDevice } from '../types/device';
|
|
9
10
|
import type DeviceConnector from '../device/DeviceConnector';
|
|
11
|
+
import type { OneKeyDeviceInfo as DeviceDescriptor } from '@onekeyfe/hd-transport';
|
|
12
|
+
import type { Device } from '../device/Device';
|
|
10
13
|
|
|
11
14
|
const Log = getLogger(LoggerNames.DevicePool);
|
|
12
15
|
|
|
16
|
+
type RequestQueueLookup = {
|
|
17
|
+
getRequestTasksIdByConnectId: (connectId: string) => number[];
|
|
18
|
+
getTask: (requestId: number) => { method: { device?: Device } } | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getDescriptorKeys = (descriptor: DeviceDescriptor) =>
|
|
22
|
+
[descriptor.path, descriptor.id].filter(
|
|
23
|
+
(key): key is string => typeof key === 'string' && key.length > 0
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const isOwnedByActiveWebUsbRequest = (
|
|
27
|
+
descriptor: DeviceDescriptor,
|
|
28
|
+
requestQueue?: RequestQueueLookup
|
|
29
|
+
) => {
|
|
30
|
+
if (!requestQueue) return false;
|
|
31
|
+
return getDescriptorKeys(descriptor).some(
|
|
32
|
+
key => requestQueue.getRequestTasksIdByConnectId(key).length > 0
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getUsbHandle = (descriptor?: DeviceDescriptor) =>
|
|
37
|
+
(descriptor as { device?: unknown } | undefined)?.device;
|
|
38
|
+
|
|
39
|
+
const getOwningRequestDevice = (
|
|
40
|
+
descriptor: DeviceDescriptor,
|
|
41
|
+
requestQueue?: RequestQueueLookup
|
|
42
|
+
) => {
|
|
43
|
+
if (!requestQueue) return undefined;
|
|
44
|
+
const usbHandle = getUsbHandle(descriptor);
|
|
45
|
+
// A path alone can be shared (the all-zero bootloader path, a synthesized serial-less
|
|
46
|
+
// path), so the owner's identity is only reported for the USB handle it actually bound.
|
|
47
|
+
if (usbHandle === undefined) return undefined;
|
|
48
|
+
for (const key of getDescriptorKeys(descriptor)) {
|
|
49
|
+
for (const requestId of requestQueue.getRequestTasksIdByConnectId(key)) {
|
|
50
|
+
const device = requestQueue.getTask(requestId)?.method.device;
|
|
51
|
+
if (
|
|
52
|
+
device?.features &&
|
|
53
|
+
(device.mainId === key || device.originalDescriptor?.path === key) &&
|
|
54
|
+
getUsbHandle(device.originalDescriptor) === usbHandle
|
|
55
|
+
) {
|
|
56
|
+
return device;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// A USB path is a routing key (the USB serial, a synthesized usb-vid-pid-name, or the
|
|
64
|
+
// bootloader placeholder), not a hardware identity, so it never fills uuid/serialNo.
|
|
65
|
+
const toSearchDeviceFromDescriptor = (descriptor: DeviceDescriptor): SearchDevice => {
|
|
66
|
+
const connectId = descriptor.path || descriptor.id || null;
|
|
67
|
+
return {
|
|
68
|
+
connectId,
|
|
69
|
+
uuid: '',
|
|
70
|
+
serialNo: null,
|
|
71
|
+
deviceId: null,
|
|
72
|
+
deviceType: EDeviceType.Unknown,
|
|
73
|
+
name: descriptor.name || connectId || '',
|
|
74
|
+
commType: descriptor.commType,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const toSearchDevice = (device: Device): SearchDevice | null => {
|
|
79
|
+
const message = device.toMessageObject();
|
|
80
|
+
return message ? (message as SearchDevice) : null;
|
|
81
|
+
};
|
|
82
|
+
|
|
13
83
|
export default class SearchDevices extends BaseMethod {
|
|
14
84
|
connector?: DeviceConnector;
|
|
15
85
|
|
|
@@ -19,19 +89,31 @@ export default class SearchDevices extends BaseMethod {
|
|
|
19
89
|
this.skipForceUpdateCheck = true;
|
|
20
90
|
}
|
|
21
91
|
|
|
22
|
-
async run() {
|
|
23
|
-
|
|
92
|
+
async run(): Promise<SearchDevice[]> {
|
|
93
|
+
const env = DataManager.getSettings('env');
|
|
94
|
+
const isWebUsb = env === 'webusb' || env === 'desktop-webusb';
|
|
95
|
+
const requestQueue = this.context?.requestQueue;
|
|
96
|
+
const hasActiveWebUsbRequest = isWebUsb && (requestQueue?.getRequestTasksId().length ?? 0) > 0;
|
|
97
|
+
// Bring up WebUSB even when schema configuration is deferred while a
|
|
98
|
+
// business request owns the discovery lock. Failures here used to be
|
|
99
|
+
// swallowed by configure(); keep searchDevices resolving empty.
|
|
100
|
+
if (isWebUsb) {
|
|
101
|
+
try {
|
|
102
|
+
await TransportManager.ensureInitialized();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
Log.debug('WebUSB bring-up unavailable', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!hasActiveWebUsbRequest) await TransportManager.configure();
|
|
24
108
|
const deviceDiff = await this.connector?.enumerate();
|
|
25
109
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
26
110
|
|
|
27
|
-
const env = DataManager.getSettings('env');
|
|
28
|
-
|
|
29
111
|
/**
|
|
30
112
|
* No need to call features during Bluetooth scaning
|
|
31
113
|
* to avoid device pairing
|
|
32
114
|
*/
|
|
33
115
|
if (DataManager.isBleConnect(env)) {
|
|
34
|
-
const devices = [];
|
|
116
|
+
const devices: SearchDevice[] = [];
|
|
35
117
|
const seenIds = new Set<string>();
|
|
36
118
|
|
|
37
119
|
for (const device of devicesDescriptor) {
|
|
@@ -56,30 +138,42 @@ export default class SearchDevices extends BaseMethod {
|
|
|
56
138
|
return devices;
|
|
57
139
|
}
|
|
58
140
|
|
|
59
|
-
const deviceList = [];
|
|
141
|
+
const deviceList: SearchDevice[] = [];
|
|
60
142
|
for (const descriptor of devicesDescriptor) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
143
|
+
if (hasActiveWebUsbRequest && isOwnedByActiveWebUsbRequest(descriptor, requestQueue)) {
|
|
144
|
+
const cached = DevicePool.getDeviceByPath(descriptor.path);
|
|
145
|
+
const known = cached?.features ? cached : getOwningRequestDevice(descriptor, requestQueue);
|
|
146
|
+
const message = known ? toSearchDevice(known) : null;
|
|
147
|
+
// Do not probe a path an in-flight request already owns. A cache miss is still a
|
|
148
|
+
// connected device: report the owning request's device, or the path with no identity.
|
|
149
|
+
deviceList.push(message ?? toSearchDeviceFromDescriptor(descriptor));
|
|
150
|
+
} else {
|
|
151
|
+
try {
|
|
152
|
+
// Discovery is best effort. Browsers may retain WebUSB grants for devices that
|
|
153
|
+
// are offline, busy, or not ready, so one descriptor must not abort the scan.
|
|
154
|
+
const result = await DevicePool.getDevices([descriptor], descriptor.path, {
|
|
155
|
+
// Discovery must actively identify the protocol instead of trusting a caller hint.
|
|
156
|
+
connectProtocol: undefined,
|
|
157
|
+
forceProtocolDetection: true,
|
|
158
|
+
refreshRuntimeState: true,
|
|
159
|
+
});
|
|
160
|
+
for (const device of result.deviceList) {
|
|
161
|
+
const message = toSearchDevice(device);
|
|
162
|
+
if (message) deviceList.push(message);
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const errorCode =
|
|
166
|
+
error && typeof error === 'object' && 'errorCode' in error
|
|
167
|
+
? (error as { errorCode?: unknown }).errorCode
|
|
168
|
+
: undefined;
|
|
169
|
+
Log.debug('Skip unavailable device during search', {
|
|
170
|
+
path: descriptor.path,
|
|
171
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
80
174
|
}
|
|
81
175
|
}
|
|
82
176
|
|
|
83
|
-
return deviceList
|
|
177
|
+
return deviceList;
|
|
84
178
|
}
|
|
85
179
|
}
|
|
@@ -7,6 +7,8 @@ import FileWrite from './FileWrite';
|
|
|
7
7
|
export type UploadPortfolioParams = {
|
|
8
8
|
packageBase64: string;
|
|
9
9
|
timeoutMs?: number | string;
|
|
10
|
+
/** Controls transfer progress UI events. Defaults to `silent`. */
|
|
11
|
+
uiMode?: 'silent' | 'progress';
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.okpkg.pending';
|
|
@@ -17,7 +19,7 @@ const PORTFOLIO_UPDATE_MESSAGE_TYPE = 61400;
|
|
|
17
19
|
|
|
18
20
|
export default class UploadPortfolio extends FileWrite {
|
|
19
21
|
init() {
|
|
20
|
-
const { packageBase64, timeoutMs } = this.payload as UploadPortfolioParams;
|
|
22
|
+
const { packageBase64, timeoutMs, uiMode = 'silent' } = this.payload as UploadPortfolioParams;
|
|
21
23
|
const packageBytes = decodeCanonicalBase64({
|
|
22
24
|
value: packageBase64,
|
|
23
25
|
parameterName: 'packageBase64',
|
|
@@ -31,13 +33,12 @@ export default class UploadPortfolio extends FileWrite {
|
|
|
31
33
|
chunkSize: PORTFOLIO_CHUNK_SIZE,
|
|
32
34
|
overwrite: true,
|
|
33
35
|
append: false,
|
|
34
|
-
emitProgress:
|
|
36
|
+
emitProgress: uiMode === 'progress',
|
|
35
37
|
timeoutMs,
|
|
36
38
|
};
|
|
37
39
|
super.init();
|
|
38
40
|
this.unlockPolicy = 'none';
|
|
39
|
-
|
|
40
|
-
this.protocolV2UiMode = 'none';
|
|
41
|
+
this.protocolV2UiMode = uiMode === 'progress' ? 'auto' : 'none';
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
async run() {
|
|
@@ -7,21 +7,74 @@ import AllNetworkGetAddressBase from './AllNetworkGetAddressBase';
|
|
|
7
7
|
import type { CoreApi } from '../../types';
|
|
8
8
|
import type {
|
|
9
9
|
AllNetworkAddress,
|
|
10
|
-
AllNetworkAddressParams,
|
|
11
10
|
AllNetworkGetAddressParams,
|
|
12
11
|
} from '../../types/api/allNetworkGetAddress';
|
|
13
12
|
|
|
14
|
-
type MethodParams =
|
|
15
|
-
methodName: keyof CoreApi;
|
|
16
|
-
params: Parameters<CoreApi[keyof CoreApi]>[0];
|
|
17
|
-
_originRequestParams: AllNetworkAddressParams;
|
|
18
|
-
_originalIndex: number;
|
|
19
|
-
};
|
|
13
|
+
type MethodParams = ReturnType<AllNetworkGetAddressBase['generateMethodName']>;
|
|
20
14
|
|
|
21
15
|
export default class AllNetworkGetAddress extends AllNetworkGetAddressBase {
|
|
16
|
+
private checkAborted() {
|
|
17
|
+
if (this.abortController?.signal.aborted) {
|
|
18
|
+
throw new Error(HardwareErrorCodeMessage[HardwareErrorCode.RepeatUnlocking]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async callAddressGroup(
|
|
23
|
+
methodName: keyof CoreApi,
|
|
24
|
+
params: MethodParams[],
|
|
25
|
+
rootFingerprint: number
|
|
26
|
+
): Promise<AllNetworkAddress[]> {
|
|
27
|
+
const methodCallParams = { bundle: params.map(param => ({ ...param.params })) };
|
|
28
|
+
if (!this.device.isProtocolV2() || params.length === 1) {
|
|
29
|
+
return this.callMethod(methodName, methodCallParams, rootFingerprint);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const postedAddressCounts = new Map<string, number>();
|
|
33
|
+
let runningIndividually = false;
|
|
34
|
+
const postMessage: typeof this.postMessage = message => {
|
|
35
|
+
if (message.type === UI_REQUEST.PREVIOUS_ADDRESS_RESULT) {
|
|
36
|
+
const { path, address } = message.payload.data;
|
|
37
|
+
const key = JSON.stringify([path, address]);
|
|
38
|
+
const count = postedAddressCounts.get(key) ?? 0;
|
|
39
|
+
if (runningIndividually && count > 0) {
|
|
40
|
+
postedAddressCounts.set(key, count - 1);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!runningIndividually) postedAddressCounts.set(key, count + 1);
|
|
44
|
+
}
|
|
45
|
+
this.postMessage(message);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Only silent reads may be replayed. Forward their notifications immediately,
|
|
49
|
+
// then suppress matching retry copies by count so repeated inputs still emit.
|
|
50
|
+
if (params.every(param => param._originRequestParams.showOnOneKey === false)) {
|
|
51
|
+
const response = await this.callMethod(
|
|
52
|
+
methodName,
|
|
53
|
+
methodCallParams,
|
|
54
|
+
rootFingerprint,
|
|
55
|
+
postMessage
|
|
56
|
+
);
|
|
57
|
+
// Skippable errors become failed items; link, cancellation and wallet errors throw.
|
|
58
|
+
if (response.some(item => item.success)) return response;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
runningIndividually = true;
|
|
62
|
+
const responses: AllNetworkAddress[] = [];
|
|
63
|
+
for (const param of params) {
|
|
64
|
+
this.checkAborted();
|
|
65
|
+
const response = await this.callMethod(
|
|
66
|
+
methodName,
|
|
67
|
+
{ bundle: [{ ...param.params }] },
|
|
68
|
+
rootFingerprint,
|
|
69
|
+
postMessage
|
|
70
|
+
);
|
|
71
|
+
responses.push(...response);
|
|
72
|
+
}
|
|
73
|
+
return responses;
|
|
74
|
+
}
|
|
75
|
+
|
|
22
76
|
async getAllNetworkAddress(rootFingerprint: number) {
|
|
23
77
|
const responses: AllNetworkAddress[] = [];
|
|
24
|
-
const resultMap: Record<string, AllNetworkAddress> = {};
|
|
25
78
|
const { bundle } = this.payload as AllNetworkGetAddressParams;
|
|
26
79
|
|
|
27
80
|
const methodParams = bundle.map((param, index) =>
|
|
@@ -31,58 +84,39 @@ export default class AllNetworkGetAddress extends AllNetworkGetAddressBase {
|
|
|
31
84
|
originalIndex: index,
|
|
32
85
|
})
|
|
33
86
|
);
|
|
34
|
-
|
|
87
|
+
// Protocol V2 DeviceSessionGet is the Initialize(session_id) equivalent: the
|
|
88
|
+
// SE wallet stays selected until the next Ask/Get or lock. Nested chain
|
|
89
|
+
// methods still resume once in callMethod; same-method addresses can share
|
|
90
|
+
// that session the way Protocol V1 bundles do.
|
|
91
|
+
const methodGroups = methodParams.reduce((groups, param) => {
|
|
35
92
|
const group = groups.get(param.methodName) ?? [];
|
|
36
93
|
group.push(param);
|
|
37
94
|
groups.set(param.methodName, group);
|
|
38
95
|
return groups;
|
|
39
96
|
}, new Map<keyof CoreApi, MethodParams[]>());
|
|
40
|
-
const requiresProtocolV2WalletHandoff =
|
|
41
|
-
this.device.isProtocolV2() &&
|
|
42
|
-
(this.payload.useEmptyPassphrase === true || !!this.payload.passphraseState);
|
|
43
|
-
const methodGroups: [keyof CoreApi, MethodParams[]][] = requiresProtocolV2WalletHandoff
|
|
44
|
-
? methodParams.map(param => [param.methodName, [param]])
|
|
45
|
-
: Array.from(groupedMethodParams.entries());
|
|
46
97
|
|
|
47
|
-
let
|
|
48
|
-
for (const [methodName, params] of methodGroups) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (this.abortController?.signal.aborted) {
|
|
56
|
-
throw new Error(HardwareErrorCodeMessage[HardwareErrorCode.RepeatUnlocking]);
|
|
57
|
-
}
|
|
58
|
-
// call method
|
|
59
|
-
const response = await this.callMethod(methodName, methodParams, rootFingerprint);
|
|
98
|
+
let processed = 0;
|
|
99
|
+
for (const [methodName, params] of methodGroups.entries()) {
|
|
100
|
+
this.checkAborted();
|
|
101
|
+
const response = await this.callAddressGroup(methodName, params, rootFingerprint);
|
|
102
|
+
this.checkAborted();
|
|
60
103
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < params.length; i++) {
|
|
66
|
-
const { _originRequestParams, _originalIndex } = params[i];
|
|
67
|
-
const responseKey = `${_originalIndex}`;
|
|
68
|
-
resultMap[responseKey] = {
|
|
104
|
+
for (let index = 0; index < params.length; index++) {
|
|
105
|
+
const { _originRequestParams, _originalIndex } = params[index];
|
|
106
|
+
responses[_originalIndex] = {
|
|
69
107
|
..._originRequestParams,
|
|
70
|
-
...response[
|
|
108
|
+
...response[index],
|
|
71
109
|
};
|
|
72
110
|
}
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
processed += params.length;
|
|
113
|
+
if (bundle.length > 1) {
|
|
114
|
+
const progress = Math.round((processed / bundle.length) * 100);
|
|
76
115
|
this.postMessage(createUiMessage(UI_REQUEST.DEVICE_PROGRESS, { progress }));
|
|
77
116
|
}
|
|
78
|
-
i++;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (let i = 0; i < bundle.length; i++) {
|
|
82
|
-
responses.push(resultMap[i]);
|
|
83
117
|
}
|
|
84
118
|
|
|
85
119
|
this.abortController = null;
|
|
86
|
-
return
|
|
120
|
+
return responses;
|
|
87
121
|
}
|
|
88
122
|
}
|