@onekeyfe/hd-core 1.2.0-alpha.54 → 1.2.0-alpha.56

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 (262) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +231 -0
  2. package/__tests__/DeviceCommands.test.ts +45 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
  5. package/__tests__/connectSettings.test.ts +5 -47
  6. package/__tests__/device-lifecycle-events.test.ts +105 -3
  7. package/__tests__/device-state-contract.test.ts +1 -0
  8. package/__tests__/device-state-mapper.test.ts +1 -1
  9. package/__tests__/deviceSettings.test.ts +1 -1
  10. package/__tests__/deviceUploadNft.test.ts +293 -0
  11. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  12. package/__tests__/get-device-state.test.ts +132 -12
  13. package/__tests__/homescreen.test.ts +41 -1
  14. package/__tests__/kaspaSignTransaction.test.ts +3 -26
  15. package/__tests__/logBlockEvent.test.ts +1 -1
  16. package/__tests__/method-protocol-support.test.ts +19 -0
  17. package/__tests__/open-wallet-session.test.ts +568 -37
  18. package/__tests__/pro2Nft.test.ts +108 -0
  19. package/__tests__/protocol-binding.test.ts +89 -0
  20. package/__tests__/protocol-v2-resources.test.ts +286 -0
  21. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  22. package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
  23. package/__tests__/protocol-v2.test.ts +1353 -1011
  24. package/__tests__/protocolV2FileWrite.test.ts +39 -0
  25. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  26. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  27. package/__tests__/search-devices.test.ts +12 -3
  28. package/__tests__/uiProgressThrottle.test.ts +74 -0
  29. package/__tests__/uiPromiseRegistry.test.ts +86 -0
  30. package/dist/api/BaseMethod.d.ts +3 -1
  31. package/dist/api/BaseMethod.d.ts.map +1 -1
  32. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
  33. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  34. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  35. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  36. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  37. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  38. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  39. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  40. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  41. package/dist/api/FirmwareUpdateV4.d.ts +11 -25
  42. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  43. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  44. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  45. package/dist/api/SearchDevices.d.ts.map +1 -1
  46. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  47. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  48. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  49. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  50. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  51. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -9
  52. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  53. package/dist/api/firmware/getBinary.d.ts +0 -7
  54. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  55. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  56. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  57. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  58. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  59. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  60. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  61. package/dist/api/index.d.ts +2 -0
  62. package/dist/api/index.d.ts.map +1 -1
  63. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  64. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  65. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  66. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  67. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  68. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  69. package/dist/core/deviceEventRegistration.d.ts +2 -0
  70. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  71. package/dist/core/index.d.ts.map +1 -1
  72. package/dist/core/uiPromiseRegistry.d.ts +4 -0
  73. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  74. package/dist/data-manager/DataManager.d.ts +4 -2
  75. package/dist/data-manager/DataManager.d.ts.map +1 -1
  76. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  77. package/dist/device/Device.d.ts +29 -6
  78. package/dist/device/Device.d.ts.map +1 -1
  79. package/dist/device/DeviceCommands.d.ts +4 -4
  80. package/dist/device/DeviceCommands.d.ts.map +1 -1
  81. package/dist/device/DevicePool.d.ts.map +1 -1
  82. package/dist/events/device.d.ts +4 -0
  83. package/dist/events/device.d.ts.map +1 -1
  84. package/dist/events/logBlockEvent.d.ts.map +1 -1
  85. package/dist/events/ui-promise.d.ts +4 -2
  86. package/dist/events/ui-promise.d.ts.map +1 -1
  87. package/dist/events/ui-request.d.ts +31 -2
  88. package/dist/events/ui-request.d.ts.map +1 -1
  89. package/dist/events/ui-response.d.ts +10 -2
  90. package/dist/events/ui-response.d.ts.map +1 -1
  91. package/dist/index.d.ts +214 -231
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +2634 -3530
  94. package/dist/inject.d.ts +6 -1
  95. package/dist/inject.d.ts.map +1 -1
  96. package/dist/lowLevelInject.d.ts.map +1 -1
  97. package/dist/protocols/protocol-v2/features.d.ts +0 -4
  98. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  99. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  100. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  101. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  102. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  103. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  104. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  105. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  106. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  107. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  108. package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
  109. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  110. package/dist/topLevelInject.d.ts.map +1 -1
  111. package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -4
  112. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  113. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  114. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  115. package/dist/types/api/deviceUnlock.d.ts +5 -1
  116. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  117. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  118. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  119. package/dist/types/api/export.d.ts +2 -4
  120. package/dist/types/api/export.d.ts.map +1 -1
  121. package/dist/types/api/firmwareUpdate.d.ts +2 -70
  122. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  123. package/dist/types/api/index.d.ts +7 -10
  124. package/dist/types/api/index.d.ts.map +1 -1
  125. package/dist/types/api/protocolV2.d.ts +3 -0
  126. package/dist/types/api/protocolV2.d.ts.map +1 -1
  127. package/dist/types/device.d.ts.map +1 -1
  128. package/dist/types/params.d.ts +1 -0
  129. package/dist/types/params.d.ts.map +1 -1
  130. package/dist/types/settings.d.ts +22 -13
  131. package/dist/types/settings.d.ts.map +1 -1
  132. package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
  133. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  134. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  135. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  136. package/dist/utils/homescreen.d.ts +4 -0
  137. package/dist/utils/homescreen.d.ts.map +1 -1
  138. package/dist/utils/index.d.ts +1 -1
  139. package/dist/utils/index.d.ts.map +1 -1
  140. package/dist/utils/patch.d.ts +1 -1
  141. package/dist/utils/patch.d.ts.map +1 -1
  142. package/dist/utils/pro2Nft.d.ts +31 -0
  143. package/dist/utils/pro2Nft.d.ts.map +1 -0
  144. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  145. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  146. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  147. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  148. package/package.json +4 -4
  149. package/src/api/BaseMethod.ts +8 -10
  150. package/src/api/CheckAllFirmwareRelease.ts +54 -57
  151. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  152. package/src/api/FirmwareUpdate.ts +7 -15
  153. package/src/api/FirmwareUpdateV2.ts +126 -316
  154. package/src/api/FirmwareUpdateV3.ts +63 -265
  155. package/src/api/FirmwareUpdateV4.ts +584 -975
  156. package/src/api/GetPassphraseState.ts +7 -1
  157. package/src/api/OpenWalletSession.ts +70 -17
  158. package/src/api/SearchDevices.ts +3 -1
  159. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  160. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +34 -11
  161. package/src/api/device/DeviceUnlock.ts +8 -3
  162. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  163. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
  164. package/src/api/firmware/updateBootloader.ts +4 -19
  165. package/src/api/firmware/uploadFirmware.ts +39 -144
  166. package/src/api/helpers/protocolV2FileWrite.ts +27 -7
  167. package/src/api/index.ts +2 -0
  168. package/src/api/kaspa/KaspaSignTransaction.ts +1 -12
  169. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  170. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  171. package/src/api/protocol-v2/helpers.ts +1 -0
  172. package/src/core/deviceEventRegistration.ts +4 -0
  173. package/src/core/index.ts +167 -83
  174. package/src/core/uiPromiseRegistry.ts +41 -0
  175. package/src/data/messages/messages-protocol-v2.json +25 -0
  176. package/src/data-manager/DataManager.ts +71 -39
  177. package/src/data-manager/connectSettings.ts +0 -11
  178. package/src/device/Device.ts +259 -43
  179. package/src/device/DeviceCommands.ts +7 -1
  180. package/src/device/DevicePool.ts +7 -2
  181. package/src/events/device.ts +4 -0
  182. package/src/events/logBlockEvent.ts +1 -0
  183. package/src/events/ui-promise.ts +4 -2
  184. package/src/events/ui-request.ts +36 -2
  185. package/src/events/ui-response.ts +12 -2
  186. package/src/index.ts +0 -5
  187. package/src/inject.ts +60 -24
  188. package/src/lowLevelInject.ts +7 -8
  189. package/src/protocols/protocol-v2/features.ts +10 -7
  190. package/src/protocols/protocol-v2/firmware.ts +2 -0
  191. package/src/protocols/protocol-v2/resources.ts +359 -0
  192. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  193. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  194. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +118 -0
  195. package/src/protocols/protocol-v2/walletSession.ts +214 -36
  196. package/src/topLevelInject.ts +7 -8
  197. package/src/types/api/checkAllFirmwareRelease.ts +2 -4
  198. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  199. package/src/types/api/deviceUnlock.ts +12 -1
  200. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  201. package/src/types/api/export.ts +1 -18
  202. package/src/types/api/firmwareUpdate.ts +3 -76
  203. package/src/types/api/index.ts +13 -14
  204. package/src/types/api/protocolV2.ts +13 -0
  205. package/src/types/device.ts +3 -0
  206. package/src/types/params.ts +7 -1
  207. package/src/types/settings.ts +42 -13
  208. package/src/utils/deviceFeaturesCompat.ts +0 -6
  209. package/src/utils/deviceFeaturesUtils.ts +8 -0
  210. package/src/utils/homescreen.ts +32 -6
  211. package/src/utils/index.ts +6 -1
  212. package/src/utils/pro2Nft.ts +142 -0
  213. package/src/utils/pro2Wallpaper.ts +35 -8
  214. package/src/utils/uiProgressThrottle.ts +63 -0
  215. package/__tests__/device-initialize-timeout.test.ts +0 -56
  216. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  217. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  218. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  219. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  220. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  221. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  222. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -294
  223. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  224. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  225. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  226. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  227. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  228. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  229. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  230. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  231. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  232. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  233. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  234. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  235. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  236. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  237. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
  238. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  239. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  240. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  241. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  242. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  243. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  244. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  245. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  246. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  247. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  248. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  249. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  250. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  251. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  252. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  253. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  254. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  255. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
  256. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -415
  257. package/src/api/firmware/progressThrottle.ts +0 -44
  258. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  259. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  260. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  261. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  262. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -0,0 +1,189 @@
1
+ import { ERRORS, HardwareErrorCode, createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
+
3
+ import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
4
+ import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
5
+ import {
6
+ PRO2_NFT_DEFAULT_CHUNK_SIZE,
7
+ PRO2_NFT_DEFAULT_PACE_MS,
8
+ PRO2_NFT_DEFAULT_TIMEOUT_MS,
9
+ PRO2_NFT_DIRECTORY,
10
+ PRO2_NFT_MAX_CHUNK_SIZE,
11
+ PRO2_NFT_MAX_ITEMS,
12
+ PRO2_NFT_MIN_CHUNK_SIZE,
13
+ type Pro2NftBundle,
14
+ type Pro2NftImage,
15
+ buildPro2NftBundle,
16
+ getCompletePro2NftBasenames,
17
+ } from '../../utils/pro2Nft';
18
+ import { BaseMethod } from '../BaseMethod';
19
+ import { invalidParameter } from '../helpers/filesystemValidation';
20
+ import { isProtocolV2ResponseTimeout, writeProtocolV2File } from '../helpers/protocolV2FileWrite';
21
+
22
+ export type DeviceUploadNftParams = {
23
+ image: Pro2NftImage;
24
+ thumbnail: Pro2NftImage;
25
+ title: string;
26
+ subtitle: string;
27
+ timestampMs?: number;
28
+ chunkSize?: number;
29
+ paceMs?: number;
30
+ timeoutMs?: number;
31
+ };
32
+
33
+ export type DeviceUploadNftResponse = {
34
+ basename: string;
35
+ imagePath: string;
36
+ thumbnailPath: string;
37
+ metadataPath: string;
38
+ totalSize: number;
39
+ nftUpdated: true;
40
+ message?: string;
41
+ };
42
+
43
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
44
+ const FILESYSTEM_DIR_LIST_MESSAGE_TYPE = 60808;
45
+ const NFT_UPDATE_MESSAGE_TYPE = 61500;
46
+
47
+ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
48
+ private bundle?: Pro2NftBundle;
49
+
50
+ getSupportedProtocols() {
51
+ return ['V2'] as const;
52
+ }
53
+
54
+ init() {
55
+ const {
56
+ image,
57
+ thumbnail,
58
+ title,
59
+ subtitle,
60
+ timestampMs = Date.now(),
61
+ chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE,
62
+ paceMs = PRO2_NFT_DEFAULT_PACE_MS,
63
+ timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS,
64
+ } = this.payload;
65
+ if (
66
+ !Number.isInteger(chunkSize) ||
67
+ chunkSize < PRO2_NFT_MIN_CHUNK_SIZE ||
68
+ chunkSize > PRO2_NFT_MAX_CHUNK_SIZE
69
+ ) {
70
+ throw invalidParameter(
71
+ `Parameter [chunkSize] must be an integer between ${PRO2_NFT_MIN_CHUNK_SIZE} and ${PRO2_NFT_MAX_CHUNK_SIZE}.`
72
+ );
73
+ }
74
+ if (!Number.isInteger(paceMs) || paceMs < 0) {
75
+ throw invalidParameter('Parameter [paceMs] must be a non-negative integer.');
76
+ }
77
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
78
+ throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
79
+ }
80
+
81
+ this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
82
+ this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
83
+ this.unlockPolicy = 'none';
84
+ this.skipForceUpdateCheck = true;
85
+ this.useDevicePassphraseState = false;
86
+ }
87
+
88
+ private async assertCapabilities() {
89
+ const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
90
+ const hasFileWrite = supportsProtocolV2Message(
91
+ protocolInfo,
92
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE
93
+ );
94
+ const hasDirList = supportsProtocolV2Message(protocolInfo, FILESYSTEM_DIR_LIST_MESSAGE_TYPE);
95
+ const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
96
+ if (!hasFileWrite || !hasDirList || !hasNftUpdate) {
97
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
98
+ }
99
+ }
100
+
101
+ private async assertStorageCapacity(basename: string) {
102
+ const { message } = await this.device.commands.typedCall(
103
+ 'FilesystemDirList',
104
+ 'FilesystemDir',
105
+ { path: PRO2_NFT_DIRECTORY, depth: 1 },
106
+ { timeoutMs: this.params.timeoutMs }
107
+ );
108
+ const existingBasenames = getCompletePro2NftBasenames(message.child_files);
109
+ if (existingBasenames.size >= PRO2_NFT_MAX_ITEMS && !existingBasenames.has(basename)) {
110
+ throw ERRORS.TypedError(HardwareErrorCode.NftStorageLimitReached, undefined, {
111
+ count: existingBasenames.size,
112
+ limit: PRO2_NFT_MAX_ITEMS,
113
+ });
114
+ }
115
+ }
116
+
117
+ private async updateNft(basename: string) {
118
+ const params = { file_name_no_ext: basename };
119
+ const options = { timeoutMs: this.params.timeoutMs };
120
+ try {
121
+ return await this.device.commands.typedCall('NftUpdate', 'Success', params, options);
122
+ } catch (error) {
123
+ if (!isProtocolV2ResponseTimeout(error)) {
124
+ throw error;
125
+ }
126
+ this.throwIfAborted();
127
+ return this.device.commands.typedCall('NftUpdate', 'Success', params, options);
128
+ }
129
+ }
130
+
131
+ async run(): Promise<DeviceUploadNftResponse> {
132
+ const { bundle } = this;
133
+ if (!bundle) throw invalidParameter('NFT data has not been initialized.');
134
+
135
+ await this.assertCapabilities();
136
+ this.throwIfAborted();
137
+ await this.assertStorageCapacity(bundle.basename);
138
+ this.throwIfAborted();
139
+
140
+ const files = [
141
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
142
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
143
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
144
+ ];
145
+ const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
146
+ let transferredBeforeFile = 0;
147
+
148
+ for (const file of files) {
149
+ const transferredAtFileStart = transferredBeforeFile;
150
+ await writeProtocolV2File({
151
+ commands: this.device.commands,
152
+ path: file.path,
153
+ data: file.data,
154
+ totalSize: file.data.byteLength,
155
+ chunkSize: this.params.chunkSize,
156
+ timeoutMs: this.params.timeoutMs,
157
+ paceMs: this.params.paceMs,
158
+ overwrite: true,
159
+ append: false,
160
+ throwIfAborted: () => this.throwIfAborted(),
161
+ onProgress: progress => {
162
+ if (typeof this.postMessage !== 'function') return;
163
+ const transferredBytes = transferredAtFileStart + progress.transferredBytes;
164
+ this.postMessage(
165
+ createUiMessage(UI_REQUEST.DEVICE_PROGRESS, {
166
+ ...progress,
167
+ progress: Math.floor((transferredBytes / totalSize) * 100),
168
+ transferredBytes,
169
+ totalBytes: totalSize,
170
+ })
171
+ );
172
+ },
173
+ });
174
+ transferredBeforeFile += file.data.byteLength;
175
+ }
176
+
177
+ this.throwIfAborted();
178
+ const response = await this.updateNft(bundle.basename);
179
+ return {
180
+ basename: bundle.basename,
181
+ imagePath: files[0].path,
182
+ thumbnailPath: files[1].path,
183
+ metadataPath: files[2].path,
184
+ totalSize,
185
+ nftUpdated: true,
186
+ message: response.message?.message,
187
+ };
188
+ }
189
+ }
@@ -13,7 +13,9 @@ export default class ProtocolInfoRequest extends BaseMethod {
13
13
  }
14
14
 
15
15
  async run() {
16
- const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {});
16
+ const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
17
+ eventless_wallet_session: true,
18
+ });
17
19
  return Promise.resolve(res.message);
18
20
  }
19
21
  }
@@ -118,6 +118,7 @@ export const isProtocolV2DeviceDisconnectedError = (error: unknown) => {
118
118
  message.includes('connection has timed out unexpectedly') ||
119
119
  message.includes('connection error has occured') ||
120
120
  message.includes('connection error has occurred') ||
121
+ message.includes('react native ble transport released') ||
121
122
  message.includes('multiplatformbleadapter') ||
122
123
  message.includes('multipalformebleadapter') ||
123
124
  compactMessage.includes('rxerrorerror6') ||
@@ -4,6 +4,8 @@ import type { Device, DeviceEvents } from '../device/Device';
4
4
 
5
5
  export type HardwareUiEventHandlers = {
6
6
  pin: (...event: DeviceEvents[typeof DEVICE.PIN]) => void;
7
+ pinOnDevice: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE]) => void;
8
+ pinOnDeviceComplete: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE_COMPLETE]) => void;
7
9
  button: (...event: DeviceEvents[typeof DEVICE.BUTTON]) => void;
8
10
  passphrase: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE]) => void;
9
11
  passphraseOnDevice: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE_ON_DEVICE]) => void;
@@ -15,6 +17,8 @@ export function registerHardwareUiEventListeners(
15
17
  handlers: HardwareUiEventHandlers
16
18
  ) {
17
19
  device.on(DEVICE.PIN, handlers.pin);
20
+ device.on(DEVICE.PIN_ON_DEVICE, handlers.pinOnDevice);
21
+ device.on(DEVICE.PIN_ON_DEVICE_COMPLETE, handlers.pinOnDeviceComplete);
18
22
  device.on(DEVICE.BUTTON, handlers.button);
19
23
  device.on(DEVICE.PASSPHRASE, handlers.passphrase);
20
24
  device.on(DEVICE.PASSPHRASE_ON_DEVICE, handlers.passphraseOnDevice);
package/src/core/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import semver from 'semver';
2
2
  import EventEmitter from 'events';
3
- import { TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
3
+ import { DeviceSessionPinType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
4
4
  import {
5
5
  ERRORS,
6
6
  ERROR_CODES_REQUIRE_RELEASE,
@@ -26,6 +26,7 @@ import {
26
26
  createRequestContext,
27
27
  createSdkTracingContext,
28
28
  formatRequestContext,
29
+ generateInstanceId,
29
30
  getActiveRequestsByDeviceInstance,
30
31
  updateRequestContext,
31
32
  } from '../utils/tracing';
@@ -53,13 +54,15 @@ import {
53
54
  import TransportManager from '../data-manager/TransportManager';
54
55
  import DeviceConnector from '../device/DeviceConnector';
55
56
  import RequestQueue from './RequestQueue';
57
+ import { findUiPromiseForResponse } from './uiPromiseRegistry';
56
58
  import { registerHardwareUiEventListeners } from './deviceEventRegistration';
57
59
  import { getSynchronize } from '../utils/getSynchronize';
58
- import { runMethodWithUnlockRetry } from '../protocols/protocol-v2/unlockRetry';
60
+ import { runMethodWithUnlockPolicy } from '../protocols/protocol-v2/unlockPolicyRunner';
59
61
  import {
60
62
  ProtocolV2UiInteractionCoordinator,
61
63
  isProtocolV2UiEnabled,
62
64
  } from '../protocols/protocol-v2/uiInteraction';
65
+ import { createUiProgressMessageFilter } from '../utils/uiProgressThrottle';
63
66
 
64
67
  import type { ConnectSettings, Features, KnownDevice } from '../types';
65
68
  import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
@@ -103,6 +106,7 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
103
106
  deviceId: method?.payload.deviceId,
104
107
  deriveCardano: method && hasDeriveCardano(method),
105
108
  connectProtocol: method?.payload.connectProtocol,
109
+ forceProtocolDetection: method?.payload.forceProtocolDetection,
106
110
  protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
107
111
  });
108
112
 
@@ -154,7 +158,12 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
154
158
  try {
155
159
  method = findMethod(message as IFrameCallMessage);
156
160
  method.connector = _connector;
157
- method.postMessage = postMessage;
161
+ const shouldPostMessage = createUiProgressMessageFilter();
162
+ method.postMessage = event => {
163
+ if (shouldPostMessage(event)) {
164
+ postMessage(event);
165
+ }
166
+ };
158
167
  method.setContext?.(context);
159
168
 
160
169
  method.requestContext = createRequestContext(method.responseID, method.name, {
@@ -406,6 +415,8 @@ const onCallDevice = async (
406
415
 
407
416
  registerHardwareUiEventListeners(device, {
408
417
  pin: onDevicePinHandler,
418
+ pinOnDevice: onEnterPinOnDeviceHandler,
419
+ pinOnDeviceComplete: onPinOnDeviceCompleteHandler,
409
420
  button: onDeviceButtonHandler,
410
421
  passphrase: message.payload.useEmptyPassphrase
411
422
  ? onEmptyPassphraseHandler
@@ -421,6 +432,9 @@ const onCallDevice = async (
421
432
  );
422
433
 
423
434
  const protocolV2UiCoordinator = new ProtocolV2UiInteractionCoordinator(device, postMessage);
435
+ let protocolV2Operation = false;
436
+ let protocolV2CloseEmitted = false;
437
+ device.beginProtocolV2UiInteraction();
424
438
  device.on(
425
439
  DEVICE.SELECT_DEVICE_FOR_SWITCH_FIRMWARE_WEB_DEVICE,
426
440
  onSelectDeviceForSwitchFirmwareWebDeviceHandler
@@ -438,6 +452,7 @@ const onCallDevice = async (
438
452
  // Protocol is established from an active device response during acquire/initialize.
439
453
  // Reject unsupported methods before any method-specific device command is sent.
440
454
  method.assertProtocolSupported(device.getProtocol(), device.getCurrentFirmwareType());
455
+ protocolV2Operation = device.isProtocolV2();
441
456
 
442
457
  // check firmware version
443
458
  const versionRange = device.getCurrentMethodVersionRange(
@@ -547,89 +562,92 @@ const onCallDevice = async (
547
562
  );
548
563
  }
549
564
 
550
- if (method.deviceId && method.checkDeviceId) {
551
- const isSameDeviceID = await checkLiveDeviceId(device, method.deviceId);
552
- if (!isSameDeviceID) {
553
- return Promise.reject(ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError));
554
- }
555
- }
556
-
557
- /**
558
- * check firmware release info
559
- */
560
- method.checkFirmwareRelease();
565
+ try {
566
+ let deviceIdCheckedDuringUnlockPreflight = false;
567
+ const response = await runMethodWithUnlockPolicy<object>(method, device, {
568
+ uiCoordinator: protocolV2UiCoordinator,
569
+ afterStatusBeforeUnlock: () => {
570
+ if (method.deviceId && method.checkDeviceId) {
571
+ if (!device.checkDeviceId(method.deviceId)) {
572
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
573
+ }
574
+ deviceIdCheckedDuringUnlockPreflight = true;
575
+ }
576
+ },
577
+ prepare: async () => {
578
+ if (method.deviceId && method.checkDeviceId && !deviceIdCheckedDuringUnlockPreflight) {
579
+ const isSameDeviceID = await checkLiveDeviceId(device, method.deviceId);
580
+ if (!isSameDeviceID) {
581
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
582
+ }
583
+ }
561
584
 
562
- /**
563
- * check additional supported feature
564
- */
565
- method.checkDeviceSupportFeature();
585
+ /**
586
+ * check firmware release info
587
+ */
588
+ method.checkFirmwareRelease();
566
589
 
567
- // reconfigure messages
568
- if (_deviceList && device.features && !device.isProtocolV2()) {
569
- await TransportManager.reconfigure(device.features);
570
- }
590
+ /**
591
+ * check additional supported feature
592
+ */
593
+ method.checkDeviceSupportFeature();
571
594
 
572
- // Check to see if it is safe to use Passphrase
573
- checkPassphraseEnableState(method, device.features);
595
+ // reconfigure messages
596
+ if (_deviceList && device.features && !device.isProtocolV2()) {
597
+ await TransportManager.reconfigure(device.features);
598
+ }
574
599
 
575
- if (shouldCheckPassphraseState(method, device)) {
576
- // check version
577
- const support = device.supportNewPassphrase();
578
- if (!support.support) {
579
- return Promise.reject(
580
- ERRORS.TypedError(
581
- HardwareErrorCode.DeviceNotSupportPassphrase,
582
- `Device not support passphrase, please update to ${support.require}`,
583
- {
584
- require: support.require,
600
+ // Check to see if it is safe to use Passphrase
601
+ checkPassphraseEnableState(method, device.features);
602
+
603
+ if (shouldCheckPassphraseState(method, device)) {
604
+ // check version
605
+ const support = device.supportNewPassphrase();
606
+ if (!support.support) {
607
+ throw ERRORS.TypedError(
608
+ HardwareErrorCode.DeviceNotSupportPassphrase,
609
+ `Device not support passphrase, please update to ${support.require}`,
610
+ {
611
+ require: support.require,
612
+ }
613
+ );
585
614
  }
586
- )
587
- );
588
- }
589
-
590
- // Check Device passphrase State
591
- const passphraseStateSafety = await device.checkPassphraseStateSafety(
592
- method.payload?.passphraseState,
593
- method.payload?.useEmptyPassphrase,
594
- method.payload?.skipPassphraseCheck
595
- );
596
615
 
597
- // Double check, handles the special case of Touch/Pro
598
- checkPassphraseEnableState(method, device.features);
616
+ // Check Device passphrase State after the Protocol V2 pre-unlock gate.
617
+ const passphraseStateSafety = await device.checkPassphraseStateSafety(
618
+ method.payload?.passphraseState,
619
+ method.payload?.useEmptyPassphrase,
620
+ method.payload?.skipPassphraseCheck,
621
+ hasDeriveCardano(method)
622
+ );
599
623
 
600
- if (!passphraseStateSafety) {
601
- DevicePool.clearDeviceCache(method.payload.connectId);
602
- return Promise.reject(
603
- ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError)
604
- );
605
- }
624
+ // Double check, handles the special case of Touch/Pro
625
+ checkPassphraseEnableState(method, device.features);
606
626
 
607
- // close pin popup window
608
- postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
609
- }
627
+ if (!passphraseStateSafety) {
628
+ DevicePool.clearDeviceCache(method.payload.connectId);
629
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
630
+ }
610
631
 
611
- // Automatic check safety_check level for Kovan, Ropsten, Rinkeby, Goerli test networks.
612
- try {
613
- await method.checkSafetyLevelOnTestNet();
614
- } catch (e) {
615
- const error =
616
- e instanceof HardwareError
617
- ? e
618
- : ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'open safety check failed.');
619
- // messageResponse = createResponseMessage(method.responseID, false, { error });
620
- // requestQueue.resolveRequest(method.responseID, messageResponse);
621
- // return;
622
- throw error;
623
- }
632
+ // Protocol V2 emits the PIN phase completion from DeviceSessionAskPin.
633
+ // Keep the legacy compatibility close for Protocol V1 only.
634
+ if (!device.isProtocolV2()) {
635
+ postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
636
+ }
637
+ }
624
638
 
625
- method.device?.commands?.checkDisposed();
639
+ // Automatic check safety_check level for Kovan, Ropsten, Rinkeby, Goerli test networks.
640
+ try {
641
+ await method.checkSafetyLevelOnTestNet();
642
+ } catch (e) {
643
+ throw e instanceof HardwareError
644
+ ? e
645
+ : ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'open safety check failed.');
646
+ }
626
647
 
627
- try {
628
- const response: object = await runMethodWithUnlockRetry(
629
- method,
630
- device,
631
- protocolV2UiCoordinator
632
- );
648
+ method.device?.commands?.checkDisposed();
649
+ },
650
+ });
633
651
  messageResponse = createResponseMessage(method.responseID, true, response);
634
652
  requestQueue.resolveRequest(method.responseID, messageResponse);
635
653
  completeMethodRequestContext(method);
@@ -647,7 +665,12 @@ const onCallDevice = async (
647
665
  throw error;
648
666
  }
649
667
  } finally {
650
- protocolV2UiCoordinator.close();
668
+ if (isProtocolV2UiEnabled(method)) {
669
+ protocolV2CloseEmitted = protocolV2UiCoordinator.close({
670
+ ensureOperationClose: protocolV2Operation,
671
+ protocolV2Operation,
672
+ });
673
+ }
651
674
  }
652
675
  };
653
676
  Log.debug('Call API - Device Run: ', device.mainId);
@@ -702,7 +725,15 @@ const onCallDevice = async (
702
725
  requestQueue.releaseTask(method.responseID);
703
726
 
704
727
  if (isProtocolV2UiEnabled(method)) {
705
- closePopup();
728
+ if (!protocolV2CloseEmitted && protocolV2Operation) {
729
+ protocolV2CloseEmitted = protocolV2UiCoordinator.close({
730
+ ensureOperationClose: true,
731
+ protocolV2Operation: true,
732
+ });
733
+ }
734
+ if (!protocolV2CloseEmitted) {
735
+ closePopup();
736
+ }
706
737
  }
707
738
 
708
739
  cleanup();
@@ -865,10 +896,18 @@ function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown)
865
896
  */
866
897
  async function connectDeviceForBle(method: BaseMethod, device: Device, retryCount = 0) {
867
898
  try {
899
+ if (method.payload.forceProtocolDetection && device.hasDeviceAcquire()) {
900
+ await device.release();
901
+ }
868
902
  const shouldAcquire =
869
- !device.hasDeviceAcquire() || !device.commands || device.commands.disposed;
903
+ method.payload.forceProtocolDetection ||
904
+ !device.hasDeviceAcquire() ||
905
+ !device.commands ||
906
+ device.commands.disposed;
870
907
  if (shouldAcquire) {
871
- await device.acquire(method.payload.connectProtocol);
908
+ await device.acquire(method.payload.connectProtocol, {
909
+ forceProtocolDetection: method.payload.forceProtocolDetection,
910
+ });
872
911
  }
873
912
  if (method.payload?.onlyConnectBleDevice) {
874
913
  if (shouldAcquire) {
@@ -1232,6 +1271,7 @@ const onDevicePinHandler = async (...[device, type, callback]: DeviceEvents['pin
1232
1271
  createUiMessage(UI_REQUEST.REQUEST_PIN, {
1233
1272
  device: device.toMessageObject() as unknown as KnownDevice,
1234
1273
  type,
1274
+ responseCorrelation: uiPromise.responseCorrelation,
1235
1275
  })
1236
1276
  );
1237
1277
  // wait for pin
@@ -1279,6 +1319,8 @@ const onDevicePassphraseHandler = async (
1279
1319
  source: requestPayload.source,
1280
1320
  reason: requestPayload.reason,
1281
1321
  expectedPassphraseState: requestPayload.expectedPassphraseState,
1322
+ ...(requestPayload.interaction ? { interaction: requestPayload.interaction } : {}),
1323
+ responseCorrelation: uiPromise.responseCorrelation,
1282
1324
  })
1283
1325
  );
1284
1326
  // wait for passphrase
@@ -1308,6 +1350,7 @@ const onEnterPassphraseOnDeviceHandler = (
1308
1350
  passphraseState: device.passphraseState,
1309
1351
  source: requestPayload?.source,
1310
1352
  reason: requestPayload?.reason,
1353
+ ...(requestPayload?.interaction ? { interaction: requestPayload.interaction } : {}),
1311
1354
  })
1312
1355
  );
1313
1356
  };
@@ -1321,10 +1364,39 @@ const onEnterAttachPinOnDeviceHandler = (
1321
1364
  type: 'ButtonRequest_AttachPin',
1322
1365
  source: requestPayload?.source,
1323
1366
  reason: requestPayload?.reason,
1367
+ ...(requestPayload?.interaction ? { interaction: requestPayload.interaction } : {}),
1368
+ })
1369
+ );
1370
+ };
1371
+
1372
+ const onEnterPinOnDeviceHandler = (
1373
+ ...[device, pinType, metadata]: [...DeviceEvents['pin_on_device']]
1374
+ ) => {
1375
+ postMessage(
1376
+ createUiMessage(UI_REQUEST.REQUEST_PIN, {
1377
+ device: device.toMessageObject() as KnownDevice,
1378
+ type:
1379
+ pinType === DeviceSessionPinType.AttachToPin
1380
+ ? 'ButtonRequest_AttachPin'
1381
+ : 'ButtonRequest_PinEntry',
1382
+ source: metadata?.source,
1383
+ reason: metadata?.reason,
1384
+ deviceOnly: metadata?.deviceOnly ?? true,
1385
+ completion: metadata?.completion,
1386
+ method: metadata?.method,
1387
+ page: metadata?.page,
1388
+ operation: metadata?.operation,
1389
+ ...(metadata?.interaction ? { interaction: metadata.interaction } : {}),
1324
1390
  })
1325
1391
  );
1326
1392
  };
1327
1393
 
1394
+ const onPinOnDeviceCompleteHandler = (
1395
+ ...[_, metadata]: [...DeviceEvents['pin_on_device_complete']]
1396
+ ) => {
1397
+ postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW, metadata));
1398
+ };
1399
+
1328
1400
  const onSelectDeviceInBootloaderForWebDeviceHandler = async (
1329
1401
  ...[device, callback]: [...DeviceEvents['select_device_in_bootloader_for_web_device']]
1330
1402
  ) => {
@@ -1372,14 +1444,21 @@ const postMessage = (message: CoreMessage) => {
1372
1444
 
1373
1445
  const createUiPromise = <T extends UiPromiseResponse['type']>(promiseEvent: T, device?: Device) => {
1374
1446
  const uiPromise: UiPromise<T> = createDeferred(promiseEvent, device);
1447
+ if (
1448
+ device &&
1449
+ (promiseEvent === UI_RESPONSE.RECEIVE_PIN || promiseEvent === UI_RESPONSE.RECEIVE_PASSPHRASE)
1450
+ ) {
1451
+ const publicDeviceId = device.toMessageObject()?.deviceId;
1452
+ uiPromise.responseCorrelation = {
1453
+ interactionId: generateInstanceId('UiResponse', device.sdkInstanceId),
1454
+ deviceId: publicDeviceId || device.instanceId,
1455
+ };
1456
+ }
1375
1457
  _uiPromises.push(uiPromise as any);
1376
1458
 
1377
1459
  return uiPromise;
1378
1460
  };
1379
1461
 
1380
- const findUiPromise = <T extends UiPromiseResponse['type']>(promiseEvent: T) =>
1381
- _uiPromises.find(p => p.id === promiseEvent);
1382
-
1383
1462
  const removeUiPromise = (promise: Deferred<any>) => {
1384
1463
  _uiPromises = _uiPromises.filter(p => p !== promise);
1385
1464
  };
@@ -1431,11 +1510,16 @@ export default class Core extends EventEmitter {
1431
1510
  case UI_RESPONSE.RECEIVE_PASSPHRASE:
1432
1511
  case UI_RESPONSE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE:
1433
1512
  case UI_RESPONSE.SELECT_DEVICE_FOR_SWITCH_FIRMWARE_WEB_DEVICE: {
1434
- const uiPromise = findUiPromise(message.type);
1513
+ const uiPromise = findUiPromiseForResponse(_uiPromises, message);
1435
1514
  if (uiPromise) {
1436
1515
  Log.log('receive UI Response: ', message.type);
1437
1516
  uiPromise.resolve(message);
1438
1517
  removeUiPromise(uiPromise);
1518
+ } else if (
1519
+ message.type === UI_RESPONSE.RECEIVE_PIN ||
1520
+ message.type === UI_RESPONSE.RECEIVE_PASSPHRASE
1521
+ ) {
1522
+ Log.warn('Ignored unmatched or ambiguous sensitive UI response:', message.type);
1439
1523
  }
1440
1524
  break;
1441
1525
  }
@@ -0,0 +1,41 @@
1
+ import { UI_RESPONSE } from '../events/ui-response';
2
+
3
+ import type { UiPromise, UiPromiseResponse } from '../events/ui-promise';
4
+ import type { UiResponseEvent } from '../events/ui-response';
5
+
6
+ const isSensitiveUiResponse = (
7
+ response: UiResponseEvent
8
+ ): response is Extract<
9
+ UiResponseEvent,
10
+ { type: typeof UI_RESPONSE.RECEIVE_PIN | typeof UI_RESPONSE.RECEIVE_PASSPHRASE }
11
+ > => response.type === UI_RESPONSE.RECEIVE_PIN || response.type === UI_RESPONSE.RECEIVE_PASSPHRASE;
12
+
13
+ export const findUiPromiseForResponse = (
14
+ uiPromises: UiPromise<UiPromiseResponse['type']>[],
15
+ response: UiResponseEvent
16
+ ) => {
17
+ const candidates = uiPromises.filter(promise => promise.id === response.type);
18
+ if (!isSensitiveUiResponse(response)) {
19
+ return candidates[0];
20
+ }
21
+
22
+ const hasInteractionId = typeof response.interactionId === 'string';
23
+ const hasDeviceId = typeof response.deviceId === 'string';
24
+ if (hasInteractionId !== hasDeviceId) {
25
+ return undefined;
26
+ }
27
+
28
+ if (hasInteractionId && hasDeviceId) {
29
+ return candidates.find(promise => {
30
+ const correlation = promise.responseCorrelation;
31
+ return (
32
+ correlation?.interactionId === response.interactionId &&
33
+ correlation?.deviceId === response.deviceId
34
+ );
35
+ });
36
+ }
37
+
38
+ // Legacy clients do not return correlation metadata. Preserve their single-request
39
+ // behavior, but never guess when multiple sensitive requests are pending.
40
+ return candidates.length === 1 ? candidates[0] : undefined;
41
+ };