@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.11

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 (79) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +13 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
  4. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
  5. package/__tests__/device-lifecycle-events.test.ts +86 -0
  6. package/__tests__/deviceUploadNft.test.ts +68 -0
  7. package/__tests__/evmSignTypedData.test.ts +42 -0
  8. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
  9. package/__tests__/logBlockEvent.test.ts +45 -122
  10. package/__tests__/open-wallet-session.test.ts +781 -69
  11. package/__tests__/pro2HostAssetPackage.test.ts +165 -0
  12. package/__tests__/protocol-v2-resources.test.ts +7 -4
  13. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
  14. package/__tests__/protocol-v2.test.ts +925 -97
  15. package/__tests__/protocolV2FileWrite.test.ts +40 -0
  16. package/dist/api/FirmwareUpdateV4.d.ts +5 -0
  17. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  18. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  19. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  20. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  21. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  22. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  23. package/dist/api/firmware/getBinary.d.ts +2 -2
  24. package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
  25. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  26. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  27. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  28. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  29. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  30. package/dist/core/RequestQueue.d.ts +2 -0
  31. package/dist/core/RequestQueue.d.ts.map +1 -1
  32. package/dist/core/index.d.ts +3 -1
  33. package/dist/core/index.d.ts.map +1 -1
  34. package/dist/device/Device.d.ts +1 -1
  35. package/dist/device/Device.d.ts.map +1 -1
  36. package/dist/device/DeviceConnector.d.ts +1 -1
  37. package/dist/device/DeviceConnector.d.ts.map +1 -1
  38. package/dist/events/logBlockEvent.d.ts.map +1 -1
  39. package/dist/events/ui-request.d.ts +3 -0
  40. package/dist/events/ui-request.d.ts.map +1 -1
  41. package/dist/index.d.ts +21 -5
  42. package/dist/index.js +2511 -1538
  43. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
  45. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  46. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  47. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  48. package/dist/types/settings.d.ts +2 -2
  49. package/dist/types/settings.d.ts.map +1 -1
  50. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  51. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  52. package/dist/utils/patch.d.ts +1 -1
  53. package/dist/utils/patch.d.ts.map +1 -1
  54. package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
  55. package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
  56. package/package.json +4 -4
  57. package/src/api/FirmwareUpdateV4.ts +474 -83
  58. package/src/api/OpenWalletSession.ts +39 -12
  59. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
  60. package/src/api/evm/EVMSignTypedData.ts +1 -0
  61. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  62. package/src/api/helpers/protocolV2FileWrite.ts +11 -5
  63. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  64. package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
  65. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
  66. package/src/api/protocol-v2/helpers.ts +8 -0
  67. package/src/core/RequestQueue.ts +16 -1
  68. package/src/core/index.ts +164 -26
  69. package/src/data/messages/messages-protocol-v2.json +1589 -1375
  70. package/src/device/Device.ts +3 -1
  71. package/src/device/DeviceConnector.ts +7 -3
  72. package/src/events/logBlockEvent.ts +6 -75
  73. package/src/events/ui-request.ts +3 -0
  74. package/src/protocols/protocol-v2/resources.ts +9 -1
  75. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
  76. package/src/protocols/protocol-v2/walletSession.ts +223 -56
  77. package/src/types/settings.ts +4 -2
  78. package/src/utils/deviceFeaturesUtils.ts +4 -0
  79. package/src/utils/pro2HostAssetPackage.ts +400 -0
@@ -29,6 +29,12 @@ const wasResumed = (session: unknown) =>
29
29
  'resumed' in session &&
30
30
  (session as { resumed?: unknown }).resumed === true;
31
31
 
32
+ const wasWalletStatusRefreshed = (session: unknown) =>
33
+ !!session &&
34
+ typeof session === 'object' &&
35
+ 'walletStatusRefreshed' in session &&
36
+ (session as { walletStatusRefreshed?: unknown }).walletStatusRefreshed === true;
37
+
32
38
  const requireHiddenWalletResponse = (session: { passphraseState?: string }) => {
33
39
  if (!session.passphraseState) {
34
40
  throw ERRORS.TypedError(
@@ -87,12 +93,20 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
87
93
  this.unlockPolicy = 'unlock-before-run';
88
94
  this.skipForceUpdateCheck = true;
89
95
  this.params = normalizeParams(this.payload as unknown as Record<string, unknown>);
96
+ this.protocolV2PreUnlockPinType =
97
+ this.params.mode === OpenWalletSessionMode.SelectHidden
98
+ ? DeviceSessionPinType.Any
99
+ : DeviceSessionPinType.Main;
90
100
  this.payload.useEmptyPassphrase = this.params.mode === OpenWalletSessionMode.Standard;
91
101
  }
92
102
 
93
103
  async run(): Promise<OpenWalletSessionPayload> {
94
104
  const isProtocolV2 = this.device.isProtocolV2();
95
- let state = await this.device.getDeviceState({ refreshSections: ['status'] });
105
+ const reusePreflightStatus =
106
+ isProtocolV2 && this.protocolV2UnlockContext?.preflightStatusRefreshed === true;
107
+ let state = reusePreflightStatus
108
+ ? await this.device.getDeviceState()
109
+ : await this.device.getDeviceState({ refreshSections: ['status'] });
96
110
  let currentDeviceId = state.identity.deviceId;
97
111
  const hasAuthoritativeProtocolV2WalletStatus = (candidate: typeof state) =>
98
112
  candidate.status.unlocked === true &&
@@ -115,14 +129,25 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
115
129
  currentDeviceId = state.identity.deviceId;
116
130
  return state;
117
131
  };
132
+ const resolveProtocolV2DeviceStateAfterSession = async (session: unknown) => {
133
+ if (!wasWalletStatusRefreshed(session)) {
134
+ return refreshProtocolV2DeviceState();
135
+ }
136
+ state = await this.device.getDeviceState();
137
+ currentDeviceId = state.identity.deviceId;
138
+ return state;
139
+ };
118
140
  const ensureProtocolV2WalletStatus = async () => {
119
141
  if (isProtocolV2 && !hasAuthoritativeProtocolV2WalletStatus(state)) {
120
- await this.device.unlockDevice(DeviceSessionPinType.Main, {
121
- source: 'unlock-coordinator',
122
- reason: 'device-locked',
123
- deviceOnly: true,
124
- method: 'openWalletSession',
125
- });
142
+ await this.device.unlockDevice(
143
+ this.protocolV2PreUnlockPinType ?? DeviceSessionPinType.Main,
144
+ {
145
+ source: 'unlock-coordinator',
146
+ reason: 'device-locked',
147
+ deviceOnly: true,
148
+ method: 'openWalletSession',
149
+ }
150
+ );
126
151
  requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState());
127
152
  }
128
153
  return state;
@@ -135,17 +160,19 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
135
160
  const session = isProtocolV2
136
161
  ? await getProtocolV2WalletSession(this.device, {
137
162
  onlyMainPin: true,
138
- deriveCardano: this.payload.deriveCardano,
139
163
  selectMainWalletBeforeRestore:
140
164
  !hasAuthoritativeProtocolV2WalletStatus(state) ||
141
165
  state.status.unlockedAttachPin === true,
166
+ mainPinSelected: this.protocolV2UnlockContext?.preflightMainPinSelected,
142
167
  })
143
168
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, {
144
169
  onlyMainPin: true,
145
170
  initSession: this.payload.initSession,
146
171
  });
147
172
  const refreshedState = isProtocolV2
148
- ? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
173
+ ? requireAuthoritativeProtocolV2WalletStatus(
174
+ await resolveProtocolV2DeviceStateAfterSession(session)
175
+ )
149
176
  : state;
150
177
  const deviceId = requireDeviceId();
151
178
  if (
@@ -197,7 +224,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
197
224
  const session = isProtocolV2
198
225
  ? await getProtocolV2WalletSession(this.device, {
199
226
  expectedPassphraseState: this.params.passphraseState,
200
- deriveCardano: this.payload.deriveCardano,
201
227
  })
202
228
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, {
203
229
  expectPassphraseState: this.params.passphraseState,
@@ -229,11 +255,12 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
229
255
  ...(readCurrentAttachPinSession
230
256
  ? { readCurrentAttachPinSession: true }
231
257
  : { forceWalletSelection: true }),
232
- deriveCardano: this.payload.deriveCardano,
233
258
  })
234
259
  : await getPassphraseStateWithRefreshDeviceInfo(this.device, { initSession: true });
235
260
  const refreshedState = isProtocolV2
236
- ? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
261
+ ? requireAuthoritativeProtocolV2WalletStatus(
262
+ await resolveProtocolV2DeviceStateAfterSession(session)
263
+ )
237
264
  : state;
238
265
  const deviceId = requireDeviceId();
239
266
  if (isProtocolV2 && refreshedState.status.passphraseProtection !== true) {
@@ -393,14 +393,17 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
393
393
  // the root fingerprint, so each nested chain method must resume the
394
394
  // requested standard or hidden wallet before sending its device command.
395
395
  const useEmptyPassphrase = this.payload.useEmptyPassphrase === true;
396
- const deriveCardano = method.name.startsWith('cardano');
396
+ // Nested Cardano methods opt in to [Standard, Cardano] if Ask rebuilds.
397
+ // Other chains stay Standard-only.
398
+ const deriveCardano = method.name.startsWith('cardano') ? true : undefined;
397
399
  const shouldResumeWalletSession = useEmptyPassphrase || !!this.payload.passphraseState;
398
400
  if (this.device.isProtocolV2() && shouldResumeWalletSession) {
399
401
  const passphraseStateSafety = await this.device.checkPassphraseStateSafety(
400
402
  this.payload.passphraseState,
401
403
  useEmptyPassphrase,
402
404
  this.payload.skipPassphraseCheck,
403
- deriveCardano
405
+ deriveCardano,
406
+ this.protocolV2UnlockContext?.preflightMainPinSelected
404
407
  );
405
408
  if (!passphraseStateSafety) {
406
409
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
@@ -360,6 +360,7 @@ export default class EVMSignTypedData extends BaseMethod<EVMSignTypedDataParams>
360
360
 
361
361
  const supportBiggerData =
362
362
  DeviceModelToTypes.model_classic1s.includes(currentDeviceType) ||
363
+ DeviceModelToTypes.model_pro2.includes(currentDeviceType) ||
363
364
  (DeviceModelToTypes.model_touch.includes(currentDeviceType) &&
364
365
  semver.gte(currentVersion, supportBiggerDataVersion));
365
366
 
@@ -41,9 +41,15 @@ const FIRMWARE_UPDATE_CONFIRM = 'Firmware install confirmed';
41
41
  */
42
42
  export const BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS = 5000;
43
43
 
44
- type FirmwareTransferMetrics = Pick<
44
+ type FirmwareProgressMetadata = Pick<
45
45
  FirmwareProgress['payload'],
46
- 'transferredBytes' | 'totalBytes' | 'rateBytesPerSecond' | 'elapsedMs'
46
+ | 'transferredBytes'
47
+ | 'totalBytes'
48
+ | 'rateBytesPerSecond'
49
+ | 'elapsedMs'
50
+ | 'installTargetId'
51
+ | 'installPhase'
52
+ | 'installPhaseProgress'
47
53
  >;
48
54
 
49
55
  const isDeviceDisconnectedError = (error: unknown) => {
@@ -103,7 +109,7 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
103
109
  postProgressMessage = (
104
110
  progress: number,
105
111
  progressType: IFirmwareUpdateProgressType,
106
- metrics?: FirmwareTransferMetrics
112
+ metrics?: FirmwareProgressMetadata
107
113
  ) => {
108
114
  this.postMessage(
109
115
  createUiMessage(UI_REQUEST.FIRMWARE_PROGRESS, {
@@ -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
@@ -4,7 +4,13 @@ import { invalidParameter } from '../helpers/filesystemValidation';
4
4
  import type { DeviceFirmwareUpdateStatusGetParams } from './helpers';
5
5
  import type { DeviceFirmwareUpdateRecordFields } from '@onekeyfe/hd-transport';
6
6
 
7
- const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = ['status', 'payload_version', 'path'] as const;
7
+ const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = [
8
+ 'status',
9
+ 'progress_percent',
10
+ 'phase_info',
11
+ 'payload_version',
12
+ 'path',
13
+ ] as const;
8
14
 
9
15
  function normalizeStatusFields(
10
16
  fields: DeviceFirmwareUpdateStatusGetParams['fields']
@@ -20,6 +20,10 @@ import {
20
20
  getCompletePro2NftBasenames,
21
21
  } from '../../utils/pro2Nft';
22
22
  import { encodePro2Image } from '../../utils/pro2Wallpaper';
23
+ import {
24
+ buildPro2HostAssetPackage,
25
+ supportsPro2HostAssetPackage,
26
+ } from '../../utils/pro2HostAssetPackage';
23
27
  import { BaseMethod } from '../BaseMethod';
24
28
  import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
25
29
  import { invalidParameter } from '../helpers/filesystemValidation';
@@ -38,8 +42,11 @@ export type DeviceUploadNftParams = {
38
42
 
39
43
  export type DeviceUploadNftResponse = {
40
44
  basename: string;
45
+ /** Final image path after firmware extracts a host-asset package. */
41
46
  imagePath: string;
47
+ /** Final thumbnail path after firmware extracts a host-asset package. */
42
48
  thumbnailPath: string;
49
+ /** Final metadata path after firmware extracts a host-asset package. */
43
50
  metadataPath: string;
44
51
  totalSize: number;
45
52
  nftUpdated: true;
@@ -138,7 +145,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
138
145
  this.useDevicePassphraseState = false;
139
146
  }
140
147
 
141
- private async assertCapabilities() {
148
+ private async assertCapabilities(useHostAssetPackage: boolean) {
142
149
  const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
143
150
  const hasFileWrite = supportsProtocolV2Message(
144
151
  protocolInfo,
@@ -150,7 +157,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
150
157
  );
151
158
  const hasDirList = supportsProtocolV2Message(protocolInfo, FILESYSTEM_DIR_LIST_MESSAGE_TYPE);
152
159
  const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
153
- if (!hasFileWrite || !hasPathInfo || !hasDirList || !hasNftUpdate) {
160
+ if (!hasFileWrite || !hasNftUpdate || (!useHostAssetPackage && (!hasPathInfo || !hasDirList))) {
154
161
  throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
155
162
  }
156
163
  }
@@ -192,16 +199,32 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
192
199
  const { bundle } = this;
193
200
  if (!bundle) throw invalidParameter('NFT data has not been initialized.');
194
201
 
195
- await this.assertCapabilities();
202
+ const useHostAssetPackage = supportsPro2HostAssetPackage(
203
+ this.device.state?.versions.firmware ?? undefined
204
+ );
205
+ await this.assertCapabilities(useHostAssetPackage);
196
206
  this.throwIfAborted();
197
- await this.assertStorageCapacity(bundle.basename);
207
+ if (!useHostAssetPackage) await this.assertStorageCapacity(bundle.basename);
198
208
  this.throwIfAborted();
199
209
 
200
- const files = [
210
+ const extractedFiles = [
201
211
  { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
202
212
  { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
203
213
  { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
204
214
  ];
215
+ const files = useHostAssetPackage
216
+ ? [
217
+ {
218
+ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.okpkg`,
219
+ data: buildPro2HostAssetPackage(
220
+ extractedFiles.map(file => ({
221
+ name: file.path.slice(`${PRO2_NFT_DIRECTORY}/`.length),
222
+ data: file.data,
223
+ }))
224
+ ),
225
+ },
226
+ ]
227
+ : extractedFiles;
205
228
  const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
206
229
  let transferredBeforeFile = 0;
207
230
 
@@ -238,9 +261,9 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
238
261
  const response = await this.updateNft(bundle.basename);
239
262
  return {
240
263
  basename: bundle.basename,
241
- imagePath: files[0].path,
242
- thumbnailPath: files[1].path,
243
- metadataPath: files[2].path,
264
+ imagePath: extractedFiles[0].path,
265
+ thumbnailPath: extractedFiles[1].path,
266
+ metadataPath: extractedFiles[2].path,
244
267
  totalSize,
245
268
  nftUpdated: true,
246
269
  message: response.message?.message,
@@ -16,14 +16,26 @@ import {
16
16
  type Pro2WallpaperColorFormat,
17
17
  encodePro2Wallpaper,
18
18
  } from '../../utils/pro2Wallpaper';
19
+ import {
20
+ buildPro2HostAssetPackage,
21
+ supportsPro2HostAssetPackage,
22
+ } from '../../utils/pro2HostAssetPackage';
19
23
 
20
24
  export type DeviceUploadWallpaperParams = {
21
25
  jpegBase64: string;
26
+ /**
27
+ * Legacy firmware uses this name for the uploaded `.bin` file. Firmware
28
+ * 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
29
+ */
22
30
  fileName?: string;
23
31
  chunkSize?: number;
24
32
  };
25
33
 
26
34
  export type DeviceUploadWallpaperResponse = {
35
+ /**
36
+ * Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
37
+ * the temporary package path; firmware extracts and persists wallpaper.bin.
38
+ */
27
39
  path: string;
28
40
  size: number;
29
41
  colorFormat: Pro2WallpaperColorFormat;
@@ -31,10 +43,13 @@ export type DeviceUploadWallpaperResponse = {
31
43
  };
32
44
 
33
45
  const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
46
+ const WALLPAPER_PACKAGE_PATH = `${WALLPAPER_DIRECTORY}/wallpaper.okpkg`;
47
+ const WALLPAPER_PACKAGE_ENTRY = 'wallpaper.bin';
34
48
  const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
35
49
  const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
36
50
  const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
37
51
  const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
52
+ const WALLPAPER_PACKAGE_BLE_CHUNK_SIZE = 1960;
38
53
  const Log = getLogger(LoggerNames.Method);
39
54
 
40
55
  function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
@@ -116,17 +131,16 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
116
131
  this.directoryReady = true;
117
132
  }
118
133
 
119
- private async upload() {
134
+ private async upload(path: string, data: Uint8Array, bleChunkSizeLimit?: number) {
120
135
  if (this.uploaded) return;
121
- const { encoded } = this;
122
- if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
123
136
 
124
137
  await writeProtocolV2File({
125
138
  commands: this.device.commands,
126
- path: this.path,
127
- data: encoded.data,
128
- totalSize: encoded.data.byteLength,
139
+ path,
140
+ data,
141
+ totalSize: data.byteLength,
129
142
  chunkSize: this.params.chunkSize,
143
+ bleChunkSizeLimit,
130
144
  maxChunkRetries: 3,
131
145
  overwrite: true,
132
146
  append: false,
@@ -145,7 +159,18 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
145
159
  if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
146
160
  await this.assertCapabilities();
147
161
  await this.ensureDirectory();
148
- await this.upload();
162
+ const useHostAssetPackage = supportsPro2HostAssetPackage(
163
+ this.device.state?.versions.firmware ?? undefined
164
+ );
165
+ const data = useHostAssetPackage
166
+ ? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
167
+ : encoded.data;
168
+ if (useHostAssetPackage) this.path = WALLPAPER_PACKAGE_PATH;
169
+ await this.upload(
170
+ this.path,
171
+ data,
172
+ useHostAssetPackage ? WALLPAPER_PACKAGE_BLE_CHUNK_SIZE : undefined
173
+ );
149
174
  const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
150
175
  settings: { wallpaper_path: this.path },
151
176
  });
@@ -160,7 +185,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
160
185
  }
161
186
  return {
162
187
  path: this.path,
163
- size: encoded.data.byteLength,
188
+ size: data.byteLength,
164
189
  colorFormat: encoded.colorFormat,
165
190
  message: response.message?.message,
166
191
  };
@@ -1,4 +1,5 @@
1
1
  import { DeviceRebootType } from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
3
 
3
4
  import { invalidParameter, validateNonEmptyString } from '../helpers/filesystemValidation';
4
5
  import { ProtocolV2FirmwareTargetType } from '../../protocols/protocol-v2/firmware';
@@ -103,6 +104,13 @@ export const getProtocolV2UnknownErrorText = (error: unknown) => {
103
104
  };
104
105
 
105
106
  export const isProtocolV2DeviceDisconnectedError = (error: unknown) => {
107
+ const errorCode =
108
+ error && typeof error === 'object'
109
+ ? (error as { errorCode?: number; code?: number }).errorCode ??
110
+ (error as { errorCode?: number; code?: number }).code
111
+ : undefined;
112
+ if (errorCode === HardwareErrorCode.BleDeviceDisconnected) return true;
113
+
106
114
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
107
115
  const compactMessage = message.replace(/\s+/g, '');
108
116
  return (
@@ -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;