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

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 (200) 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 +187 -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 +259 -18
  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 +56 -0
  14. package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
  15. package/__tests__/protocol-v2.test.ts +526 -955
  16. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  17. package/__tests__/uiProgressThrottle.test.ts +74 -0
  18. package/dist/api/BaseMethod.d.ts +3 -1
  19. package/dist/api/BaseMethod.d.ts.map +1 -1
  20. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  21. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  22. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  24. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  25. package/dist/api/FirmwareUpdateV4.d.ts +6 -25
  26. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  27. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  28. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  29. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  30. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  32. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -8
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  34. package/dist/api/firmware/getBinary.d.ts +0 -7
  35. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  36. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  37. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  38. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  39. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  40. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  41. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  42. package/dist/api/index.d.ts +1 -0
  43. package/dist/api/index.d.ts.map +1 -1
  44. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
  45. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  46. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  47. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  48. package/dist/core/deviceEventRegistration.d.ts +2 -0
  49. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  50. package/dist/core/index.d.ts.map +1 -1
  51. package/dist/data-manager/DataManager.d.ts +3 -2
  52. package/dist/data-manager/DataManager.d.ts.map +1 -1
  53. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  54. package/dist/device/Device.d.ts +22 -4
  55. package/dist/device/Device.d.ts.map +1 -1
  56. package/dist/events/device.d.ts +4 -0
  57. package/dist/events/device.d.ts.map +1 -1
  58. package/dist/events/logBlockEvent.d.ts.map +1 -1
  59. package/dist/events/ui-request.d.ts +27 -2
  60. package/dist/events/ui-request.d.ts.map +1 -1
  61. package/dist/index.d.ts +110 -182
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +1919 -3336
  64. package/dist/inject.d.ts.map +1 -1
  65. package/dist/lowLevelInject.d.ts.map +1 -1
  66. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  67. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  68. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  69. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  70. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  71. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  72. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  73. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  74. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  75. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  76. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  77. package/dist/topLevelInject.d.ts.map +1 -1
  78. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -4
  79. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  80. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  81. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  82. package/dist/types/api/export.d.ts +1 -4
  83. package/dist/types/api/export.d.ts.map +1 -1
  84. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  85. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  86. package/dist/types/api/index.d.ts +3 -10
  87. package/dist/types/api/index.d.ts.map +1 -1
  88. package/dist/types/api/protocolV2.d.ts +3 -0
  89. package/dist/types/api/protocolV2.d.ts.map +1 -1
  90. package/dist/types/settings.d.ts +12 -13
  91. package/dist/types/settings.d.ts.map +1 -1
  92. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  93. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  94. package/dist/utils/homescreen.d.ts +4 -0
  95. package/dist/utils/homescreen.d.ts.map +1 -1
  96. package/dist/utils/index.d.ts +1 -1
  97. package/dist/utils/index.d.ts.map +1 -1
  98. package/dist/utils/patch.d.ts +1 -1
  99. package/dist/utils/patch.d.ts.map +1 -1
  100. package/dist/utils/pro2Nft.d.ts +29 -0
  101. package/dist/utils/pro2Nft.d.ts.map +1 -0
  102. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  103. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  104. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  105. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  106. package/package.json +4 -4
  107. package/src/api/BaseMethod.ts +8 -10
  108. package/src/api/CheckAllFirmwareRelease.ts +40 -53
  109. package/src/api/FirmwareUpdateV2.ts +120 -298
  110. package/src/api/FirmwareUpdateV3.ts +57 -263
  111. package/src/api/FirmwareUpdateV4.ts +307 -912
  112. package/src/api/GetPassphraseState.ts +7 -1
  113. package/src/api/OpenWalletSession.ts +54 -17
  114. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  115. package/src/api/device/DeviceUnlock.ts +1 -1
  116. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  117. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -57
  118. package/src/api/firmware/updateBootloader.ts +4 -19
  119. package/src/api/firmware/uploadFirmware.ts +22 -140
  120. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  121. package/src/api/index.ts +1 -0
  122. package/src/api/protocol-v2/DeviceUploadNft.ts +166 -0
  123. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  124. package/src/core/deviceEventRegistration.ts +4 -0
  125. package/src/core/index.ts +118 -75
  126. package/src/data/messages/messages-protocol-v2.json +53 -0
  127. package/src/data-manager/DataManager.ts +63 -38
  128. package/src/data-manager/connectSettings.ts +0 -11
  129. package/src/device/Device.ts +193 -21
  130. package/src/events/device.ts +4 -0
  131. package/src/events/logBlockEvent.ts +1 -0
  132. package/src/events/ui-request.ts +32 -2
  133. package/src/index.ts +0 -5
  134. package/src/inject.ts +3 -22
  135. package/src/lowLevelInject.ts +1 -5
  136. package/src/protocols/protocol-v2/features.ts +9 -2
  137. package/src/protocols/protocol-v2/resources.ts +233 -0
  138. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  139. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  140. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
  141. package/src/protocols/protocol-v2/walletSession.ts +93 -18
  142. package/src/topLevelInject.ts +1 -5
  143. package/src/types/api/checkAllFirmwareRelease.ts +1 -4
  144. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  145. package/src/types/api/export.ts +0 -18
  146. package/src/types/api/firmwareUpdate.ts +0 -76
  147. package/src/types/api/index.ts +2 -14
  148. package/src/types/api/protocolV2.ts +13 -0
  149. package/src/types/settings.ts +26 -13
  150. package/src/utils/deviceFeaturesUtils.ts +4 -0
  151. package/src/utils/homescreen.ts +32 -6
  152. package/src/utils/index.ts +6 -1
  153. package/src/utils/pro2Nft.ts +91 -0
  154. package/src/utils/pro2Wallpaper.ts +35 -8
  155. package/src/utils/uiProgressThrottle.ts +63 -0
  156. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  157. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  158. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  159. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  160. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  161. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  162. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  163. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  164. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  165. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  166. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  167. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  168. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  169. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  170. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  171. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  172. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  173. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  174. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  175. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  176. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  177. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  178. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  179. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  180. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  181. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  182. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  183. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  184. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  185. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  186. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  187. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  188. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  189. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  190. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  191. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  192. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  193. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  194. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  195. package/src/api/firmware/progressThrottle.ts +0 -44
  196. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  197. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  198. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  199. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  200. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -0,0 +1,233 @@
1
+ import { sha256 } from '@noble/hashes/sha256';
2
+
3
+ import type {
4
+ IProtocolV2Resource,
5
+ IProtocolV2ResourceType,
6
+ IProtocolV2Resources,
7
+ } from '../../types';
8
+ import type { DeviceCommands } from '../../device/DeviceCommands';
9
+ import type { ResourceInventory } from '@onekeyfe/hd-transport';
10
+
11
+ export const PROTOCOL_V2_RESOURCE_TYPES = [
12
+ 'images',
13
+ 'animation',
14
+ 'wallpaper',
15
+ 'translations',
16
+ 'roobert',
17
+ 'noto',
18
+ ] as const satisfies readonly IProtocolV2ResourceType[];
19
+
20
+ export const PROTOCOL_V2_RESOURCE_DEVICE_PATHS: Readonly<Record<IProtocolV2ResourceType, string>> =
21
+ {
22
+ images: 'vol0:/bundles/images/images.okpkg',
23
+ animation: 'vol0:/bundles/images/animation.okpkg',
24
+ wallpaper: 'vol0:/bundles/images/wallpaper.okpkg',
25
+ translations: 'vol0:/bundles/translations/translations.okpkg',
26
+ roobert: 'vol0:/bundles/font/roobert.okpkg',
27
+ noto: 'vol0:/bundles/font/noto.okpkg',
28
+ };
29
+
30
+ const RESOURCE_TYPE_SET = new Set<string>(PROTOCOL_V2_RESOURCE_TYPES);
31
+ const SHA256_HEX_LENGTH = 64;
32
+ const SHA3_512_HEX_LENGTH = 128;
33
+ export const PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS = 5 * 1000;
34
+
35
+ const RESOURCE_TYPE_BY_DEVICE_VALUE: Readonly<Record<string, IProtocolV2ResourceType>> = {
36
+ '0': 'images',
37
+ IMAGES: 'images',
38
+ '1': 'animation',
39
+ ANIMATION: 'animation',
40
+ '2': 'wallpaper',
41
+ WALLPAPER: 'wallpaper',
42
+ '3': 'translations',
43
+ TRANSLATIONS: 'translations',
44
+ '4': 'roobert',
45
+ ROOBERT: 'roobert',
46
+ '5': 'noto',
47
+ NOTO: 'noto',
48
+ };
49
+
50
+ export type ProtocolV2ResourceInventoryItem = {
51
+ type: IProtocolV2ResourceType;
52
+ size: number;
53
+ headerHash: string;
54
+ };
55
+
56
+ export type ProtocolV2ResourceUpdateMode = 'application' | 'bootloader-recovery';
57
+
58
+ export type ProtocolV2ResourceUpdatePlan = {
59
+ status: 'valid' | 'outdated' | 'unknown';
60
+ resources: IProtocolV2Resource[];
61
+ };
62
+
63
+ /** Normalize the success-only device response into the SDK resource identity shape. */
64
+ export function parseProtocolV2ResourceInventory(
65
+ value: ResourceInventory | unknown
66
+ ): ProtocolV2ResourceInventoryItem[] {
67
+ const items = (value as { items?: unknown })?.items;
68
+ if (!Array.isArray(items)) {
69
+ throw new Error('Invalid Pro2 resource inventory: items must be an array');
70
+ }
71
+
72
+ const inventory = items.map((item, index) => {
73
+ if (!item || typeof item !== 'object') {
74
+ throw new Error(`Invalid Pro2 resource inventory item at ${index}`);
75
+ }
76
+ const raw = item as { type?: unknown; size?: unknown; header_hash?: unknown };
77
+ const type = RESOURCE_TYPE_BY_DEVICE_VALUE[String(raw.type).toUpperCase()];
78
+ if (!type) {
79
+ throw new Error(`Invalid Pro2 resource inventory type at ${index}`);
80
+ }
81
+ if (!Number.isSafeInteger(raw.size) || Number(raw.size) <= 0) {
82
+ throw new Error(`Invalid Pro2 resource inventory size at ${index}`);
83
+ }
84
+ return {
85
+ type,
86
+ size: Number(raw.size),
87
+ headerHash: normalizeHex(raw.header_hash, SHA3_512_HEX_LENGTH, 'inventory headerHash'),
88
+ };
89
+ });
90
+
91
+ if (new Set(inventory.map(item => item.type)).size !== inventory.length) {
92
+ throw new Error('Invalid Pro2 resource inventory: duplicate resource type');
93
+ }
94
+ return PROTOCOL_V2_RESOURCE_TYPES.flatMap(type => {
95
+ const item = inventory.find(candidate => candidate.type === type);
96
+ return item ? [item] : [];
97
+ });
98
+ }
99
+
100
+ export async function requestProtocolV2ResourceInventory({
101
+ commands,
102
+ timeoutMs = PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS,
103
+ }: {
104
+ commands: DeviceCommands;
105
+ timeoutMs?: number;
106
+ }): Promise<ProtocolV2ResourceInventoryItem[]> {
107
+ const { message } = await commands.typedCall(
108
+ 'ResourceInventoryGet',
109
+ 'ResourceInventory',
110
+ {},
111
+ { timeoutMs }
112
+ );
113
+ return parseProtocolV2ResourceInventory(message);
114
+ }
115
+
116
+ function normalizeHex(value: unknown, expectedLength: number, field: string): string {
117
+ if (typeof value !== 'string') {
118
+ throw new Error(`Invalid Pro2 resource ${field}: expected a hexadecimal string`);
119
+ }
120
+ const normalized = value.replace(/^0x/i, '').toLowerCase();
121
+ if (normalized.length !== expectedLength || !/^[0-9a-f]+$/.test(normalized)) {
122
+ throw new Error(
123
+ `Invalid Pro2 resource ${field}: expected ${expectedLength} hexadecimal characters`
124
+ );
125
+ }
126
+ return normalized;
127
+ }
128
+
129
+ function validateResource(value: unknown, index: number): IProtocolV2Resource {
130
+ if (!value || typeof value !== 'object') {
131
+ throw new Error(`Invalid Pro2 resource at stable[${index}]`);
132
+ }
133
+ const resource = value as Partial<IProtocolV2Resource>;
134
+ if (typeof resource.type !== 'string' || !RESOURCE_TYPE_SET.has(resource.type)) {
135
+ throw new Error(`Invalid Pro2 resource type at stable[${index}]`);
136
+ }
137
+ if (typeof resource.url !== 'string' || !resource.url.startsWith('https://')) {
138
+ throw new Error(`Invalid Pro2 resource url at stable[${index}]`);
139
+ }
140
+ if (!Number.isSafeInteger(resource.size) || Number(resource.size) <= 0) {
141
+ throw new Error(`Invalid Pro2 resource size at stable[${index}]`);
142
+ }
143
+ return {
144
+ type: resource.type,
145
+ url: resource.url,
146
+ size: Number(resource.size),
147
+ fileHash: normalizeHex(resource.fileHash, SHA256_HEX_LENGTH, 'fileHash'),
148
+ headerHash: normalizeHex(resource.headerHash, SHA3_512_HEX_LENGTH, 'headerHash'),
149
+ };
150
+ }
151
+
152
+ /** Validate a complete Pro2 stable resource set from remote configuration. */
153
+ export function parseProtocolV2Resources(value: unknown): IProtocolV2Resources | undefined {
154
+ if (value === undefined) return undefined;
155
+ if (
156
+ !value ||
157
+ typeof value !== 'object' ||
158
+ !Array.isArray((value as { stable?: unknown }).stable)
159
+ ) {
160
+ throw new Error('Invalid Pro2 resources config: stable must be an array');
161
+ }
162
+
163
+ const stable = (value as { stable: unknown[] }).stable.map(validateResource);
164
+ const types = new Set(stable.map(resource => resource.type));
165
+ if (stable.length !== PROTOCOL_V2_RESOURCE_TYPES.length || types.size !== stable.length) {
166
+ throw new Error('Invalid Pro2 resources config: stable must contain six unique resource types');
167
+ }
168
+ for (const type of PROTOCOL_V2_RESOURCE_TYPES) {
169
+ if (!types.has(type)) {
170
+ throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
171
+ }
172
+ }
173
+ return {
174
+ stable: PROTOCOL_V2_RESOURCE_TYPES.map(type => {
175
+ const resource = stable.find(item => item.type === type);
176
+ if (!resource) {
177
+ throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
178
+ }
179
+ return resource;
180
+ }),
181
+ };
182
+ }
183
+
184
+ /** Compare the application inventory or select the full set for bootloader recovery. */
185
+ export function buildProtocolV2ResourceUpdatePlan({
186
+ resources,
187
+ inventory,
188
+ mode,
189
+ forced = false,
190
+ }: {
191
+ resources: readonly IProtocolV2Resource[];
192
+ inventory?: readonly ProtocolV2ResourceInventoryItem[];
193
+ mode: ProtocolV2ResourceUpdateMode;
194
+ forced?: boolean;
195
+ }): ProtocolV2ResourceUpdatePlan {
196
+ if (mode === 'bootloader-recovery' || forced) {
197
+ return {
198
+ status: resources.length > 0 ? 'outdated' : 'valid',
199
+ resources: [...resources],
200
+ };
201
+ }
202
+ if (!inventory) {
203
+ return { status: 'unknown', resources: [] };
204
+ }
205
+
206
+ const inventoryByType = new Map(inventory.map(item => [item.type, item]));
207
+ const changedResources = resources.filter(resource => {
208
+ const current = inventoryByType.get(resource.type);
209
+ return (
210
+ !current ||
211
+ current.size !== resource.size ||
212
+ current.headerHash.toLowerCase() !== resource.headerHash.toLowerCase()
213
+ );
214
+ });
215
+
216
+ return {
217
+ status: changedResources.length === 0 ? 'valid' : 'outdated',
218
+ resources: changedResources,
219
+ };
220
+ }
221
+
222
+ function bytesToHex(bytes: Uint8Array): string {
223
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
224
+ }
225
+
226
+ /** Verify the complete downloaded file before any device mutation. */
227
+ export function isProtocolV2ResourceFileValid(
228
+ binary: ArrayBuffer,
229
+ resource: Pick<IProtocolV2Resource, 'size' | 'fileHash'>
230
+ ): boolean {
231
+ if (binary.byteLength !== resource.size) return false;
232
+ return bytesToHex(sha256(new Uint8Array(binary))) === resource.fileHash.toLowerCase();
233
+ }
@@ -1,6 +1,6 @@
1
1
  import { UI_REQUEST, createUiMessage } from '../../events';
2
2
 
3
- import type { CoreMessage, ProtocolV2UiCompletion } from '../../events';
3
+ import type { CoreMessage, HardwareUiInteractionMeta, ProtocolV2UiCompletion } from '../../events';
4
4
  import type { Device } from '../../device/Device';
5
5
  import type { KnownDevice } from '../../types';
6
6
 
@@ -28,7 +28,15 @@ type ProtocolV2InteractionMethod = {
28
28
  protocolV2UiInteraction?: ProtocolV2InteractionDescriptor;
29
29
  };
30
30
 
31
- type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'>;
31
+ type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'> &
32
+ Partial<
33
+ Pick<
34
+ Device,
35
+ | 'createProtocolV2UiPhaseMetadata'
36
+ | 'finishProtocolV2UiInteraction'
37
+ | 'hasOpenProtocolV2UiInteraction'
38
+ >
39
+ >;
32
40
  type PostMessage = (message: CoreMessage) => void;
33
41
 
34
42
  export const isProtocolV2UiEnabled = (method: { protocolV2UiMode?: 'auto' | 'none' }) =>
@@ -156,12 +164,17 @@ export class ProtocolV2UiInteractionCoordinator {
156
164
  this.methodInteraction = interaction;
157
165
  if (!interaction) return;
158
166
  const { request, ...metadata } = interaction;
167
+ const interactionMeta = this.device.createProtocolV2UiPhaseMetadata?.(
168
+ request === 'pin' ? 'pin' : 'button',
169
+ 'start'
170
+ );
159
171
 
160
172
  this.emit(
161
173
  request === 'pin' ? UI_REQUEST.REQUEST_PIN : UI_REQUEST.REQUEST_BUTTON,
162
174
  {
163
175
  device: this.device.toMessageObject() as KnownDevice,
164
176
  ...metadata,
177
+ ...(interactionMeta ? { interaction: interactionMeta } : {}),
165
178
  },
166
179
  `method:${request}:${interaction.reason}:${interaction.page ?? ''}:${
167
180
  interaction.operation ?? ''
@@ -169,7 +182,8 @@ export class ProtocolV2UiInteractionCoordinator {
169
182
  );
170
183
  }
171
184
 
172
- enterUnlockInteraction(method?: string) {
185
+ enterUnlockInteraction(method?: string): HardwareUiInteractionMeta | undefined {
186
+ const interaction = this.device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
173
187
  this.emit(
174
188
  UI_REQUEST.REQUEST_PIN,
175
189
  {
@@ -178,9 +192,11 @@ export class ProtocolV2UiInteractionCoordinator {
178
192
  reason: 'device-locked',
179
193
  deviceOnly: true,
180
194
  method,
195
+ ...(interaction ? { interaction } : {}),
181
196
  },
182
197
  `unlock:${method ?? ''}`
183
198
  );
199
+ return interaction;
184
200
  }
185
201
 
186
202
  resumeMethodInteraction() {
@@ -188,9 +204,19 @@ export class ProtocolV2UiInteractionCoordinator {
188
204
  }
189
205
 
190
206
  close() {
191
- if (!this.device.isProtocolV2() || !this.opened || this.closed) return;
207
+ if (
208
+ !this.device.isProtocolV2() ||
209
+ (!this.opened && !this.device.hasOpenProtocolV2UiInteraction?.()) ||
210
+ this.closed
211
+ )
212
+ return;
192
213
  this.closed = true;
193
- this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW));
214
+ const interaction = this.device.finishProtocolV2UiInteraction?.();
215
+ this.postMessage(
216
+ interaction
217
+ ? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, interaction)
218
+ : createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
219
+ );
194
220
  }
195
221
 
196
222
  private emit(
@@ -1,105 +1 @@
1
- export type UnlockPolicy = 'none' | 'unlock-before-run' | 'retry-on-locked';
2
-
3
- /**
4
- * Protocol V2 methods that are safe to replay after a locked response.
5
- *
6
- * Keep this as an exact allowlist. Name-pattern matching can silently make a new
7
- * state-changing method replayable when it happens to share a suffix.
8
- */
9
- export const PROTOCOL_V2_RETRY_ON_LOCKED_METHODS = [
10
- 'cipherKeyValue',
11
- 'allNetworkGetAddress',
12
- 'allNetworkGetAddressByLoop',
13
- 'btcGetAddress',
14
- 'btcGetPublicKey',
15
- 'btcSignMessage',
16
- 'btcSignPsbt',
17
- 'btcSignTransaction',
18
- 'btcVerifyMessage',
19
- 'confluxGetAddress',
20
- 'confluxSignMessage',
21
- 'confluxSignTransaction',
22
- 'evmGetAddress',
23
- 'evmGetPublicKey',
24
- 'evmSignMessage',
25
- 'evmSignTransaction',
26
- 'evmSignTypedData',
27
- 'evmVerifyMessage',
28
- 'starcoinGetAddress',
29
- 'starcoinGetPublicKey',
30
- 'starcoinSignMessage',
31
- 'starcoinSignTransaction',
32
- 'starcoinVerifyMessage',
33
- 'nemGetAddress',
34
- 'nemSignTransaction',
35
- 'solGetAddress',
36
- 'solSignTransaction',
37
- 'solSignOffchainMessage',
38
- 'solSignMessage',
39
- 'stellarGetAddress',
40
- 'stellarSignTransaction',
41
- 'tronGetAddress',
42
- 'tronSignMessage',
43
- 'tronSignTransaction',
44
- 'nearGetAddress',
45
- 'nearSignTransaction',
46
- 'aptosGetAddress',
47
- 'aptosGetPublicKey',
48
- 'aptosSignTransaction',
49
- 'aptosSignMessage',
50
- 'aptosSignInMessage',
51
- 'algoGetAddress',
52
- 'algoSignTransaction',
53
- 'cosmosGetAddress',
54
- 'cosmosGetPublicKey',
55
- 'cosmosSignTransaction',
56
- 'xrpGetAddress',
57
- 'xrpSignTransaction',
58
- 'suiGetAddress',
59
- 'suiGetPublicKey',
60
- 'suiSignMessage',
61
- 'suiSignTransaction',
62
- 'cardanoGetAddress',
63
- 'cardanoGetPublicKey',
64
- 'cardanoSignTransaction',
65
- 'cardanoSignMessage',
66
- 'filecoinGetAddress',
67
- 'filecoinSignTransaction',
68
- 'polkadotGetAddress',
69
- 'polkadotSignTransaction',
70
- 'kaspaGetAddress',
71
- 'kaspaSignTransaction',
72
- 'nexaGetAddress',
73
- 'nexaSignTransaction',
74
- 'nostrGetPublicKey',
75
- 'nostrSignEvent',
76
- 'nostrEncryptMessage',
77
- 'nostrDecryptMessage',
78
- 'nostrSignSchnorr',
79
- 'lnurlAuth',
80
- 'nervosGetAddress',
81
- 'nervosSignTransaction',
82
- 'dnxGetAddress',
83
- 'dnxSignTransaction',
84
- 'tonGetAddress',
85
- 'tonSignMessage',
86
- 'tonSignProof',
87
- 'tonSignData',
88
- 'scdoGetAddress',
89
- 'scdoSignTransaction',
90
- 'scdoSignMessage',
91
- 'alephiumGetAddress',
92
- 'alephiumSignTransaction',
93
- 'alephiumSignMessage',
94
- 'benfenGetAddress',
95
- 'benfenGetPublicKey',
96
- 'benfenSignMessage',
97
- 'benfenSignTransaction',
98
- 'neoGetAddress',
99
- 'neoSignTransaction',
100
- ] as const;
101
-
102
- const retryOnLockedMethods = new Set<string>(PROTOCOL_V2_RETRY_ON_LOCKED_METHODS);
103
-
104
- export const getProtocolV2UnlockPolicy = (methodName: string): UnlockPolicy =>
105
- retryOnLockedMethods.has(methodName) ? 'retry-on-locked' : 'none';
1
+ export type UnlockPolicy = 'none' | 'unlock-before-run';
@@ -0,0 +1,117 @@
1
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import { LoggerNames, getLogger } from '../../utils';
4
+ import { isProtocolV2UiEnabled, resolveProtocolV2UiInteraction } from './uiInteraction';
5
+ import { refreshProtocolV2DeviceStatus } from './walletSession';
6
+
7
+ import type { BaseMethod } from '../../api/BaseMethod';
8
+ import type { Device } from '../../device/Device';
9
+ import type { HardwareUiInteractionMeta } from '../../events';
10
+ import type { ProtocolV2UiInteractionCoordinator } from './uiInteraction';
11
+
12
+ const Log = getLogger(LoggerNames.Core);
13
+
14
+ export type ProtocolV2UnlockContext = {
15
+ preflightCompleted: boolean;
16
+ };
17
+
18
+ export const createProtocolV2UnlockContext = (): ProtocolV2UnlockContext => ({
19
+ preflightCompleted: false,
20
+ });
21
+
22
+ type RunnableMethod = Pick<
23
+ BaseMethod,
24
+ | 'run'
25
+ | 'unlockPolicy'
26
+ | 'useDevicePassphraseState'
27
+ | 'protocolV2UiInteraction'
28
+ | 'protocolV2UiMode'
29
+ | 'params'
30
+ | 'payload'
31
+ > & {
32
+ name?: string;
33
+ protocolV2UnlockContext?: ProtocolV2UnlockContext;
34
+ };
35
+
36
+ type UiInteractionCoordinator = Pick<
37
+ ProtocolV2UiInteractionCoordinator,
38
+ 'enterMethodInteraction' | 'enterUnlockInteraction'
39
+ >;
40
+
41
+ type RunMethodWithUnlockPolicyOptions<T> = {
42
+ context?: ProtocolV2UnlockContext;
43
+ uiCoordinator?: UiInteractionCoordinator;
44
+ afterStatusBeforeUnlock?: () => void | Promise<void>;
45
+ prepare?: () => Promise<void>;
46
+ run?: () => Promise<T>;
47
+ };
48
+
49
+ export async function runMethodWithUnlockPolicy<T = unknown>(
50
+ method: RunnableMethod,
51
+ device: Device,
52
+ options: RunMethodWithUnlockPolicyOptions<T> = {}
53
+ ): Promise<T> {
54
+ const {
55
+ context = createProtocolV2UnlockContext(),
56
+ uiCoordinator,
57
+ afterStatusBeforeUnlock,
58
+ prepare,
59
+ run: configuredRun,
60
+ } = options;
61
+ const run = configuredRun ?? (() => method.run() as Promise<T>);
62
+ method.protocolV2UnlockContext = context;
63
+
64
+ const shouldEmitUi = isProtocolV2UiEnabled(method);
65
+ const shouldCoordinateUi = shouldEmitUi && uiCoordinator !== undefined;
66
+ const requiresPreUnlock =
67
+ device.isProtocolV2() &&
68
+ (method.useDevicePassphraseState || method.unlockPolicy === 'unlock-before-run') &&
69
+ !device.isBootloader?.() &&
70
+ !device.isRomloader?.();
71
+
72
+ if (requiresPreUnlock && !context.preflightCompleted) {
73
+ const status = await refreshProtocolV2DeviceStatus(device);
74
+ if (typeof status?.unlocked !== 'boolean') {
75
+ throw ERRORS.TypedError(
76
+ HardwareErrorCode.RuntimeError,
77
+ 'Protocol V2 DeviceStatus did not report an explicit unlock state.'
78
+ );
79
+ }
80
+
81
+ await afterStatusBeforeUnlock?.();
82
+
83
+ if (!status.unlocked) {
84
+ const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
85
+ ? uiCoordinator.enterUnlockInteraction(method.name)
86
+ : undefined;
87
+ const unlockedStatus = await device.unlockDevice(
88
+ undefined,
89
+ shouldCoordinateUi
90
+ ? { emitUiEvent: false, interaction: unlockInteraction }
91
+ : {
92
+ source: 'unlock-coordinator',
93
+ reason: 'device-locked',
94
+ deviceOnly: true,
95
+ method: method.name,
96
+ }
97
+ );
98
+ if (unlockedStatus?.unlocked !== true) {
99
+ throw ERRORS.TypedError(
100
+ HardwareErrorCode.RuntimeError,
101
+ 'Protocol V2 device remained locked after the unlock flow.'
102
+ );
103
+ }
104
+ Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
105
+ }
106
+
107
+ context.preflightCompleted = true;
108
+ }
109
+
110
+ await prepare?.();
111
+
112
+ if (shouldCoordinateUi) {
113
+ uiCoordinator.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
114
+ }
115
+
116
+ return run();
117
+ }