@onekeyfe/hd-core 1.2.0-alpha.41 → 1.2.0-alpha.43

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 (202) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
  2. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +14 -1
  4. package/__tests__/connectSettings.test.ts +5 -47
  5. package/__tests__/deviceUploadNft.test.ts +272 -0
  6. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  7. package/__tests__/get-device-state.test.ts +132 -12
  8. package/__tests__/homescreen.test.ts +41 -1
  9. package/__tests__/logBlockEvent.test.ts +1 -1
  10. package/__tests__/open-wallet-session.test.ts +366 -31
  11. package/__tests__/pro2Nft.test.ts +108 -0
  12. package/__tests__/protocol-v2-resources.test.ts +204 -0
  13. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  14. package/__tests__/protocol-v2-unlock-policy.test.ts +196 -35
  15. package/__tests__/protocol-v2.test.ts +568 -957
  16. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  17. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  18. package/__tests__/uiProgressThrottle.test.ts +74 -0
  19. package/dist/api/BaseMethod.d.ts +3 -1
  20. package/dist/api/BaseMethod.d.ts.map +1 -1
  21. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  22. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  23. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  25. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV4.d.ts +6 -25
  27. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  28. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  29. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  30. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  32. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -8
  34. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  35. package/dist/api/firmware/getBinary.d.ts +0 -7
  36. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  37. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  38. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  39. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  40. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  41. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  42. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  43. package/dist/api/index.d.ts +1 -0
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  46. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  48. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  49. package/dist/core/deviceEventRegistration.d.ts +2 -0
  50. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/data-manager/DataManager.d.ts +3 -2
  53. package/dist/data-manager/DataManager.d.ts.map +1 -1
  54. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  55. package/dist/device/Device.d.ts +24 -4
  56. package/dist/device/Device.d.ts.map +1 -1
  57. package/dist/device/DeviceCommands.d.ts +4 -4
  58. package/dist/events/device.d.ts +4 -0
  59. package/dist/events/device.d.ts.map +1 -1
  60. package/dist/events/logBlockEvent.d.ts.map +1 -1
  61. package/dist/events/ui-request.d.ts +28 -2
  62. package/dist/events/ui-request.d.ts.map +1 -1
  63. package/dist/index.d.ts +115 -184
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2051 -3349
  66. package/dist/inject.d.ts.map +1 -1
  67. package/dist/lowLevelInject.d.ts.map +1 -1
  68. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  69. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  70. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  71. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  72. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  73. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  74. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  75. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  76. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  77. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  78. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  79. package/dist/topLevelInject.d.ts.map +1 -1
  80. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -4
  81. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  82. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  83. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  84. package/dist/types/api/export.d.ts +1 -4
  85. package/dist/types/api/export.d.ts.map +1 -1
  86. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  87. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  88. package/dist/types/api/index.d.ts +3 -10
  89. package/dist/types/api/index.d.ts.map +1 -1
  90. package/dist/types/api/protocolV2.d.ts +3 -0
  91. package/dist/types/api/protocolV2.d.ts.map +1 -1
  92. package/dist/types/settings.d.ts +12 -13
  93. package/dist/types/settings.d.ts.map +1 -1
  94. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  95. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  96. package/dist/utils/homescreen.d.ts +4 -0
  97. package/dist/utils/homescreen.d.ts.map +1 -1
  98. package/dist/utils/index.d.ts +1 -1
  99. package/dist/utils/index.d.ts.map +1 -1
  100. package/dist/utils/patch.d.ts +1 -1
  101. package/dist/utils/patch.d.ts.map +1 -1
  102. package/dist/utils/pro2Nft.d.ts +31 -0
  103. package/dist/utils/pro2Nft.d.ts.map +1 -0
  104. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  105. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  106. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  107. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  108. package/package.json +4 -4
  109. package/src/api/BaseMethod.ts +8 -10
  110. package/src/api/CheckAllFirmwareRelease.ts +40 -53
  111. package/src/api/FirmwareUpdateV2.ts +120 -298
  112. package/src/api/FirmwareUpdateV3.ts +57 -263
  113. package/src/api/FirmwareUpdateV4.ts +307 -912
  114. package/src/api/GetPassphraseState.ts +7 -1
  115. package/src/api/OpenWalletSession.ts +54 -17
  116. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  117. package/src/api/device/DeviceUnlock.ts +1 -1
  118. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  119. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -57
  120. package/src/api/firmware/updateBootloader.ts +4 -19
  121. package/src/api/firmware/uploadFirmware.ts +22 -140
  122. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  123. package/src/api/index.ts +1 -0
  124. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  125. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  126. package/src/core/deviceEventRegistration.ts +4 -0
  127. package/src/core/index.ts +136 -77
  128. package/src/data/messages/messages-protocol-v2.json +68 -0
  129. package/src/data-manager/DataManager.ts +63 -38
  130. package/src/data-manager/connectSettings.ts +0 -11
  131. package/src/device/Device.ts +203 -21
  132. package/src/events/device.ts +4 -0
  133. package/src/events/logBlockEvent.ts +1 -0
  134. package/src/events/ui-request.ts +33 -2
  135. package/src/index.ts +0 -5
  136. package/src/inject.ts +3 -22
  137. package/src/lowLevelInject.ts +1 -5
  138. package/src/protocols/protocol-v2/features.ts +9 -2
  139. package/src/protocols/protocol-v2/resources.ts +233 -0
  140. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  141. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  142. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +124 -0
  143. package/src/protocols/protocol-v2/walletSession.ts +154 -30
  144. package/src/topLevelInject.ts +1 -5
  145. package/src/types/api/checkAllFirmwareRelease.ts +1 -4
  146. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  147. package/src/types/api/export.ts +0 -18
  148. package/src/types/api/firmwareUpdate.ts +0 -76
  149. package/src/types/api/index.ts +2 -14
  150. package/src/types/api/protocolV2.ts +13 -0
  151. package/src/types/settings.ts +26 -13
  152. package/src/utils/deviceFeaturesUtils.ts +4 -0
  153. package/src/utils/homescreen.ts +32 -6
  154. package/src/utils/index.ts +6 -1
  155. package/src/utils/pro2Nft.ts +142 -0
  156. package/src/utils/pro2Wallpaper.ts +35 -8
  157. package/src/utils/uiProgressThrottle.ts +63 -0
  158. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  159. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  160. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  161. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  162. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  163. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  164. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  165. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  166. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  167. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  168. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  169. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  170. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  171. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  172. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  173. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  174. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  175. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  176. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  177. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  178. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  179. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  180. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  181. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  182. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  183. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  184. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  185. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  186. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  187. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  188. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  189. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  190. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  191. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  192. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  193. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  194. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  195. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  196. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  197. package/src/api/firmware/progressThrottle.ts +0 -44
  198. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  199. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  200. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  201. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  202. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -31,8 +31,6 @@ export type ConnectSettings = {
31
31
  timestamp: number;
32
32
  isFrame?: boolean;
33
33
  preRelease?: boolean;
34
- firmwareManifestMode?: 'sdk-managed' | 'external-only';
35
- preloadedConfig?: RemoteConfigResponse;
36
34
  fetchConfig?: boolean;
37
35
  extension?: string;
38
36
  configFetcher?: (url: string) => Promise<RemoteConfigResponse | null>;
@@ -60,18 +58,39 @@ export type IProtocolV2FirmwareComponent = {
60
58
  fingerprint?: string;
61
59
  /** OKPP payload hash, used to detect same-version package changes. */
62
60
  payloadHash?: string;
63
- expectedSize?: number;
64
61
  version?: IVersionArray;
65
62
  };
66
63
 
64
+ export type IProtocolV2ResourceType =
65
+ | 'images'
66
+ | 'animation'
67
+ | 'wallpaper'
68
+ | 'translations'
69
+ | 'roobert'
70
+ | 'noto';
71
+
72
+ /** Pro2 RES package descriptor. Hashes identify content without a human-managed version. */
73
+ export type IProtocolV2Resource = {
74
+ type: IProtocolV2ResourceType;
75
+ url: string;
76
+ /** Complete okpkg file size in bytes. */
77
+ size: number;
78
+ /** SHA-256 of the complete okpkg file. */
79
+ fileHash: string;
80
+ /** SHA3-512 header_hash from the signed okpkg header. */
81
+ headerHash: string;
82
+ };
83
+
84
+ export type IProtocolV2Resources = {
85
+ stable: IProtocolV2Resource[];
86
+ };
87
+
67
88
  /** Pro2 RESC bundle okpkg descriptor for incremental FileWrite synchronization. */
68
89
  export type IProtocolV2ResourceBundle = {
69
90
  /** Bundle name, such as images, animation, translations, or fonts_roobert. */
70
91
  name: string;
71
92
  /** Download URL. */
72
93
  url: string;
73
- fingerprint?: string;
74
- expectedSize?: number;
75
94
  /** Device target path, such as vol0:/bundles/images/images.okpkg. */
76
95
  devicePath: string;
77
96
  /** okpkg payload_version used to skip matching content after FileRead. */
@@ -93,16 +112,10 @@ export type IFirmwareReleaseInfo = {
93
112
  firmwareType?: EFirmwareType;
94
113
  /** Firmware UI resource */
95
114
  resource?: string;
96
- resourceFingerprint?: string;
97
- resourceExpectedSize?: number;
98
115
  /** Firmware full UI resource */
99
116
  fullResource?: string;
100
- fullResourceFingerprint?: string;
101
- fullResourceExpectedSize?: number;
102
117
  fullResourceRange?: string[];
103
118
  bootloaderResource?: string;
104
- bootloaderFingerprint?: string;
105
- bootloaderExpectedSize?: number;
106
119
  bootloaderVersion?: IVersionArray;
107
120
  displayBootloaderVersion?: IVersionArray;
108
121
  bootloaderRelatedFirmwareVersion?: IVersionArray;
@@ -115,7 +128,6 @@ export type IFirmwareReleaseInfo = {
115
128
  [k in ILocale]: string;
116
129
  };
117
130
  fingerprint: string;
118
- expectedSize?: number;
119
131
  version: IVersionArray;
120
132
  changelog: {
121
133
  [k in ILocale]: string;
@@ -131,7 +143,6 @@ export type IBLEFirmwareReleaseInfo = {
131
143
  webUpdate: string;
132
144
  fingerprint: string;
133
145
  fingerprintWeb: string;
134
- expectedSize?: number;
135
146
  version: IVersionArray;
136
147
  changelog: {
137
148
  [k in ILocale]: string;
@@ -149,6 +160,8 @@ export type DeviceTypeMap = {
149
160
  'firmware-v8'?: IFirmwareReleaseInfo[];
150
161
  'firmware-btc-v8'?: IFirmwareReleaseInfo[];
151
162
  ble: IBLEFirmwareReleaseInfo[];
163
+ /** Independent Pro2 resource release configuration. */
164
+ resources?: IProtocolV2Resources;
152
165
  };
153
166
  };
154
167
 
@@ -106,6 +106,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
106
106
  expectPassphraseState?: string;
107
107
  onlyMainPin?: boolean;
108
108
  initSession?: boolean;
109
+ deriveCardano?: boolean;
109
110
  }
110
111
  ) => {
111
112
  if (device.isProtocolV2()) {
@@ -121,6 +122,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
121
122
  initSession: options?.initSession,
122
123
  expectedPassphraseState: options?.expectPassphraseState,
123
124
  onlyMainPin: options?.onlyMainPin,
125
+ deriveCardano: options?.deriveCardano,
124
126
  });
125
127
  }
126
128
 
@@ -185,6 +187,7 @@ export const getPassphraseState = async (
185
187
  expectPassphraseState?: string;
186
188
  onlyMainPin?: boolean;
187
189
  initSession?: boolean;
190
+ deriveCardano?: boolean;
188
191
  }
189
192
  ): Promise<{
190
193
  passphraseState: string | undefined;
@@ -205,6 +208,7 @@ export const getPassphraseState = async (
205
208
  initSession: options?.initSession,
206
209
  expectedPassphraseState: options?.expectPassphraseState,
207
210
  onlyMainPin: options?.onlyMainPin,
211
+ deriveCardano: options?.deriveCardano,
208
212
  });
209
213
  }
210
214
 
@@ -4,6 +4,12 @@ import { EDeviceType } from '@onekeyfe/hd-shared';
4
4
  import { getDeviceType } from './deviceInfoUtils';
5
5
  import { getDeviceFirmwareVersion } from './deviceVersionUtils';
6
6
  import { PRO2_WALLPAPER_HEIGHT, PRO2_WALLPAPER_WIDTH } from './pro2Wallpaper';
7
+ import {
8
+ PRO2_NFT_IMAGE_HEIGHT,
9
+ PRO2_NFT_IMAGE_WIDTH,
10
+ PRO2_NFT_THUMBNAIL_HEIGHT,
11
+ PRO2_NFT_THUMBNAIL_WIDTH,
12
+ } from './pro2Nft';
7
13
 
8
14
  import type { Features, IDeviceType } from '../types';
9
15
 
@@ -302,6 +308,31 @@ type SizeConfig = {
302
308
  radius?: number;
303
309
  };
304
310
 
311
+ export const getNftSize = ({
312
+ deviceType,
313
+ thumbnail,
314
+ }: {
315
+ deviceType: IDeviceType;
316
+ thumbnail?: boolean;
317
+ }): SizeConfig | undefined => {
318
+ const sizes: Partial<Record<IDeviceType, { full: SizeConfig; thumbnail: SizeConfig }>> = {
319
+ touch: {
320
+ full: { width: 480, height: 800 },
321
+ thumbnail: { width: 238, height: 238 },
322
+ },
323
+ pro: {
324
+ full: { width: 480, height: 800 },
325
+ thumbnail: { width: 226, height: 226, radius: 40 },
326
+ },
327
+ pro2: {
328
+ full: { width: PRO2_NFT_IMAGE_WIDTH, height: PRO2_NFT_IMAGE_HEIGHT },
329
+ thumbnail: { width: PRO2_NFT_THUMBNAIL_WIDTH, height: PRO2_NFT_THUMBNAIL_HEIGHT },
330
+ },
331
+ };
332
+
333
+ return sizes[deviceType]?.[thumbnail ? 'thumbnail' : 'full'];
334
+ };
335
+
305
336
  export const getHomeScreenSize = ({
306
337
  deviceType,
307
338
  homeScreenType,
@@ -312,12 +343,7 @@ export const getHomeScreenSize = ({
312
343
  thumbnail?: boolean;
313
344
  }) => {
314
345
  if (deviceType === EDeviceType.Pro2) {
315
- return thumbnail
316
- ? undefined
317
- : {
318
- width: PRO2_WALLPAPER_WIDTH,
319
- height: PRO2_WALLPAPER_HEIGHT,
320
- };
346
+ return thumbnail ? undefined : { width: PRO2_WALLPAPER_WIDTH, height: PRO2_WALLPAPER_HEIGHT };
321
347
  }
322
348
 
323
349
  const sizes: Partial<
@@ -54,7 +54,12 @@ export { getHDPath, getScriptType, getOutputScriptType } from '../api/helpers/pa
54
54
 
55
55
  export const isBleConnect = (env: string) => env === 'react-native' || env === 'lowlevel';
56
56
 
57
- export { getHomeScreenHex, getHomeScreenDefaultList, getHomeScreenSize } from './homescreen';
57
+ export {
58
+ getHomeScreenHex,
59
+ getHomeScreenDefaultList,
60
+ getHomeScreenSize,
61
+ getNftSize,
62
+ } from './homescreen';
58
63
  export { encodePro2Wallpaper, PRO2_WALLPAPER_HEIGHT, PRO2_WALLPAPER_WIDTH } from './pro2Wallpaper';
59
64
  export type { Pro2WallpaperColorFormat } from './pro2Wallpaper';
60
65
 
@@ -0,0 +1,142 @@
1
+ import { blake2s } from '@noble/hashes/blake2s';
2
+ import { bytesToHex } from '@noble/hashes/utils';
3
+
4
+ import { invalidParameter } from '../api/helpers/filesystemValidation';
5
+ import { encodePro2Image } from './pro2Wallpaper';
6
+
7
+ export const PRO2_NFT_IMAGE_WIDTH = 540;
8
+ export const PRO2_NFT_IMAGE_HEIGHT = 540;
9
+ export const PRO2_NFT_THUMBNAIL_WIDTH = 263;
10
+ export const PRO2_NFT_THUMBNAIL_HEIGHT = 263;
11
+ export const PRO2_NFT_DIRECTORY = 'vol1:/nft';
12
+ export const PRO2_NFT_DEFAULT_CHUNK_SIZE = 512;
13
+ export const PRO2_NFT_DEFAULT_PACE_MS = 20;
14
+ export const PRO2_NFT_DEFAULT_TIMEOUT_MS = 15_000;
15
+ export const PRO2_NFT_MIN_CHUNK_SIZE = 64;
16
+ export const PRO2_NFT_MAX_CHUNK_SIZE = 2048;
17
+ export const PRO2_NFT_MAX_ITEMS = 10;
18
+
19
+ export type Pro2NftImage = {
20
+ width: number;
21
+ height: number;
22
+ rgba: Uint8Array | ArrayBuffer;
23
+ };
24
+
25
+ export type Pro2NftBundle = {
26
+ basename: string;
27
+ image: Uint8Array;
28
+ thumbnail: Uint8Array;
29
+ metadata: Uint8Array;
30
+ };
31
+
32
+ const PRO2_NFT_BASENAME_PATTERN = /^nft-[0-9a-f]{8}-[1-9][0-9]*$/;
33
+
34
+ type Pro2NftFileType = 'image' | 'thumbnail' | 'metadata';
35
+
36
+ function parsePro2NftFile(listedPath: string):
37
+ | {
38
+ basename: string;
39
+ fileType: Pro2NftFileType;
40
+ }
41
+ | undefined {
42
+ const filename = listedPath.trim().split('/').at(-1);
43
+ if (!filename) return undefined;
44
+
45
+ let basename: string | undefined;
46
+ let fileType: Pro2NftFileType | undefined;
47
+ if (filename.endsWith('_m.bin')) {
48
+ basename = filename.slice(0, -'_m.bin'.length);
49
+ fileType = 'thumbnail';
50
+ } else if (filename.endsWith('.json')) {
51
+ basename = filename.slice(0, -'.json'.length);
52
+ fileType = 'metadata';
53
+ } else if (filename.endsWith('.bin')) {
54
+ basename = filename.slice(0, -'.bin'.length);
55
+ fileType = 'image';
56
+ }
57
+
58
+ return basename && fileType && PRO2_NFT_BASENAME_PATTERN.test(basename)
59
+ ? { basename, fileType }
60
+ : undefined;
61
+ }
62
+
63
+ export function getCompletePro2NftBasenames(childFiles?: string): Set<string> {
64
+ const filesByBasename = new Map<string, Set<Pro2NftFileType>>();
65
+
66
+ for (const listedPath of childFiles?.split('\n') ?? []) {
67
+ const file = parsePro2NftFile(listedPath);
68
+ if (file) {
69
+ const fileTypes = filesByBasename.get(file.basename) ?? new Set<Pro2NftFileType>();
70
+ fileTypes.add(file.fileType);
71
+ filesByBasename.set(file.basename, fileTypes);
72
+ }
73
+ }
74
+
75
+ return new Set(
76
+ [...filesByBasename.entries()]
77
+ .filter(([, fileTypes]) => fileTypes.size === 3)
78
+ .map(([basename]) => basename)
79
+ );
80
+ }
81
+
82
+ function utf8Length(value: string): number {
83
+ return new TextEncoder().encode(value).byteLength;
84
+ }
85
+
86
+ function assertImage(
87
+ name: string,
88
+ image: Pro2NftImage,
89
+ expectedWidth: number,
90
+ expectedHeight: number
91
+ ) {
92
+ if (image.width !== expectedWidth || image.height !== expectedHeight) {
93
+ throw invalidParameter(
94
+ `Pro2 NFT ${name} dimensions must be ${expectedWidth}x${expectedHeight}.`
95
+ );
96
+ }
97
+ if (!(image.rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(image.rgba)) {
98
+ throw invalidParameter(`Parameter [${name}.rgba] must be an ArrayBuffer or Uint8Array.`);
99
+ }
100
+ }
101
+
102
+ export function buildPro2NftBundle(options: {
103
+ image: Pro2NftImage;
104
+ thumbnail: Pro2NftImage;
105
+ title: string;
106
+ subtitle: string;
107
+ timestampMs: number;
108
+ }): Pro2NftBundle {
109
+ const { image, thumbnail, title, subtitle, timestampMs } = options;
110
+ assertImage('image', image, PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
111
+ assertImage('thumbnail', thumbnail, PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT);
112
+ const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
113
+ const subtitleLength =
114
+ typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
115
+ if (titleLength < 1 || titleLength > 63) {
116
+ throw invalidParameter('Pro2 NFT title must contain 1 to 63 UTF-8 bytes.');
117
+ }
118
+ if (subtitleLength > 95) {
119
+ throw invalidParameter('Pro2 NFT subtitle must contain at most 95 UTF-8 bytes.');
120
+ }
121
+ if (!Number.isSafeInteger(timestampMs) || timestampMs <= 0) {
122
+ throw invalidParameter('Parameter [timestampMs] must be a positive safe integer.');
123
+ }
124
+
125
+ const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
126
+ const encodedThumbnail = encodePro2Image({
127
+ ...thumbnail,
128
+ alphaMode: 'black-background',
129
+ }).data;
130
+ const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
131
+ if (metadata.byteLength === 0 || metadata.byteLength > 512) {
132
+ throw invalidParameter('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
133
+ }
134
+
135
+ const hash8 = bytesToHex(blake2s(encodedImage)).slice(0, 8);
136
+ return {
137
+ basename: `nft-${hash8}-${timestampMs}`,
138
+ image: encodedImage,
139
+ thumbnail: encodedThumbnail,
140
+ metadata,
141
+ };
142
+ }
@@ -8,6 +8,8 @@ export const PRO2_WALLPAPER_HEIGHT = 1024;
8
8
 
9
9
  export type Pro2WallpaperColorFormat = 'RGB565' | 'RGB565A8';
10
10
 
11
+ export type Pro2ImageAlphaMode = 'preserve' | 'black-background';
12
+
11
13
  const COLOR_FORMAT_RGB565 = 0x12;
12
14
  const COLOR_FORMAT_RGB565A8 = 0x14;
13
15
 
@@ -36,10 +38,11 @@ function align(value: number, boundary: number): number {
36
38
  return Math.ceil(value / boundary) * boundary;
37
39
  }
38
40
 
39
- export function encodePro2Wallpaper(options: {
41
+ export function encodePro2Image(options: {
40
42
  width: number;
41
43
  height: number;
42
44
  rgba: Uint8Array | ArrayBuffer;
45
+ alphaMode?: Pro2ImageAlphaMode;
43
46
  }): { data: Uint8Array; colorFormat: Pro2WallpaperColorFormat } {
44
47
  const { width, height } = options;
45
48
  if (!Number.isInteger(width) || width <= 0 || width > 0xffff) {
@@ -57,11 +60,14 @@ export function encodePro2Wallpaper(options: {
57
60
  );
58
61
  }
59
62
 
63
+ const alphaMode = options.alphaMode ?? 'preserve';
60
64
  let hasTransparency = false;
61
- for (let index = 3; index < rgba.length; index += 4) {
62
- if (rgba[index] !== 0xff) {
63
- hasTransparency = true;
64
- break;
65
+ if (alphaMode === 'preserve') {
66
+ for (let index = 3; index < rgba.length; index += 4) {
67
+ if (rgba[index] !== 0xff) {
68
+ hasTransparency = true;
69
+ break;
70
+ }
65
71
  }
66
72
  }
67
73
 
@@ -85,9 +91,22 @@ export function encodePro2Wallpaper(options: {
85
91
  for (let x = 0; x < width; x += 1) {
86
92
  const sourceOffset = (y * width + x) * 4;
87
93
  const thresholdIndex = ((y & 7) << 3) + (x & 7);
88
- const red = Math.min(rgba[sourceOffset] + RED_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
89
- const green = Math.min(rgba[sourceOffset + 1] + GREEN_THRESHOLD[thresholdIndex], 0xff) & 0xfc;
90
- const blue = Math.min(rgba[sourceOffset + 2] + BLUE_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
94
+ const alpha = rgba[sourceOffset + 3];
95
+ const redChannel =
96
+ alphaMode === 'black-background'
97
+ ? Math.round((rgba[sourceOffset] * alpha) / 0xff)
98
+ : rgba[sourceOffset];
99
+ const greenChannel =
100
+ alphaMode === 'black-background'
101
+ ? Math.round((rgba[sourceOffset + 1] * alpha) / 0xff)
102
+ : rgba[sourceOffset + 1];
103
+ const blueChannel =
104
+ alphaMode === 'black-background'
105
+ ? Math.round((rgba[sourceOffset + 2] * alpha) / 0xff)
106
+ : rgba[sourceOffset + 2];
107
+ const red = Math.min(redChannel + RED_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
108
+ const green = Math.min(greenChannel + GREEN_THRESHOLD[thresholdIndex], 0xff) & 0xfc;
109
+ const blue = Math.min(blueChannel + BLUE_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
91
110
  const rgb565 = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
92
111
  const rgbOffset = 12 + y * stride + x * 2;
93
112
  data[rgbOffset] = rgb565 & 0xff;
@@ -101,3 +120,11 @@ export function encodePro2Wallpaper(options: {
101
120
 
102
121
  return { data, colorFormat };
103
122
  }
123
+
124
+ export function encodePro2Wallpaper(options: {
125
+ width: number;
126
+ height: number;
127
+ rgba: Uint8Array | ArrayBuffer;
128
+ }): { data: Uint8Array; colorFormat: Pro2WallpaperColorFormat } {
129
+ return encodePro2Image(options);
130
+ }
@@ -0,0 +1,63 @@
1
+ import { UI_EVENT, UI_REQUEST } from '../events/ui-request';
2
+
3
+ import type { CoreMessage } from '../events';
4
+
5
+ const DEFAULT_UI_PROGRESS_INTERVAL_MS = 250;
6
+
7
+ type UiProgressThrottleState = {
8
+ lastEmittedAt: number;
9
+ lastProgress?: number;
10
+ };
11
+
12
+ const normalizeProgress = (progress: unknown) => {
13
+ if (typeof progress !== 'number' || !Number.isFinite(progress)) {
14
+ return undefined;
15
+ }
16
+
17
+ return Math.max(0, Math.min(100, Math.floor(progress)));
18
+ };
19
+
20
+ export const createUiProgressMessageFilter = (
21
+ intervalMs = DEFAULT_UI_PROGRESS_INTERVAL_MS,
22
+ now = Date.now
23
+ ) => {
24
+ const stateByType = new Map<string, UiProgressThrottleState>();
25
+
26
+ return (message: CoreMessage) => {
27
+ if (
28
+ !('event' in message) ||
29
+ message.event !== UI_EVENT ||
30
+ (message.type !== UI_REQUEST.DEVICE_PROGRESS && message.type !== UI_REQUEST.FIRMWARE_PROGRESS)
31
+ ) {
32
+ return true;
33
+ }
34
+
35
+ const key =
36
+ message.type === UI_REQUEST.FIRMWARE_PROGRESS
37
+ ? `${message.type}:${message.payload.progressType}`
38
+ : message.type;
39
+ const progress = normalizeProgress(message.payload.progress);
40
+ const currentTime = now();
41
+ const state = stateByType.get(key);
42
+
43
+ if (!state) {
44
+ stateByType.set(key, { lastEmittedAt: currentTime, lastProgress: progress });
45
+ return true;
46
+ }
47
+
48
+ const isBoundary = progress === 0 || progress === 100;
49
+ if (isBoundary && progress === state.lastProgress) {
50
+ return false;
51
+ }
52
+
53
+ const startsNewPhase =
54
+ progress !== undefined && state.lastProgress !== undefined && progress < state.lastProgress;
55
+
56
+ if (!isBoundary && !startsNewPhase && currentTime - state.lastEmittedAt < intervalMs) {
57
+ return false;
58
+ }
59
+
60
+ stateByType.set(key, { lastEmittedAt: currentTime, lastProgress: progress });
61
+ return true;
62
+ };
63
+ };
@@ -1,175 +0,0 @@
1
- import { EDeviceType, EFirmwareType, ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
- import CheckAllFirmwareRelease from '../../src/api/CheckAllFirmwareRelease';
4
- import { buildFirmwareUpdatePlan } from '../../src/api/firmware/FirmwareUpdatePlan';
5
- import {
6
- getBleFirmwareReleaseInfo,
7
- getBootloaderReleaseInfo,
8
- getFirmwareReleaseInfo,
9
- } from '../../src/api/firmware/releaseHelper';
10
-
11
- jest.mock('../../src/data/config', () => ({
12
- getSDKVersion: jest.fn(() => '1.0.0'),
13
- DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
14
- }));
15
-
16
- jest.mock('../../src/api/firmware/FirmwareUpdatePlan', () => ({
17
- buildFirmwareUpdatePlan: jest.fn(),
18
- validateFirmwareUpdatePlanForceTargets: jest.fn(value => value ?? []),
19
- }));
20
-
21
- jest.mock('../../src/api/firmware/releaseHelper', () => ({
22
- getBleFirmwareReleaseInfo: jest.fn(),
23
- getBootloaderReleaseInfo: jest.fn(),
24
- getFirmwareReleaseInfo: jest.fn(),
25
- }));
26
-
27
- const mockBuildFirmwareUpdatePlan = buildFirmwareUpdatePlan as jest.MockedFunction<
28
- typeof buildFirmwareUpdatePlan
29
- >;
30
- const mockGetBleFirmwareReleaseInfo = getBleFirmwareReleaseInfo as jest.MockedFunction<
31
- typeof getBleFirmwareReleaseInfo
32
- >;
33
- const mockGetBootloaderReleaseInfo = getBootloaderReleaseInfo as jest.MockedFunction<
34
- typeof getBootloaderReleaseInfo
35
- >;
36
- const mockGetFirmwareReleaseInfo = getFirmwareReleaseInfo as jest.MockedFunction<
37
- typeof getFirmwareReleaseInfo
38
- >;
39
-
40
- const noUpdate = {
41
- status: 'valid' as const,
42
- release: undefined,
43
- };
44
-
45
- const createMethod = (serialNo = 'device-id') => {
46
- const method = new CheckAllFirmwareRelease({
47
- id: 1,
48
- payload: {
49
- method: 'checkAllFirmwareRelease',
50
- platform: 'native',
51
- },
52
- });
53
- method.init();
54
- method.device = {
55
- isProtocolV2: jest.fn(() => false),
56
- features: {
57
- deviceType: EDeviceType.Classic1s,
58
- serialNo,
59
- firmwareType: EFirmwareType.Universal,
60
- firmwareVersion: '1.0.0',
61
- bootloaderVersion: '1.0.0',
62
- },
63
- } as typeof method.device;
64
- return method;
65
- };
66
-
67
- const createMethodWithForceTargets = () => {
68
- const method = createMethod();
69
- method.payload = {
70
- ...method.payload,
71
- forceUpdateTargets: ['firmware', 'resource'],
72
- };
73
- return method;
74
- };
75
-
76
- describe('CheckAllFirmwareRelease', () => {
77
- beforeEach(() => {
78
- jest.clearAllMocks();
79
- mockGetFirmwareReleaseInfo.mockReturnValue(noUpdate);
80
- mockGetBootloaderReleaseInfo.mockReturnValue(noUpdate);
81
- mockGetBleFirmwareReleaseInfo.mockReturnValue(noUpdate);
82
- });
83
-
84
- test('keeps release information available when an optional Plan cannot be built', async () => {
85
- const firmware = {
86
- status: 'outdated' as const,
87
- release: {
88
- url: 'https://firmware.onekey.so/classic/firmware.bin',
89
- version: [3, 0, 0],
90
- },
91
- };
92
- mockGetFirmwareReleaseInfo.mockReturnValue(firmware);
93
- mockBuildFirmwareUpdatePlan.mockImplementation(() => {
94
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Plan cannot be built', {
95
- firmwareUpdateCode: 'FirmwarePlanInvalid',
96
- });
97
- });
98
-
99
- await expect(createMethod('').run()).resolves.toEqual(
100
- expect.objectContaining({
101
- firmware,
102
- bootloader: noUpdate,
103
- ble: noUpdate,
104
- firmwareUpdatePlan: undefined,
105
- })
106
- );
107
- });
108
-
109
- test('returns an optional Plan when the builder succeeds', async () => {
110
- const plan = {
111
- schemaVersion: 2 as const,
112
- planDigest: 'a'.repeat(64),
113
- executor: 'v3' as const,
114
- deviceIdentity: 'device-id',
115
- deviceModel: EDeviceType.Classic1s,
116
- firmwareType: EFirmwareType.Universal,
117
- platform: 'native' as const,
118
- artifacts: [],
119
- targetsToUpdate: [],
120
- };
121
- mockBuildFirmwareUpdatePlan.mockReturnValue(plan);
122
-
123
- await expect(createMethod().run()).resolves.toEqual(
124
- expect.objectContaining({
125
- firmwareUpdatePlan: plan,
126
- })
127
- );
128
- });
129
-
130
- test('passes host-selected update targets to the Plan builder', async () => {
131
- mockBuildFirmwareUpdatePlan.mockReturnValue({
132
- schemaVersion: 2,
133
- planDigest: 'a'.repeat(64),
134
- executor: 'v2',
135
- deviceIdentity: 'device-id',
136
- deviceModel: EDeviceType.Classic1s,
137
- firmwareType: EFirmwareType.Universal,
138
- platform: 'native',
139
- artifacts: [],
140
- targetsToUpdate: [],
141
- });
142
-
143
- await createMethodWithForceTargets().run();
144
-
145
- expect(mockBuildFirmwareUpdatePlan).toHaveBeenCalledWith(
146
- expect.objectContaining({
147
- forceUpdateTargets: ['firmware', 'resource'],
148
- })
149
- );
150
- });
151
-
152
- test('does not hide a Plan failure for an explicitly forced target', async () => {
153
- const planError = ERRORS.TypedError(
154
- HardwareErrorCode.RuntimeError,
155
- 'Forced firmware target is unavailable',
156
- {
157
- firmwareUpdateCode: 'FirmwarePlanInvalid',
158
- }
159
- );
160
- mockBuildFirmwareUpdatePlan.mockImplementation(() => {
161
- throw planError;
162
- });
163
-
164
- await expect(createMethodWithForceTargets().run()).rejects.toBe(planError);
165
- });
166
-
167
- test('does not hide unexpected Plan builder failures', async () => {
168
- const unexpectedError = new Error('unexpected builder failure');
169
- mockBuildFirmwareUpdatePlan.mockImplementation(() => {
170
- throw unexpectedError;
171
- });
172
-
173
- await expect(createMethod().run()).rejects.toBe(unexpectedError);
174
- });
175
- });