@onekeyfe/hd-core 1.2.0-alpha.52 → 1.2.0-alpha.53

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 (251) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -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__/deviceUploadNft.test.ts +293 -0
  8. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  9. package/__tests__/get-device-state.test.ts +132 -12
  10. package/__tests__/homescreen.test.ts +41 -1
  11. package/__tests__/logBlockEvent.test.ts +1 -1
  12. package/__tests__/method-protocol-support.test.ts +19 -0
  13. package/__tests__/open-wallet-session.test.ts +446 -33
  14. package/__tests__/pro2Nft.test.ts +108 -0
  15. package/__tests__/protocol-binding.test.ts +89 -0
  16. package/__tests__/protocol-v2-resources.test.ts +284 -0
  17. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  18. package/__tests__/protocol-v2-unlock-policy.test.ts +201 -35
  19. package/__tests__/protocol-v2.test.ts +1349 -1005
  20. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  21. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  22. package/__tests__/search-devices.test.ts +12 -3
  23. package/__tests__/uiProgressThrottle.test.ts +74 -0
  24. package/__tests__/uiPromiseRegistry.test.ts +86 -0
  25. package/dist/api/BaseMethod.d.ts +3 -1
  26. package/dist/api/BaseMethod.d.ts.map +1 -1
  27. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
  28. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  29. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  30. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  31. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  32. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  33. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  34. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  35. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  36. package/dist/api/FirmwareUpdateV4.d.ts +11 -25
  37. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  38. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  39. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  40. package/dist/api/SearchDevices.d.ts.map +1 -1
  41. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  42. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  43. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  44. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  45. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  46. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -9
  47. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  48. package/dist/api/firmware/getBinary.d.ts +0 -7
  49. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  50. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  51. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  52. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  53. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  54. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  55. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  56. package/dist/api/index.d.ts +2 -0
  57. package/dist/api/index.d.ts.map +1 -1
  58. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  59. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  60. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  61. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  62. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  63. package/dist/core/deviceEventRegistration.d.ts +2 -0
  64. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  65. package/dist/core/index.d.ts.map +1 -1
  66. package/dist/core/uiPromiseRegistry.d.ts +4 -0
  67. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  68. package/dist/data-manager/DataManager.d.ts +4 -2
  69. package/dist/data-manager/DataManager.d.ts.map +1 -1
  70. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  71. package/dist/device/Device.d.ts +29 -6
  72. package/dist/device/Device.d.ts.map +1 -1
  73. package/dist/device/DeviceCommands.d.ts +4 -4
  74. package/dist/device/DeviceCommands.d.ts.map +1 -1
  75. package/dist/device/DevicePool.d.ts.map +1 -1
  76. package/dist/events/device.d.ts +4 -0
  77. package/dist/events/device.d.ts.map +1 -1
  78. package/dist/events/logBlockEvent.d.ts.map +1 -1
  79. package/dist/events/ui-promise.d.ts +4 -2
  80. package/dist/events/ui-promise.d.ts.map +1 -1
  81. package/dist/events/ui-request.d.ts +31 -2
  82. package/dist/events/ui-request.d.ts.map +1 -1
  83. package/dist/events/ui-response.d.ts +10 -2
  84. package/dist/events/ui-response.d.ts.map +1 -1
  85. package/dist/index.d.ts +214 -231
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2559 -3497
  88. package/dist/inject.d.ts +6 -1
  89. package/dist/inject.d.ts.map +1 -1
  90. package/dist/lowLevelInject.d.ts.map +1 -1
  91. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  92. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  93. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  94. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  95. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  96. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  97. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  98. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  99. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  100. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  101. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  102. package/dist/topLevelInject.d.ts.map +1 -1
  103. package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -4
  104. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  105. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  106. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  107. package/dist/types/api/deviceUnlock.d.ts +5 -1
  108. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  109. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  110. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  111. package/dist/types/api/export.d.ts +2 -4
  112. package/dist/types/api/export.d.ts.map +1 -1
  113. package/dist/types/api/firmwareUpdate.d.ts +2 -70
  114. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  115. package/dist/types/api/index.d.ts +7 -10
  116. package/dist/types/api/index.d.ts.map +1 -1
  117. package/dist/types/api/protocolV2.d.ts +3 -0
  118. package/dist/types/api/protocolV2.d.ts.map +1 -1
  119. package/dist/types/device.d.ts.map +1 -1
  120. package/dist/types/params.d.ts +1 -0
  121. package/dist/types/params.d.ts.map +1 -1
  122. package/dist/types/settings.d.ts +22 -13
  123. package/dist/types/settings.d.ts.map +1 -1
  124. package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
  125. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  126. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  127. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  128. package/dist/utils/homescreen.d.ts +4 -0
  129. package/dist/utils/homescreen.d.ts.map +1 -1
  130. package/dist/utils/index.d.ts +1 -1
  131. package/dist/utils/index.d.ts.map +1 -1
  132. package/dist/utils/patch.d.ts +1 -1
  133. package/dist/utils/patch.d.ts.map +1 -1
  134. package/dist/utils/pro2Nft.d.ts +31 -0
  135. package/dist/utils/pro2Nft.d.ts.map +1 -0
  136. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  137. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  138. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  139. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  140. package/package.json +4 -4
  141. package/src/api/BaseMethod.ts +8 -10
  142. package/src/api/CheckAllFirmwareRelease.ts +54 -57
  143. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  144. package/src/api/FirmwareUpdate.ts +7 -15
  145. package/src/api/FirmwareUpdateV2.ts +126 -316
  146. package/src/api/FirmwareUpdateV3.ts +63 -265
  147. package/src/api/FirmwareUpdateV4.ts +566 -965
  148. package/src/api/GetPassphraseState.ts +7 -1
  149. package/src/api/OpenWalletSession.ts +54 -17
  150. package/src/api/SearchDevices.ts +3 -1
  151. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  152. package/src/api/device/DeviceUnlock.ts +8 -3
  153. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  154. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +16 -83
  155. package/src/api/firmware/updateBootloader.ts +4 -19
  156. package/src/api/firmware/uploadFirmware.ts +39 -144
  157. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  158. package/src/api/index.ts +2 -0
  159. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  160. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  161. package/src/api/protocol-v2/helpers.ts +1 -0
  162. package/src/core/deviceEventRegistration.ts +4 -0
  163. package/src/core/index.ts +167 -83
  164. package/src/core/uiPromiseRegistry.ts +41 -0
  165. package/src/data/messages/messages-protocol-v2.json +25 -0
  166. package/src/data-manager/DataManager.ts +67 -38
  167. package/src/data-manager/connectSettings.ts +0 -11
  168. package/src/device/Device.ts +258 -42
  169. package/src/device/DeviceCommands.ts +7 -1
  170. package/src/device/DevicePool.ts +7 -2
  171. package/src/events/device.ts +4 -0
  172. package/src/events/logBlockEvent.ts +1 -0
  173. package/src/events/ui-promise.ts +4 -2
  174. package/src/events/ui-request.ts +36 -2
  175. package/src/events/ui-response.ts +12 -2
  176. package/src/index.ts +0 -5
  177. package/src/inject.ts +60 -24
  178. package/src/lowLevelInject.ts +7 -8
  179. package/src/protocols/protocol-v2/features.ts +10 -3
  180. package/src/protocols/protocol-v2/resources.ts +359 -0
  181. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  182. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  183. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +118 -0
  184. package/src/protocols/protocol-v2/walletSession.ts +180 -30
  185. package/src/topLevelInject.ts +7 -8
  186. package/src/types/api/checkAllFirmwareRelease.ts +2 -4
  187. package/src/types/api/detectDeviceConnectProtocol.ts +7 -0
  188. package/src/types/api/deviceUnlock.ts +12 -1
  189. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  190. package/src/types/api/export.ts +1 -18
  191. package/src/types/api/firmwareUpdate.ts +3 -76
  192. package/src/types/api/index.ts +13 -14
  193. package/src/types/api/protocolV2.ts +13 -0
  194. package/src/types/device.ts +3 -0
  195. package/src/types/params.ts +7 -1
  196. package/src/types/settings.ts +42 -13
  197. package/src/utils/deviceFeaturesCompat.ts +0 -6
  198. package/src/utils/deviceFeaturesUtils.ts +8 -0
  199. package/src/utils/homescreen.ts +32 -6
  200. package/src/utils/index.ts +6 -1
  201. package/src/utils/pro2Nft.ts +142 -0
  202. package/src/utils/pro2Wallpaper.ts +35 -8
  203. package/src/utils/uiProgressThrottle.ts +63 -0
  204. package/__tests__/device-initialize-timeout.test.ts +0 -56
  205. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  206. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  207. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  208. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  209. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  210. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  211. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -294
  212. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  213. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  214. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  215. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  216. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  217. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  218. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  219. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  220. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  221. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  222. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  223. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  224. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  225. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  226. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
  227. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  228. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  229. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  230. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  231. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  232. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  233. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  234. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  235. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  236. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  237. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  238. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  239. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  240. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  241. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  242. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  243. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  244. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
  245. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -415
  246. package/src/api/firmware/progressThrottle.ts +0 -44
  247. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  248. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  249. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  250. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  251. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
package/src/index.ts CHANGED
@@ -19,11 +19,6 @@ export * from './types';
19
19
  export { whitelist, whitelistExtension } from './data/config';
20
20
  export { executeCallback, cleanupCallback };
21
21
  export { preloadSessionCache } from './device/Device';
22
- export {
23
- getFirmwareUpdateHostBindingGeneration,
24
- registerFirmwareUpdateHostBinding,
25
- unregisterFirmwareUpdateHostBinding,
26
- } from './api/firmware/FirmwareHostBinding';
27
22
 
28
23
  const HardwareSdk = ({
29
24
  init,
package/src/inject.ts CHANGED
@@ -1,17 +1,7 @@
1
- import { getFirmwareUpdateCapabilities } from './api/firmware/FirmwareUpdateCapabilities';
2
- import {
3
- prepareFirmwareUpdatePlan,
4
- validateFirmwareUpdatePreparedPlan,
5
- } from './api/firmware/FirmwareUpdatePreparedPlan';
6
- import {
7
- getFirmwareUpdateHostBindingGeneration,
8
- registerFirmwareUpdateHostBinding,
9
- unregisterFirmwareUpdateHostBinding,
10
- } from './api/firmware/FirmwareHostBinding';
11
-
1
+ import type { HardwareConnectProtocol } from '@onekeyfe/hd-shared';
2
+ import type { Unsuccessful } from './types';
12
3
  import type { EventEmitter } from 'events';
13
4
  import type { CallMethod } from './events';
14
- import type { Unsuccessful } from './types';
15
5
  import type { CoreApi } from './types/api';
16
6
  import type { AllNetworkAddress } from './types/api/allNetworkGetAddress';
17
7
 
@@ -50,6 +40,50 @@ export interface InjectApi {
50
40
  switchTransport: CoreApi['switchTransport'];
51
41
  }
52
42
 
43
+ const normalizeConnectId = (connectId: string) => connectId.trim().toLowerCase();
44
+
45
+ /**
46
+ * Keeps verified protocols isolated per transport endpoint and injects them at
47
+ * the single public call boundary, including APIs that do not expose params.
48
+ */
49
+ export const createProtocolAwareCall = (rawCall: CoreApi['call']) => {
50
+ const protocolByConnectId = new Map<string, HardwareConnectProtocol>();
51
+
52
+ const setDeviceConnectProtocol: CoreApi['setDeviceConnectProtocol'] = (
53
+ connectId,
54
+ connectProtocol
55
+ ) => {
56
+ const normalizedConnectId = normalizeConnectId(connectId);
57
+ if (!normalizedConnectId) return;
58
+ if (connectProtocol) {
59
+ protocolByConnectId.set(normalizedConnectId, connectProtocol);
60
+ } else {
61
+ protocolByConnectId.delete(normalizedConnectId);
62
+ }
63
+ };
64
+
65
+ const call: CoreApi['call'] = params => {
66
+ if (!params || typeof params !== 'object') {
67
+ return rawCall(params);
68
+ }
69
+
70
+ const connectId = typeof params.connectId === 'string' ? params.connectId : undefined;
71
+ const boundProtocol = connectId
72
+ ? protocolByConnectId.get(normalizeConnectId(connectId))
73
+ : undefined;
74
+ if (
75
+ boundProtocol &&
76
+ params.connectProtocol === undefined &&
77
+ params.forceProtocolDetection !== true
78
+ ) {
79
+ return rawCall({ ...params, connectProtocol: boundProtocol });
80
+ }
81
+ return rawCall(params);
82
+ };
83
+
84
+ return { call, setDeviceConnectProtocol };
85
+ };
86
+
53
87
  export const inject = ({
54
88
  call,
55
89
  cancel,
@@ -60,6 +94,7 @@ export const inject = ({
60
94
  switchTransport,
61
95
  uiResponse,
62
96
  }: InjectApi): CoreApi => {
97
+ const protocolAwareCall = createProtocolAwareCall(call);
63
98
  const api: CoreApi = {
64
99
  on: <T extends string, P extends (...args: any[]) => any>(type: T, fn: P) => {
65
100
  eventEmitter.on(type, fn);
@@ -77,12 +112,11 @@ export const inject = ({
77
112
 
78
113
  init,
79
114
 
80
- call,
115
+ call: protocolAwareCall.call,
81
116
 
82
- dispose: async () => {
83
- unregisterFirmwareUpdateHostBinding();
84
- await dispose();
85
- },
117
+ setDeviceConnectProtocol: protocolAwareCall.setDeviceConnectProtocol,
118
+
119
+ dispose,
86
120
 
87
121
  uiResponse,
88
122
 
@@ -92,7 +126,7 @@ export const inject = ({
92
126
 
93
127
  switchTransport,
94
128
 
95
- ...createCoreApi(call),
129
+ ...createCoreApi(protocolAwareCall.call),
96
130
  };
97
131
  return api;
98
132
  };
@@ -107,24 +141,25 @@ export const createCoreApi = (
107
141
  | 'removeAllListeners'
108
142
  | 'init'
109
143
  | 'call'
144
+ | 'setDeviceConnectProtocol'
110
145
  | 'dispose'
111
146
  | 'uiResponse'
112
147
  | 'cancel'
113
148
  | 'updateSettings'
114
149
  | 'switchTransport'
115
150
  > => ({
116
- getFirmwareUpdateCapabilities,
117
- registerFirmwareUpdateHostBinding,
118
- unregisterFirmwareUpdateHostBinding,
119
- getFirmwareUpdateHostBindingGeneration,
120
- prepareFirmwareUpdatePlan,
121
- validateFirmwareUpdatePreparedPlan,
122
151
  getLogs: () => call({ method: 'getLogs' }),
123
152
  clearSessionCache: params => call({ ...params, method: 'clearSessionCache' }),
124
153
  /**
125
154
  * 搜索设备
126
155
  */
127
156
  searchDevices: params => call({ ...params, method: 'searchDevices' }),
157
+ detectDeviceConnectProtocol: connectId =>
158
+ call({
159
+ connectId,
160
+ method: 'detectDeviceConnectProtocol',
161
+ forceProtocolDetection: true,
162
+ }),
128
163
 
129
164
  /**
130
165
  * 获取设备信息
@@ -182,6 +217,7 @@ export const createCoreApi = (
182
217
  call({ ...params, connectId, method: 'deviceGetOnboardingStatus' }),
183
218
  deviceUploadWallpaper: (connectId, params) =>
184
219
  call({ ...params, connectId, method: 'deviceUploadWallpaper' }),
220
+ deviceUploadNft: (connectId, params) => call({ ...params, connectId, method: 'deviceUploadNft' }),
185
221
  uploadPortfolio: (connectId, params) => call({ ...params, connectId, method: 'uploadPortfolio' }),
186
222
  deviceRecovery: (connectId, params) => call({ ...params, connectId, method: 'deviceRecovery' }),
187
223
  deviceReset: (connectId, params) => call({ ...params, connectId, method: 'deviceReset' }),
@@ -1,5 +1,4 @@
1
- import { createCoreApi } from './inject';
2
- import { unregisterFirmwareUpdateHostBinding } from './api/firmware/FirmwareHostBinding';
1
+ import { createCoreApi, createProtocolAwareCall } from './inject';
3
2
 
4
3
  import type { EventEmitter } from 'events';
5
4
  import type { CallMethod, CoreMessage } from './events';
@@ -34,6 +33,7 @@ export const lowLevelInject = ({
34
33
  switchTransport,
35
34
  addHardwareGlobalEventListener,
36
35
  }: LowLevelInjectApi): LowLevelCoreApi => {
36
+ const protocolAwareCall = createProtocolAwareCall(call);
37
37
  const api: LowLevelCoreApi = {
38
38
  addHardwareGlobalEventListener,
39
39
  removeAllListeners: type => {
@@ -42,12 +42,11 @@ export const lowLevelInject = ({
42
42
 
43
43
  init,
44
44
 
45
- call,
45
+ call: protocolAwareCall.call,
46
46
 
47
- dispose: async () => {
48
- unregisterFirmwareUpdateHostBinding();
49
- await dispose();
50
- },
47
+ setDeviceConnectProtocol: protocolAwareCall.setDeviceConnectProtocol,
48
+
49
+ dispose,
51
50
 
52
51
  uiResponse,
53
52
 
@@ -59,7 +58,7 @@ export const lowLevelInject = ({
59
58
 
60
59
  emit: () => {},
61
60
 
62
- ...createCoreApi(call),
61
+ ...createCoreApi(protocolAwareCall.call),
63
62
  };
64
63
  return api;
65
64
  };
@@ -157,7 +157,7 @@ export const PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST = {
157
157
  };
158
158
 
159
159
  export const PROTOCOL_V2_DEVICE_INFO_REQUEST = PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST;
160
- export const PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS = 10 * 1000;
160
+ export const PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS = 30 * 1000;
161
161
 
162
162
  export async function requestProtocolV2ProtocolInfo({
163
163
  commands,
@@ -168,8 +168,15 @@ export async function requestProtocolV2ProtocolInfo({
168
168
  }): Promise<ProtocolInfo> {
169
169
  const response =
170
170
  timeoutMs === undefined
171
- ? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {})
172
- : await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {}, { timeoutMs });
171
+ ? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
172
+ eventless_wallet_session: true,
173
+ })
174
+ : await commands.typedCall(
175
+ 'ProtocolInfoRequest',
176
+ 'ProtocolInfo',
177
+ { eventless_wallet_session: true },
178
+ { timeoutMs }
179
+ );
173
180
  return response.message;
174
181
  }
175
182
 
@@ -0,0 +1,359 @@
1
+ import { sha256 } from '@noble/hashes/sha256';
2
+ import { PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE } from '@onekeyfe/hd-transport';
3
+
4
+ import type {
5
+ IProtocolV2BootResources,
6
+ IProtocolV2Resource,
7
+ IProtocolV2ResourceType,
8
+ IProtocolV2Resources,
9
+ } from '../../types';
10
+ import type { DeviceCommands } from '../../device/DeviceCommands';
11
+
12
+ export const PROTOCOL_V2_RESOURCE_TYPES = [
13
+ 'images',
14
+ 'animation',
15
+ 'wallpaper',
16
+ 'translations',
17
+ 'roobert',
18
+ 'noto',
19
+ ] as const satisfies readonly IProtocolV2ResourceType[];
20
+
21
+ export const PROTOCOL_V2_RESOURCE_DEVICE_PATHS: Readonly<Record<IProtocolV2ResourceType, string>> =
22
+ {
23
+ images: 'vol0:/bundles/images/images.okpkg',
24
+ animation: 'vol0:/bundles/images/animation.okpkg',
25
+ wallpaper: 'vol0:/bundles/images/wallpaper.okpkg',
26
+ translations: 'vol0:/bundles/translations/translations.okpkg',
27
+ roobert: 'vol0:/bundles/font/roobert.okpkg',
28
+ noto: 'vol0:/bundles/font/noto.okpkg',
29
+ };
30
+
31
+ const RESOURCE_TYPE_SET = new Set<string>(PROTOCOL_V2_RESOURCE_TYPES);
32
+ const SHA256_HEX_LENGTH = 64;
33
+ const SHA3_512_HEX_LENGTH = 128;
34
+ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
35
+ const PROTOCOL_V2_OKPP_TYPE_OFFSET = 0x08;
36
+ const PROTOCOL_V2_OKPP_HEADER_LENGTH_OFFSET = 0x0c;
37
+ const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
38
+ const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
39
+ const PROTOCOL_V2_RESOURCE_IDENTITY_READ_SIZE =
40
+ PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE;
41
+ const PROTOCOL_V2_MIN_FILE_READ_CHUNK_SIZE = 64;
42
+ export const PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS = 5 * 1000;
43
+
44
+ export type ProtocolV2ResourceInventoryItem = {
45
+ type: IProtocolV2ResourceType;
46
+ size: number;
47
+ headerHash: string;
48
+ };
49
+
50
+ export type ProtocolV2ResourceUpdateMode = 'application' | 'bootloader-recovery';
51
+
52
+ export type ProtocolV2ResourceUpdatePlan = {
53
+ status: 'valid' | 'outdated' | 'unknown';
54
+ resources: IProtocolV2Resource[];
55
+ };
56
+
57
+ function toFiniteNumber(value: unknown): number | undefined {
58
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
59
+ if (typeof value === 'string') {
60
+ const numeric = Number(value);
61
+ return Number.isFinite(numeric) ? numeric : undefined;
62
+ }
63
+ if (value && typeof value === 'object') {
64
+ const longLike = value as { toNumber?: () => number };
65
+ if (typeof longLike.toNumber === 'function') {
66
+ const numeric = longLike.toNumber();
67
+ return Number.isFinite(numeric) ? numeric : undefined;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ function toUint8Array(value: unknown): Uint8Array {
74
+ if (value instanceof Uint8Array) return value;
75
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
76
+ if (ArrayBuffer.isView(value)) {
77
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
78
+ }
79
+ if (typeof value === 'string') {
80
+ const hex = value.replace(/^0x/i, '');
81
+ if (!hex || hex.length % 2 !== 0 || /[^0-9a-f]/i.test(hex)) {
82
+ return new Uint8Array(0);
83
+ }
84
+ const bytes = new Uint8Array(hex.length / 2);
85
+ for (let index = 0; index < bytes.length; index += 1) {
86
+ bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
87
+ }
88
+ return bytes;
89
+ }
90
+ return new Uint8Array(0);
91
+ }
92
+
93
+ function readAscii(bytes: Uint8Array, offset: number, length: number): string {
94
+ return Array.from(bytes.slice(offset, offset + length), byte => String.fromCharCode(byte)).join(
95
+ ''
96
+ );
97
+ }
98
+
99
+ function parseProtocolV2ResourceHeaderHash(bytes: Uint8Array): string | undefined {
100
+ if (bytes.byteLength < PROTOCOL_V2_RESOURCE_IDENTITY_READ_SIZE) return undefined;
101
+ if (readAscii(bytes, 0, 4) !== 'OKPP') return undefined;
102
+ if (readAscii(bytes, PROTOCOL_V2_OKPP_TYPE_OFFSET, 4) !== 'RESC') return undefined;
103
+
104
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
105
+ if (
106
+ view.getUint32(PROTOCOL_V2_OKPP_HEADER_LENGTH_OFFSET, true) !== PROTOCOL_V2_OKPP_HEADER_SIZE
107
+ ) {
108
+ return undefined;
109
+ }
110
+ return bytesToHex(
111
+ bytes.slice(
112
+ PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
113
+ PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
114
+ )
115
+ );
116
+ }
117
+
118
+ async function readProtocolV2ResourceIdentity({
119
+ commands,
120
+ resource,
121
+ chunkSize,
122
+ timeoutMs = PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS,
123
+ }: {
124
+ commands: Pick<DeviceCommands, 'typedCall'>;
125
+ resource: IProtocolV2Resource;
126
+ chunkSize: number;
127
+ timeoutMs?: number;
128
+ }): Promise<ProtocolV2ResourceInventoryItem | undefined> {
129
+ const path = PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type];
130
+ const pathInfo = await commands.typedCall(
131
+ 'FilesystemPathInfoQuery',
132
+ 'FilesystemPathInfo',
133
+ { path },
134
+ { timeoutMs }
135
+ );
136
+ const size = toFiniteNumber(pathInfo.message?.size);
137
+ if (
138
+ !pathInfo.message?.exist ||
139
+ pathInfo.message?.directory ||
140
+ !Number.isSafeInteger(size) ||
141
+ size !== resource.size ||
142
+ size < PROTOCOL_V2_OKPP_HEADER_SIZE
143
+ ) {
144
+ return undefined;
145
+ }
146
+
147
+ const header = new Uint8Array(PROTOCOL_V2_RESOURCE_IDENTITY_READ_SIZE);
148
+ let offset = 0;
149
+ while (offset < header.byteLength) {
150
+ const readLength = Math.min(chunkSize, header.byteLength - offset);
151
+ const response = await commands.typedCall(
152
+ 'FilesystemFileRead',
153
+ 'FilesystemFile',
154
+ {
155
+ file: { path, offset, total_size: 0 },
156
+ chunk_len: readLength,
157
+ },
158
+ { timeoutMs }
159
+ );
160
+ const data = toUint8Array(response.message?.data);
161
+ if (data.byteLength === 0) return undefined;
162
+ const copied = Math.min(data.byteLength, header.byteLength - offset);
163
+ header.set(data.subarray(0, copied), offset);
164
+ offset += copied;
165
+ }
166
+
167
+ const headerHash = parseProtocolV2ResourceHeaderHash(header);
168
+ return headerHash ? { type: resource.type, size, headerHash } : undefined;
169
+ }
170
+
171
+ /**
172
+ * 使用已发布 Pro2 固件支持的文件系统消息构建资源清单。
173
+ * 缺失、无法读取或格式错误的文件不会进入清单,因此会被选中重写。
174
+ */
175
+ export async function readProtocolV2ResourceInventory({
176
+ commands,
177
+ resources,
178
+ chunkSize = PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
179
+ timeoutMs = PROTOCOL_V2_RESOURCE_INVENTORY_TIMEOUT_MS,
180
+ }: {
181
+ commands: Pick<DeviceCommands, 'typedCall'>;
182
+ resources: readonly IProtocolV2Resource[];
183
+ chunkSize?: number;
184
+ timeoutMs?: number;
185
+ }): Promise<ProtocolV2ResourceInventoryItem[]> {
186
+ const normalizedChunkSize = Number.isFinite(chunkSize)
187
+ ? Math.max(Math.floor(chunkSize), PROTOCOL_V2_MIN_FILE_READ_CHUNK_SIZE)
188
+ : PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE;
189
+ const inventory: ProtocolV2ResourceInventoryItem[] = [];
190
+ for (const resource of resources) {
191
+ try {
192
+ const item = await readProtocolV2ResourceIdentity({
193
+ commands,
194
+ resource,
195
+ chunkSize: normalizedChunkSize,
196
+ timeoutMs,
197
+ });
198
+ if (item) inventory.push(item);
199
+ } catch {
200
+ // 单个资源无法读取时按缺失处理,不阻断其余资源的增量检查。
201
+ }
202
+ }
203
+ return inventory;
204
+ }
205
+
206
+ function normalizeHex(value: unknown, expectedLength: number, field: string): string {
207
+ if (typeof value !== 'string') {
208
+ throw new Error(`Invalid Pro2 resource ${field}: expected a hexadecimal string`);
209
+ }
210
+ const normalized = value.replace(/^0x/i, '').toLowerCase();
211
+ if (normalized.length !== expectedLength || !/^[0-9a-f]+$/.test(normalized)) {
212
+ throw new Error(
213
+ `Invalid Pro2 resource ${field}: expected ${expectedLength} hexadecimal characters`
214
+ );
215
+ }
216
+ return normalized;
217
+ }
218
+
219
+ function validateResource(value: unknown, index: number): IProtocolV2Resource {
220
+ if (!value || typeof value !== 'object') {
221
+ throw new Error(`Invalid Pro2 resource at stable[${index}]`);
222
+ }
223
+ const resource = value as Partial<IProtocolV2Resource>;
224
+ if (typeof resource.type !== 'string' || !RESOURCE_TYPE_SET.has(resource.type)) {
225
+ throw new Error(`Invalid Pro2 resource type at stable[${index}]`);
226
+ }
227
+ if (typeof resource.url !== 'string' || !resource.url.startsWith('https://')) {
228
+ throw new Error(`Invalid Pro2 resource url at stable[${index}]`);
229
+ }
230
+ if (!Number.isSafeInteger(resource.size) || Number(resource.size) <= 0) {
231
+ throw new Error(`Invalid Pro2 resource size at stable[${index}]`);
232
+ }
233
+ return {
234
+ type: resource.type,
235
+ url: resource.url,
236
+ size: Number(resource.size),
237
+ fileHash: normalizeHex(resource.fileHash, SHA256_HEX_LENGTH, 'fileHash'),
238
+ headerHash: normalizeHex(resource.headerHash, SHA3_512_HEX_LENGTH, 'headerHash'),
239
+ };
240
+ }
241
+
242
+ function validateBootResources(value: unknown): IProtocolV2BootResources {
243
+ if (!value || typeof value !== 'object') {
244
+ throw new Error('Invalid Pro2 boot resources config');
245
+ }
246
+ const resource = value as Partial<IProtocolV2BootResources>;
247
+ if (resource.required !== false) {
248
+ throw new Error('Invalid Pro2 boot resources required flag: expected false');
249
+ }
250
+ if (resource.target !== 'CRATE') {
251
+ throw new Error('Invalid Pro2 boot resources target: expected CRATE');
252
+ }
253
+ if (typeof resource.url !== 'string' || !resource.url.startsWith('https://')) {
254
+ throw new Error('Invalid Pro2 boot resources url');
255
+ }
256
+ if (!Number.isSafeInteger(resource.size) || Number(resource.size) <= 0) {
257
+ throw new Error('Invalid Pro2 boot resources size');
258
+ }
259
+ return {
260
+ required: false,
261
+ target: 'CRATE',
262
+ url: resource.url,
263
+ size: Number(resource.size),
264
+ fileHash: normalizeHex(resource.fileHash, SHA256_HEX_LENGTH, 'boot fileHash'),
265
+ payloadHash: normalizeHex(resource.payloadHash, SHA3_512_HEX_LENGTH, 'boot payloadHash'),
266
+ headerHash: normalizeHex(resource.headerHash, SHA3_512_HEX_LENGTH, 'boot headerHash'),
267
+ };
268
+ }
269
+
270
+ /** Validate a complete Pro2 stable resource set from remote configuration. */
271
+ export function parseProtocolV2Resources(value: unknown): IProtocolV2Resources | undefined {
272
+ if (value === undefined) return undefined;
273
+ if (
274
+ !value ||
275
+ typeof value !== 'object' ||
276
+ !Array.isArray((value as { stable?: unknown }).stable)
277
+ ) {
278
+ throw new Error('Invalid Pro2 resources config: stable must be an array');
279
+ }
280
+
281
+ const config = value as { stable: unknown[]; boot?: unknown };
282
+ const stable = config.stable.map(validateResource);
283
+ const types = new Set(stable.map(resource => resource.type));
284
+ if (stable.length !== PROTOCOL_V2_RESOURCE_TYPES.length || types.size !== stable.length) {
285
+ throw new Error('Invalid Pro2 resources config: stable must contain six unique resource types');
286
+ }
287
+ for (const type of PROTOCOL_V2_RESOURCE_TYPES) {
288
+ if (!types.has(type)) {
289
+ throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
290
+ }
291
+ }
292
+ const boot = config.boot === undefined ? undefined : validateBootResources(config.boot);
293
+ return {
294
+ stable: PROTOCOL_V2_RESOURCE_TYPES.map(type => {
295
+ const resource = stable.find(item => item.type === type);
296
+ if (!resource) {
297
+ throw new Error(`Invalid Pro2 resources config: stable is missing ${type}`);
298
+ }
299
+ return resource;
300
+ }),
301
+ ...(boot ? { boot } : undefined),
302
+ };
303
+ }
304
+
305
+ /** 比较文件系统资源清单;恢复模式无法取得清单时回退到全量更新。 */
306
+ export function buildProtocolV2ResourceUpdatePlan({
307
+ resources,
308
+ inventory,
309
+ mode,
310
+ forced = false,
311
+ }: {
312
+ resources: readonly IProtocolV2Resource[];
313
+ inventory?: readonly ProtocolV2ResourceInventoryItem[];
314
+ mode: ProtocolV2ResourceUpdateMode;
315
+ forced?: boolean;
316
+ }): ProtocolV2ResourceUpdatePlan {
317
+ if (forced) {
318
+ return {
319
+ status: resources.length > 0 ? 'outdated' : 'valid',
320
+ resources: [...resources],
321
+ };
322
+ }
323
+ if (!inventory) {
324
+ return mode === 'bootloader-recovery'
325
+ ? {
326
+ status: resources.length > 0 ? 'outdated' : 'valid',
327
+ resources: [...resources],
328
+ }
329
+ : { status: 'unknown', resources: [] };
330
+ }
331
+
332
+ const inventoryByType = new Map(inventory.map(item => [item.type, item]));
333
+ const changedResources = resources.filter(resource => {
334
+ const current = inventoryByType.get(resource.type);
335
+ return (
336
+ !current ||
337
+ current.size !== resource.size ||
338
+ current.headerHash.toLowerCase() !== resource.headerHash.toLowerCase()
339
+ );
340
+ });
341
+
342
+ return {
343
+ status: changedResources.length === 0 ? 'valid' : 'outdated',
344
+ resources: changedResources,
345
+ };
346
+ }
347
+
348
+ function bytesToHex(bytes: Uint8Array): string {
349
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
350
+ }
351
+
352
+ /** Verify the complete downloaded file before any device mutation. */
353
+ export function isProtocolV2ResourceFileValid(
354
+ binary: ArrayBuffer,
355
+ resource: Pick<IProtocolV2Resource, 'size' | 'fileHash'>
356
+ ): boolean {
357
+ if (binary.byteLength !== resource.size) return false;
358
+ return bytesToHex(sha256(new Uint8Array(binary))) === resource.fileHash.toLowerCase();
359
+ }
@@ -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,19 +192,45 @@ 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() {
187
203
  this.enterMethodInteraction(this.methodInteraction);
188
204
  }
189
205
 
190
- close() {
191
- if (!this.device.isProtocolV2() || !this.opened || this.closed) return;
206
+ close(
207
+ options: {
208
+ ensureOperationClose?: boolean;
209
+ protocolV2Operation?: boolean;
210
+ } = {}
211
+ ): boolean {
212
+ if (
213
+ (!options.protocolV2Operation && !this.device.isProtocolV2()) ||
214
+ (!options.ensureOperationClose &&
215
+ !this.opened &&
216
+ !this.device.hasOpenProtocolV2UiInteraction?.()) ||
217
+ this.closed
218
+ )
219
+ return false;
192
220
  this.closed = true;
193
- this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW));
221
+ const interaction = this.device.finishProtocolV2UiInteraction?.('succeeded', {
222
+ ensureMetadata: options.ensureOperationClose,
223
+ });
224
+ const device = this.device.toMessageObject();
225
+ this.postMessage(
226
+ interaction
227
+ ? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, {
228
+ ...interaction,
229
+ ...(device ? { device } : {}),
230
+ })
231
+ : createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
232
+ );
233
+ return true;
194
234
  }
195
235
 
196
236
  private emit(