@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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +503 -10
  3. package/__tests__/DeviceCommands.test.ts +254 -1
  4. package/__tests__/core-error-output.test.ts +169 -1
  5. package/__tests__/device-lifecycle-events.test.ts +395 -15
  6. package/__tests__/logBlockEvent.test.ts +45 -122
  7. package/__tests__/open-wallet-session-error-response.test.ts +2 -2
  8. package/__tests__/open-wallet-session.test.ts +8 -358
  9. package/__tests__/protocol-v2.test.ts +130 -0
  10. package/__tests__/public-device-state-api.test.ts +2 -7
  11. package/__tests__/search-devices.test.ts +196 -8
  12. package/__tests__/sol-sign-offchain-message.test.ts +64 -0
  13. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  14. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  15. package/dist/api/GetFeatures.d.ts.map +1 -1
  16. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  17. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  18. package/dist/api/SearchDevices.d.ts +2 -15
  19. package/dist/api/SearchDevices.d.ts.map +1 -1
  20. package/dist/api/UploadPortfolio.d.ts +1 -0
  21. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  22. package/dist/api/allnetwork/AllNetworkGetAddress.d.ts +2 -0
  23. package/dist/api/allnetwork/AllNetworkGetAddress.d.ts.map +1 -1
  24. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +7 -1
  25. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  26. package/dist/api/allnetwork/AllNetworkGetAddressByLoop.d.ts.map +1 -1
  27. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  28. package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
  29. package/dist/core/RequestQueue.d.ts +1 -0
  30. package/dist/core/RequestQueue.d.ts.map +1 -1
  31. package/dist/core/index.d.ts +3 -1
  32. package/dist/core/index.d.ts.map +1 -1
  33. package/dist/core/uiPromiseRegistry.d.ts +1 -1
  34. package/dist/data-manager/TransportManager.d.ts +2 -0
  35. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  36. package/dist/device/Device.d.ts +2 -0
  37. package/dist/device/Device.d.ts.map +1 -1
  38. package/dist/device/DeviceCommands.d.ts +5 -4
  39. package/dist/device/DeviceCommands.d.ts.map +1 -1
  40. package/dist/events/logBlockEvent.d.ts.map +1 -1
  41. package/dist/index.d.ts +35 -30
  42. package/dist/index.js +587 -314
  43. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  44. package/dist/types/api/getFeatures.d.ts.map +1 -1
  45. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  46. package/dist/types/api/openWalletSession.d.ts +1 -10
  47. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  48. package/dist/types/api/protocolV2.d.ts +3 -4
  49. package/dist/types/api/protocolV2.d.ts.map +1 -1
  50. package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
  51. package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
  52. package/dist/types/params.d.ts.map +1 -1
  53. package/dist/utils/patch.d.ts +1 -1
  54. package/dist/utils/patch.d.ts.map +1 -1
  55. package/package.json +4 -4
  56. package/src/api/FirmwareUpdateV4.ts +9 -4
  57. package/src/api/GetFeatures.ts +1 -0
  58. package/src/api/GetPassphraseState.ts +1 -0
  59. package/src/api/OpenWalletSession.ts +7 -77
  60. package/src/api/SearchDevices.ts +121 -27
  61. package/src/api/UploadPortfolio.ts +5 -4
  62. package/src/api/allnetwork/AllNetworkGetAddress.ts +79 -45
  63. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +95 -24
  64. package/src/api/allnetwork/AllNetworkGetAddressByLoop.ts +3 -0
  65. package/src/api/device/DeviceVerify.ts +8 -0
  66. package/src/api/solana/SolSignOffchainMessage.ts +44 -4
  67. package/src/core/RequestQueue.ts +20 -0
  68. package/src/core/index.ts +159 -52
  69. package/src/data/messages/messages-protocol-v2.json +49 -33
  70. package/src/data/messages/messages.json +8 -5
  71. package/src/data-manager/TransportManager.ts +14 -3
  72. package/src/device/Device.ts +58 -27
  73. package/src/device/DeviceCommands.ts +29 -2
  74. package/src/events/logBlockEvent.ts +6 -75
  75. package/src/protocols/protocol-v2/walletSession.ts +14 -2
  76. package/src/types/api/getFeatures.ts +2 -1
  77. package/src/types/api/getPassphraseState.ts +2 -5
  78. package/src/types/api/openWalletSession.ts +7 -19
  79. package/src/types/api/protocolV2.ts +3 -4
  80. package/src/types/api/solSignOffchainMessage.ts +2 -0
  81. 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
- payload.mode === OpenWalletSessionMode.Standard ||
70
- payload.mode === OpenWalletSessionMode.SelectHidden
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) {
@@ -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
- await TransportManager.configure();
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
- try {
62
- // Discovery is best effort. Browsers may retain WebUSB grants for devices that
63
- // are offline, busy, or not ready, so one descriptor must not abort the scan.
64
- const result = await DevicePool.getDevices([descriptor], descriptor.path, {
65
- // Discovery must actively identify the protocol instead of trusting a caller hint.
66
- connectProtocol: undefined,
67
- forceProtocolDetection: true,
68
- refreshRuntimeState: true,
69
- });
70
- deviceList.push(...result.deviceList);
71
- } catch (error) {
72
- const errorCode =
73
- error && typeof error === 'object' && 'errorCode' in error
74
- ? (error as { errorCode?: unknown }).errorCode
75
- : undefined;
76
- Log.debug('Skip unavailable device during search', {
77
- path: descriptor.path,
78
- ...(errorCode !== undefined ? { errorCode } : {}),
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.map(device => device.toMessageObject());
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: false,
36
+ emitProgress: uiMode === 'progress',
35
37
  timeoutMs,
36
38
  };
37
39
  super.init();
38
40
  this.unlockPolicy = 'none';
39
- // Portfolio is a background write/apply flow and never synthesizes UI events.
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
- const groupedMethodParams = methodParams.reduce((groups, param) => {
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 i = 0;
48
- for (const [methodName, params] of methodGroups) {
49
- const methodParams = {
50
- bundle: params.map(param => ({
51
- ...param.params,
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
- if (this.abortController?.signal.aborted) {
62
- throw new Error(HardwareErrorCodeMessage[HardwareErrorCode.RepeatUnlocking]);
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[i],
108
+ ...response[index],
71
109
  };
72
110
  }
73
111
 
74
- if (this.payload?.bundle?.length > 1) {
75
- const progress = Math.round(((i + 1) / this.payload.bundle.length) * 100);
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 Promise.resolve(responses);
120
+ return responses;
87
121
  }
88
122
  }