@onekeyfe/hd-core 1.2.2-alpha.1 → 1.2.2-alpha.10

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 (34) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +2 -2
  2. package/__tests__/device-lifecycle-events.test.ts +67 -0
  3. package/__tests__/open-wallet-session.test.ts +613 -80
  4. package/__tests__/pro2HostAssetPackage.test.ts +108 -1
  5. package/__tests__/protocol-v2.test.ts +194 -49
  6. package/__tests__/protocolV2FileWrite.test.ts +40 -0
  7. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  8. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  9. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  10. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  11. package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
  12. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  13. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  14. package/dist/core/RequestQueue.d.ts +2 -0
  15. package/dist/core/RequestQueue.d.ts.map +1 -1
  16. package/dist/core/index.d.ts +2 -1
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1411 -1117
  20. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  21. package/dist/utils/patch.d.ts +1 -1
  22. package/dist/utils/patch.d.ts.map +1 -1
  23. package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -1
  24. package/package.json +4 -4
  25. package/src/api/FirmwareUpdateV4.ts +42 -10
  26. package/src/api/OpenWalletSession.ts +0 -3
  27. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +3 -1
  28. package/src/api/helpers/protocolV2FileWrite.ts +11 -5
  29. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +8 -2
  30. package/src/core/RequestQueue.ts +16 -1
  31. package/src/core/index.ts +48 -21
  32. package/src/data/messages/messages-protocol-v2.json +1471 -1367
  33. package/src/protocols/protocol-v2/walletSession.ts +157 -38
  34. package/src/utils/pro2HostAssetPackage.ts +73 -27
@@ -36,6 +36,8 @@ export type ProtocolV2FileWriteOptions = {
36
36
  chunkSize?: number;
37
37
  chunkLen?: number;
38
38
  chunkSizeLimit?: number;
39
+ /** BLE-only limit for a caller whose fixed short path has a verified larger frame budget. */
40
+ bleChunkSizeLimit?: number;
39
41
  overwrite?: boolean;
40
42
  append?: boolean;
41
43
  uiPercentage?: number;
@@ -114,11 +116,15 @@ export function isProtocolV2ResponseTimeout(error: unknown) {
114
116
  );
115
117
  }
116
118
 
117
- function getProtocolV2FileChunkLimit() {
119
+ function getProtocolV2FileChunkLimit(bleChunkSizeLimit?: number) {
118
120
  const env = DataManager.getSettings('env');
119
- return env && DataManager.isBleConnect(env)
120
- ? PROTOCOL_V2_BLE_FILE_CHUNK_SIZE
121
- : PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
121
+ if (env && DataManager.isBleConnect(env)) {
122
+ const configuredLimit = Number(bleChunkSizeLimit);
123
+ return Number.isFinite(configuredLimit) && configuredLimit > 0
124
+ ? Math.floor(configuredLimit)
125
+ : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
126
+ }
127
+ return PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
122
128
  }
123
129
 
124
130
  async function dataToUint8Array(data: ProtocolV2FileWriteData): Promise<Uint8Array> {
@@ -178,7 +184,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
178
184
  );
179
185
  }
180
186
 
181
- const defaultChunkSizeLimit = getProtocolV2FileChunkLimit();
187
+ const defaultChunkSizeLimit = getProtocolV2FileChunkLimit(options.bleChunkSizeLimit);
182
188
  const configuredChunkSizeLimit = Number(options.chunkSizeLimit);
183
189
  const chunkSizeLimit =
184
190
  Number.isFinite(configuredChunkSizeLimit) && configuredChunkSizeLimit > 0
@@ -49,6 +49,7 @@ const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
49
49
  const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
50
50
  const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
51
51
  const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
52
+ const WALLPAPER_PACKAGE_BLE_CHUNK_SIZE = 1960;
52
53
  const Log = getLogger(LoggerNames.Method);
53
54
 
54
55
  function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
@@ -130,7 +131,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
130
131
  this.directoryReady = true;
131
132
  }
132
133
 
133
- private async upload(path: string, data: Uint8Array) {
134
+ private async upload(path: string, data: Uint8Array, bleChunkSizeLimit?: number) {
134
135
  if (this.uploaded) return;
135
136
 
136
137
  await writeProtocolV2File({
@@ -139,6 +140,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
139
140
  data,
140
141
  totalSize: data.byteLength,
141
142
  chunkSize: this.params.chunkSize,
143
+ bleChunkSizeLimit,
142
144
  maxChunkRetries: 3,
143
145
  overwrite: true,
144
146
  append: false,
@@ -164,7 +166,11 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
164
166
  ? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
165
167
  : encoded.data;
166
168
  if (useHostAssetPackage) this.path = WALLPAPER_PACKAGE_PATH;
167
- await this.upload(this.path, data);
169
+ await this.upload(
170
+ this.path,
171
+ data,
172
+ useHostAssetPackage ? WALLPAPER_PACKAGE_BLE_CHUNK_SIZE : undefined
173
+ );
168
174
  const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
169
175
  settings: { wallpaper_path: this.path },
170
176
  });
@@ -56,11 +56,20 @@ export default class RequestQueue {
56
56
  return false;
57
57
  }
58
58
 
59
+ private isRequestForConnectId(request: RequestTask, connectId: string) {
60
+ const { method } = request;
61
+ return (
62
+ method.connectId === connectId ||
63
+ method.device?.mainId === connectId ||
64
+ method.device?.getConnectId() === connectId
65
+ );
66
+ }
67
+
59
68
  // 取消与指定connectId相关的所有请求
60
69
  public abortRequestsByConnectId(connectId: string) {
61
70
  let count = 0;
62
71
  this.requestQueue.forEach((request, _) => {
63
- if (request.abortController && request.method.connectId === connectId) {
72
+ if (request.abortController && this.isRequestForConnectId(request, connectId)) {
64
73
  request.abortController.abort();
65
74
  request.abortController = undefined;
66
75
  count++;
@@ -69,6 +78,12 @@ export default class RequestQueue {
69
78
  return count;
70
79
  }
71
80
 
81
+ public getRequestTasksIdByConnectId(connectId: string) {
82
+ return Array.from(this.requestQueue.values())
83
+ .filter(request => this.isRequestForConnectId(request, connectId))
84
+ .map(request => request.id);
85
+ }
86
+
72
87
  // 取消所有请求
73
88
  public abortAllRequests() {
74
89
  let count = 0;
package/src/core/index.ts CHANGED
@@ -2,7 +2,11 @@ import semver from 'semver';
2
2
  import EventEmitter from 'events';
3
3
  import {
4
4
  DeviceSessionPinType,
5
+ type LowlevelTransportSharedPlugin,
6
+ type OneKeyDeviceInfo,
7
+ type ProtocolType,
5
8
  TRANSPORT_EVENT,
9
+ type TransportDeviceDisconnectEvent,
6
10
  isProtocolV2LinkDisabledError,
7
11
  } from '@onekeyfe/hd-transport';
8
12
  import {
@@ -74,11 +78,6 @@ import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } fro
74
78
  import type { DeviceEvents, InitOptions, RunOptions } from '../device/Device';
75
79
  import type { SdkTracingContext } from '../utils/tracing';
76
80
  import type { Deferred } from '@onekeyfe/hd-shared';
77
- import type {
78
- LowlevelTransportSharedPlugin,
79
- OneKeyDeviceInfo,
80
- TransportDeviceDisconnectEvent,
81
- } from '@onekeyfe/hd-transport';
82
81
  import type { BaseMethod } from '../api/BaseMethod';
83
82
 
84
83
  const Log = getLogger(LoggerNames.Core);
@@ -92,7 +91,7 @@ const preWarmDoneAt = new Map<string, number>();
92
91
 
93
92
  export type CoreContext = ReturnType<Core['getCoreContext']>;
94
93
 
95
- function hasDeriveCardano(method: BaseMethod): boolean {
94
+ function resolveDeriveCardano(method: BaseMethod): boolean | undefined {
96
95
  if (
97
96
  method.name.startsWith('allNetworkGetAddress') &&
98
97
  method.payload &&
@@ -102,15 +101,22 @@ function hasDeriveCardano(method: BaseMethod): boolean {
102
101
  ) {
103
102
  return true;
104
103
  }
105
-
106
- return method.name.startsWith('cardano') || method.payload?.deriveCardano;
104
+ if (method.name.startsWith('cardano')) {
105
+ return true;
106
+ }
107
+ // V1 Initialize only sends derive_cardano on an explicit true.
108
+ // V2 AskPassphrase maps true to [Standard, Cardano] and anything else to [Standard].
109
+ if (method.payload?.deriveCardano === true) {
110
+ return true;
111
+ }
112
+ return undefined;
107
113
  }
108
114
 
109
115
  const parseInitOptions = (method?: BaseMethod): InitOptions => ({
110
116
  initSession: method?.payload.initSession,
111
117
  passphraseState: method?.payload.useEmptyPassphrase ? undefined : method?.payload.passphraseState,
112
118
  deviceId: method?.payload.deviceId,
113
- deriveCardano: method && hasDeriveCardano(method),
119
+ deriveCardano: method ? resolveDeriveCardano(method) : undefined,
114
120
  connectProtocol: method?.payload.connectProtocol,
115
121
  forceProtocolDetection: method?.payload.forceProtocolDetection,
116
122
  protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
@@ -651,7 +657,7 @@ const onCallDevice = async (
651
657
  method.payload?.passphraseState,
652
658
  method.payload?.useEmptyPassphrase,
653
659
  method.payload?.skipPassphraseCheck,
654
- hasDeriveCardano(method),
660
+ resolveDeriveCardano(method),
655
661
  method.protocolV2UnlockContext?.preflightMainPinSelected
656
662
  );
657
663
 
@@ -1037,6 +1043,7 @@ async function connectDeviceForBle(
1037
1043
  !device.commands ||
1038
1044
  device.commands.disposed;
1039
1045
  if (shouldAcquire) {
1046
+ const connectProtocol = resolveBleConnectProtocol(method);
1040
1047
  // The deadline/abort guards are scoped to the desktop electron
1041
1048
  // transport: its IPC acquire is the only path with a proven
1042
1049
  // never-settling failure mode, while react-native/lowlevel acquire may
@@ -1048,13 +1055,13 @@ async function connectDeviceForBle(
1048
1055
  throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
1049
1056
  }
1050
1057
  if (!useAcquireGuards) {
1051
- await device.acquire(method.payload.connectProtocol, {
1058
+ await device.acquire(connectProtocol, {
1052
1059
  forceProtocolDetection: method.payload.forceProtocolDetection,
1053
1060
  });
1054
1061
  } else {
1055
1062
  try {
1056
1063
  await raceBleAcquire(
1057
- device.acquire(method.payload.connectProtocol, {
1064
+ device.acquire(connectProtocol, {
1058
1065
  forceProtocolDetection: method.payload.forceProtocolDetection,
1059
1066
  }),
1060
1067
  abortSignal
@@ -1120,6 +1127,14 @@ async function connectDeviceForBle(
1120
1127
  }
1121
1128
  }
1122
1129
 
1130
+ export function resolveBleConnectProtocol(method: BaseMethod): ProtocolType | undefined {
1131
+ if (method.payload.connectProtocol === 'V1' || method.payload.connectProtocol === 'V2') {
1132
+ return method.payload.connectProtocol;
1133
+ }
1134
+ const supportedProtocols = method.getSupportedProtocols();
1135
+ return supportedProtocols.length === 1 && supportedProtocols[0] === 'V2' ? 'V2' : undefined;
1136
+ }
1137
+
1123
1138
  type IPollFn<T> = (time?: number) => T;
1124
1139
  // eslint-disable-next-line @typescript-eslint/require-await
1125
1140
  const ensureConnected = async (
@@ -1318,7 +1333,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1318
1333
  // cancel callback tasks
1319
1334
  requestQueue.cancelCallbackTasks(connectId);
1320
1335
 
1321
- const requestIds = requestQueue.getRequestTasksId();
1336
+ const requestIds = requestQueue.getRequestTasksIdByConnectId(connectId);
1322
1337
  Log.debug(
1323
1338
  `Cancel Api connect requestQueues: length:${requestIds.length} requestIds:${requestIds.join(
1324
1339
  ','
@@ -1326,6 +1341,8 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1326
1341
  );
1327
1342
  // Abort before rejecting: rejectRequest releases the task and would make
1328
1343
  // its AbortController unreachable to an in-flight method loop.
1344
+ // Match both the requested connectId and a device selected internally by the
1345
+ // method, such as Desktop WebUSB firmwareUpdateV4.
1329
1346
  requestQueue.abortRequestsByConnectId(connectId);
1330
1347
  const canceledDevices: Device[] = [];
1331
1348
  const interruptDevice = (device: Device | undefined, deviceConnectId: string) => {
@@ -1342,7 +1359,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1342
1359
  // During ensureConnected the method has a connectId but device is
1343
1360
  // assigned only after the poll succeeds. Interrupt the cached BLE
1344
1361
  // Device so an in-flight acquire/initialize cannot finish.
1345
- interruptDevice(task.method?.device, task.method.connectId ?? connectId);
1362
+ interruptDevice(task.method?.device, connectId);
1346
1363
  interruptDevice(deviceCacheMap.get(connectId), connectId);
1347
1364
  requestQueue.rejectRequest(
1348
1365
  requestId,
@@ -1357,10 +1374,11 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1357
1374
  }
1358
1375
  } else {
1359
1376
  const env = DataManager.getSettings('env');
1377
+ // Abort every method before rejecting its queue task. Non-BLE methods also
1378
+ // use the signal to stop recovery loops after the public promise is rejected.
1379
+ requestQueue.abortAllRequests();
1360
1380
  if (DataManager.isBleConnect(env)) {
1361
1381
  Log.debug('Cancel Api all _deviceList: ');
1362
- // Keep method abort signals observable until every active task is rejected.
1363
- requestQueue.abortAllRequests();
1364
1382
  const canceledDevices: Device[] = [];
1365
1383
  const interruptDevice = (device?: Device) => {
1366
1384
  if (!device || canceledDevices.includes(device)) {
@@ -1403,8 +1421,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1403
1421
  }
1404
1422
  }
1405
1423
 
1406
- cleanup();
1407
- closePopup();
1424
+ cleanup(connectId);
1425
+ if (!connectId || _uiPromises.length === 0) {
1426
+ closePopup();
1427
+ }
1408
1428
  };
1409
1429
 
1410
1430
  const checkPassphraseEnableState = (method: BaseMethod, features?: Features) => {
@@ -1442,9 +1462,16 @@ const shouldCheckPassphraseState = (method: BaseMethod, device: Device) => {
1442
1462
  return device.hasUsePassphrase();
1443
1463
  };
1444
1464
 
1445
- const cleanup = () => {
1446
- const pendingUiPromises = _uiPromises;
1447
- _uiPromises = [];
1465
+ const cleanup = (connectId?: string) => {
1466
+ const pendingUiPromises = connectId
1467
+ ? _uiPromises.filter(
1468
+ uiPromise =>
1469
+ uiPromise.data?.mainId === connectId || uiPromise.data?.getConnectId() === connectId
1470
+ )
1471
+ : _uiPromises;
1472
+ _uiPromises = connectId
1473
+ ? _uiPromises.filter(uiPromise => !pendingUiPromises.includes(uiPromise))
1474
+ : [];
1448
1475
  rejectUiPromises(
1449
1476
  pendingUiPromises,
1450
1477
  ERRORS.TypedError(HardwareErrorCode.ActionCancelled, 'UI request was cancelled')