@onekeyfe/hd-core 1.2.0-alpha.19 → 1.2.0-alpha.20

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 (256) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +75 -0
  2. package/__tests__/DeviceCommands.test.ts +143 -7
  3. package/__tests__/RequestQueue.test.ts +106 -0
  4. package/__tests__/UploadPortfolio.test.ts +46 -0
  5. package/__tests__/core-dispose.test.ts +34 -0
  6. package/__tests__/core-error-output.test.ts +39 -0
  7. package/__tests__/device-lifecycle-events.test.ts +117 -0
  8. package/__tests__/device-pool-state.test.ts +20 -1
  9. package/__tests__/device-settings.test.ts +184 -6
  10. package/__tests__/device-state-events.test.ts +1 -2
  11. package/__tests__/device-state-mapper.test.ts +33 -26
  12. package/__tests__/device-state-store.test.ts +1 -34
  13. package/__tests__/device-utils.test.ts +106 -0
  14. package/__tests__/device-wallet-session-store.test.ts +65 -29
  15. package/__tests__/deviceSettings.test.ts +21 -12
  16. package/__tests__/evmLedgerLegacySafety.test.ts +2 -2
  17. package/__tests__/evmSignTypedData.test.ts +80 -0
  18. package/__tests__/filesystemValidation.test.ts +53 -0
  19. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +402 -0
  20. package/__tests__/get-device-state.test.ts +77 -41
  21. package/__tests__/logBlockEvent.test.ts +24 -11
  22. package/__tests__/networkUtils.test.ts +386 -0
  23. package/__tests__/open-wallet-session-error-response.test.ts +43 -0
  24. package/__tests__/open-wallet-session.test.ts +559 -0
  25. package/__tests__/protocol-v2.test.ts +756 -452
  26. package/__tests__/public-device-state-api.test.ts +113 -13
  27. package/__tests__/public-pro2-api-boundary.test.ts +91 -0
  28. package/__tests__/search-devices.test.ts +43 -0
  29. package/dist/api/BaseMethod.d.ts +3 -0
  30. package/dist/api/BaseMethod.d.ts.map +1 -1
  31. package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
  32. package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
  33. package/dist/api/ClearSessionCache.d.ts.map +1 -1
  34. package/dist/api/DirList.d.ts.map +1 -1
  35. package/dist/api/FileRead.d.ts.map +1 -1
  36. package/dist/api/FirmwareUpdateV2.d.ts +2 -2
  37. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  38. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  39. package/dist/api/FirmwareUpdateV4.d.ts +3 -2
  40. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  41. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  42. package/dist/api/OpenWalletSession.d.ts +14 -0
  43. package/dist/api/OpenWalletSession.d.ts.map +1 -0
  44. package/dist/api/PathInfo.d.ts.map +1 -1
  45. package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
  46. package/dist/api/SearchDevices.d.ts +3 -0
  47. package/dist/api/SearchDevices.d.ts.map +1 -1
  48. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  49. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  50. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  51. package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
  52. package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
  53. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  54. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  55. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  56. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  57. package/dist/api/firmware/getBinary.d.ts +7 -3
  58. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  59. package/dist/api/helpers/filesystemValidation.d.ts +3 -0
  60. package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
  61. package/dist/api/helpers/paramsValidator.d.ts +1 -0
  62. package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
  63. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  64. package/dist/api/index.d.ts +7 -11
  65. package/dist/api/index.d.ts.map +1 -1
  66. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  67. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -1
  68. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -1
  69. package/dist/api/utils.d.ts.map +1 -1
  70. package/dist/core/RequestQueue.d.ts +17 -1
  71. package/dist/core/RequestQueue.d.ts.map +1 -1
  72. package/dist/core/index.d.ts +3 -1
  73. package/dist/core/index.d.ts.map +1 -1
  74. package/dist/device/Device.d.ts +6 -5
  75. package/dist/device/Device.d.ts.map +1 -1
  76. package/dist/device/DeviceCommands.d.ts +5 -5
  77. package/dist/device/DeviceCommands.d.ts.map +1 -1
  78. package/dist/device/DevicePool.d.ts +1 -0
  79. package/dist/device/DevicePool.d.ts.map +1 -1
  80. package/dist/device/DeviceStateMapper.d.ts +2 -2
  81. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  82. package/dist/device/DeviceStateProjector.d.ts +1 -2
  83. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  84. package/dist/device/DeviceStateStore.d.ts +1 -3
  85. package/dist/device/DeviceStateStore.d.ts.map +1 -1
  86. package/dist/device/DeviceWalletSessionStore.d.ts +6 -1
  87. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  88. package/dist/device/cloneDeviceState.d.ts +2 -0
  89. package/dist/device/cloneDeviceState.d.ts.map +1 -0
  90. package/dist/deviceProfile/buildDeviceFeatures.d.ts +3 -1
  91. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  92. package/dist/events/logBlockEvent.d.ts.map +1 -1
  93. package/dist/index.d.ts +608 -75
  94. package/dist/index.js +2287 -1706
  95. package/dist/inject.d.ts.map +1 -1
  96. package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
  97. package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
  98. package/dist/protocols/protocol-v2/features.d.ts +11 -4
  99. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  100. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  101. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  102. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +16 -0
  103. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
  104. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  105. package/dist/protocols/protocol-v2/walletSession.d.ts +8 -3
  106. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  107. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -2
  108. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  109. package/dist/types/api/deviceSettings.d.ts +1 -0
  110. package/dist/types/api/deviceSettings.d.ts.map +1 -1
  111. package/dist/types/api/deviceUnlock.d.ts +1 -1
  112. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  113. package/dist/types/api/getFeatures.d.ts.map +1 -1
  114. package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
  115. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  116. package/dist/types/api/index.d.ts +13 -15
  117. package/dist/types/api/index.d.ts.map +1 -1
  118. package/dist/types/api/openWalletSession.d.ts +41 -0
  119. package/dist/types/api/openWalletSession.d.ts.map +1 -0
  120. package/dist/types/api/protocolV2.d.ts +6 -11
  121. package/dist/types/api/protocolV2.d.ts.map +1 -1
  122. package/dist/types/device.d.ts +2 -9
  123. package/dist/types/device.d.ts.map +1 -1
  124. package/dist/utils/assets.d.ts +2 -1
  125. package/dist/utils/assets.d.ts.map +1 -1
  126. package/dist/utils/deviceFeaturesCompat.d.ts +13 -0
  127. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
  128. package/dist/utils/deviceFeaturesUtils.d.ts +11 -1
  129. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  130. package/dist/utils/deviceInfoUtils.d.ts +9 -8
  131. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  132. package/dist/utils/deviceSettings.d.ts +2 -0
  133. package/dist/utils/deviceSettings.d.ts.map +1 -1
  134. package/dist/utils/deviceVersionUtils.d.ts +6 -5
  135. package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
  136. package/dist/utils/index.d.ts +2 -2
  137. package/dist/utils/index.d.ts.map +1 -1
  138. package/dist/utils/networkUtils.d.ts +9 -1
  139. package/dist/utils/networkUtils.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/pro2Wallpaper.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/api/BaseMethod.ts +17 -10
  145. package/src/api/CheckBootloaderRelease.ts +1 -2
  146. package/src/api/CheckFirmwareRelease.ts +1 -2
  147. package/src/api/ClearSessionCache.ts +0 -4
  148. package/src/api/DirList.ts +5 -3
  149. package/src/api/DirMake.ts +3 -3
  150. package/src/api/DirRemove.ts +3 -3
  151. package/src/api/FileDelete.ts +3 -3
  152. package/src/api/FileRead.ts +9 -6
  153. package/src/api/FileWrite.ts +3 -3
  154. package/src/api/FirmwareUpdate.ts +4 -4
  155. package/src/api/FirmwareUpdateV2.ts +133 -45
  156. package/src/api/FirmwareUpdateV3.ts +3 -5
  157. package/src/api/FirmwareUpdateV4.ts +192 -154
  158. package/src/api/GetPassphraseState.ts +10 -16
  159. package/src/api/OpenWalletSession.ts +221 -0
  160. package/src/api/PathInfo.ts +5 -3
  161. package/src/api/PromptWebDeviceAccess.ts +2 -3
  162. package/src/api/SearchDevices.ts +6 -2
  163. package/src/api/UploadPortfolio.ts +12 -3
  164. package/src/api/alephium/AlephiumSignTransaction.ts +2 -2
  165. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +4 -8
  166. package/src/api/benfen/BenfenGetAddress.ts +1 -1
  167. package/src/api/btc/BTCGetPublicKey.ts +1 -1
  168. package/src/api/device/DeviceChangePin.ts +1 -0
  169. package/src/api/device/DeviceFullyUploadResource.ts +4 -5
  170. package/src/api/device/DeviceSettings.ts +93 -0
  171. package/src/api/device/DeviceUpdateBootloader.ts +1 -2
  172. package/src/api/device/DeviceWipe.ts +1 -0
  173. package/src/api/evm/EVMSignTypedData.ts +3 -5
  174. package/src/api/evm/legacyV1/getAddress.ts +2 -2
  175. package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
  176. package/src/api/evm/legacyV1/signMessage.ts +2 -2
  177. package/src/api/evm/legacyV1/signTypedData.ts +2 -2
  178. package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
  179. package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
  180. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +3 -3
  181. package/src/api/firmware/getBinary.ts +8 -3
  182. package/src/api/helpers/batchGetPublickeys.ts +2 -2
  183. package/src/api/helpers/filesystemValidation.ts +61 -0
  184. package/src/api/helpers/paramsValidator.ts +1 -1
  185. package/src/api/helpers/protocolV2FileWrite.ts +3 -2
  186. package/src/api/index.ts +9 -12
  187. package/src/api/nervos/NervosSignTransaction.ts +2 -2
  188. package/src/api/nexa/NexaSignTransaction.ts +1 -1
  189. package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +1 -1
  190. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  191. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +4 -4
  192. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +6 -5
  193. package/src/api/protocol-v2/DeviceInfoGet.ts +4 -4
  194. package/src/api/protocol-v2/DeviceReboot.ts +1 -1
  195. package/src/api/protocol-v2/DeviceSettingsPageShow.ts +11 -8
  196. package/src/api/protocol-v2/DeviceSettingsSet.ts +4 -11
  197. package/src/api/protocol-v2/FilesystemFormat.ts +1 -1
  198. package/src/api/protocol-v2/FilesystemPermissionFix.ts +1 -1
  199. package/src/api/protocol-v2/Ping.ts +1 -1
  200. package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -1
  201. package/src/api/utils.ts +1 -3
  202. package/src/core/RequestQueue.ts +107 -6
  203. package/src/core/index.ts +91 -46
  204. package/src/data/messages/messages-protocol-v2.json +25 -79
  205. package/src/data-manager/DataManager.ts +6 -6
  206. package/src/device/Device.ts +186 -102
  207. package/src/device/DeviceCommands.ts +51 -21
  208. package/src/device/DeviceFeaturesState.ts +4 -4
  209. package/src/device/DevicePool.ts +17 -3
  210. package/src/device/DeviceStateMapper.ts +13 -29
  211. package/src/device/DeviceStateProjector.ts +11 -8
  212. package/src/device/DeviceStateStore.ts +11 -52
  213. package/src/device/DeviceWalletSessionStore.ts +31 -4
  214. package/src/device/cloneDeviceState.ts +39 -0
  215. package/src/device/deviceIdentity.ts +2 -2
  216. package/src/deviceProfile/buildDeviceFeatures.ts +29 -88
  217. package/src/events/logBlockEvent.ts +8 -1
  218. package/src/inject.ts +14 -24
  219. package/src/protocols/protocol-v2/deviceSession.ts +24 -0
  220. package/src/protocols/protocol-v2/features.ts +37 -49
  221. package/src/protocols/protocol-v2/firmware.ts +1 -1
  222. package/src/protocols/protocol-v2/index.ts +2 -3
  223. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +61 -0
  224. package/src/protocols/protocol-v2/uiInteraction.ts +4 -3
  225. package/src/protocols/protocol-v2/walletSession.ts +57 -146
  226. package/src/types/api/checkAllFirmwareRelease.ts +1 -2
  227. package/src/types/api/deviceSettings.ts +1 -0
  228. package/src/types/api/deviceUnlock.ts +1 -1
  229. package/src/types/api/firmwareUpdate.ts +5 -5
  230. package/src/types/api/getDeviceState.ts +1 -1
  231. package/src/types/api/getFeatures.ts +3 -0
  232. package/src/types/api/getOnekeyFeatures.ts +3 -0
  233. package/src/types/api/getPassphraseState.ts +6 -0
  234. package/src/types/api/index.ts +16 -23
  235. package/src/types/api/openWalletSession.ts +64 -0
  236. package/src/types/api/protocolV2.ts +36 -18
  237. package/src/types/device.ts +20 -14
  238. package/src/types/params.ts +1 -1
  239. package/src/types/settings.ts +8 -8
  240. package/src/utils/assets.ts +4 -1
  241. package/src/utils/deviceFeaturesCompat.ts +173 -0
  242. package/src/utils/deviceFeaturesUtils.ts +9 -8
  243. package/src/utils/deviceInfoUtils.ts +26 -64
  244. package/src/utils/deviceSettings.ts +18 -11
  245. package/src/utils/deviceVersionUtils.ts +25 -44
  246. package/src/utils/findDefectiveBatchDevice.ts +3 -3
  247. package/src/utils/index.ts +2 -5
  248. package/src/utils/networkUtils.ts +270 -14
  249. package/src/utils/pro2Wallpaper.ts +11 -13
  250. package/src/utils/tracing.ts +2 -2
  251. package/dist/api/internal.d.ts +0 -4
  252. package/dist/api/internal.d.ts.map +0 -1
  253. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts +0 -28
  254. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts.map +0 -1
  255. package/src/api/internal.ts +0 -9
  256. package/src/api/protocol-v2/DeviceSessionOpen.ts +0 -100
@@ -22,11 +22,9 @@ import { DevicePool } from '../device/DevicePool';
22
22
  import {
23
23
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
24
24
  ProtocolV2FirmwareTargetType,
25
- isProtocolV2LoaderDeviceInfo,
26
25
  } from '../protocols/protocol-v2';
27
26
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
28
27
  import {
29
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
30
28
  getProtocolV2UnknownErrorText,
31
29
  isProtocolV2DeviceDisconnectedError,
32
30
  } from './protocol-v2/helpers';
@@ -46,6 +44,7 @@ const Log = getLogger(LoggerNames.Method);
46
44
 
47
45
  const SESSION_ERROR = 'session not found';
48
46
  const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
47
+ const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 2 * 60 * 1000;
49
48
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
50
49
  const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
51
50
  const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
@@ -95,10 +94,7 @@ type ProtocolV2FirmwareUpdateStatusTarget = {
95
94
  path?: string;
96
95
  };
97
96
 
98
- type ProtocolV2FirmwareUpdateStartResponse =
99
- | TypedResponseMessage<'Success'>
100
- | TypedResponseMessage<'DeviceFirmwareUpdateStatus'>
101
- | undefined;
97
+ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
102
98
 
103
99
  type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
104
100
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
@@ -115,12 +111,12 @@ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
115
111
  onTransferredBytes?: (transferredBytes: number) => void;
116
112
  };
117
113
 
118
- /** RESC bundle okpkg(FileWrite 直写模式),每个独立同步到 devicePath */
114
+ /** RESC bundle okpkg written independently to devicePath through FileWrite. */
119
115
  type ProtocolV2ResourceBundleBinary = {
120
116
  name: string;
121
117
  binary: ArrayBuffer;
122
118
  devicePath: string;
123
- /** 远端配置模式下的下载 URL(手动模式不填) */
119
+ /** Download URL for remote-config mode; omitted in manual mode. */
124
120
  url?: string;
125
121
  version?: IVersionArray;
126
122
  payloadHash?: string;
@@ -203,8 +199,8 @@ const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4T
203
199
  const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
204
200
  'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
205
201
 
206
- // hd-transport 的历史 decode 行为会把单值 enum 输出为枚举名字符串;
207
- // Protocol V2 沿用这个 SDK 语义,内部比较前再映射回固件协议数值。
202
+ // hd-transport historically decodes scalar enums as enum-name strings.
203
+ // Map them back to firmware protocol values before internal comparisons.
208
204
  const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
209
205
  Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
210
206
  );
@@ -230,13 +226,12 @@ const isProtocolV2ReconnectProbeError = (error: unknown) => {
230
226
  );
231
227
  };
232
228
 
233
- const isProtocolV2StartUpdateTransientError = (error: unknown) => {
229
+ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
234
230
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
235
231
  return (
236
- isProtocolV2DeviceDisconnectedError(error) ||
237
- isProtocolV2ReconnectProbeError(error) ||
238
- message.includes('libusb_transfer_timed_out') ||
239
- (message.includes('response timeout') && message.includes('devicefirmwareupdaterequest'))
232
+ message.includes('handler not registered') ||
233
+ message.includes('message handler not found') ||
234
+ message.includes('unsupported message')
240
235
  );
241
236
  };
242
237
 
@@ -403,6 +398,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
403
398
  init() {
404
399
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
405
400
  this.requireDeviceMode = [];
401
+ this.unlockPolicy = 'retry-on-locked';
406
402
  this.useDevicePassphraseState = false;
407
403
  this.skipForceUpdateCheck = true;
408
404
 
@@ -495,6 +491,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
495
491
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
496
492
  let bootloaderBinary: ArrayBuffer | null = null;
497
493
  let installItems: ProtocolV2InstallItem[] | undefined;
494
+ let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
498
495
  try {
499
496
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
500
497
  fwBinaryMap = this.collectExplicitTargetBinaries();
@@ -508,13 +505,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
508
505
  fwBinaryMap = remoteBinaries.fwBinaryMap;
509
506
  installItems = remoteBinaries.installItems;
510
507
  }
508
+ resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
509
+ if (resourceBundles?.length) {
510
+ resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
511
+ }
511
512
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
512
513
  } catch (err) {
513
514
  throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
514
515
  }
515
516
 
516
- const resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
517
-
518
517
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
519
518
  throw ERRORS.TypedError(
520
519
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
@@ -742,22 +741,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
742
741
  }
743
742
 
744
743
  // ============================================================
745
- // RESC bundle 直写增量同步(FileRead 比对 + FilesystemFileWrite 直写)
744
+ // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
746
745
  // ============================================================
747
746
 
748
747
  /**
749
- * 准备 RESC bundle 列表。
748
+ * Prepare the RESC bundle list.
750
749
  *
751
- * 两种模式(同 FirmwareUpdateV3 binary vs version 模式):
752
- * - 用户传了 resourceBundleFiles(binary 数组):直接用,不做版本比对。
753
- * - 用户没传:从远端 config.json release.resourceBundles 拉取,
754
- * 此时 syncProtocolV2ResourceBundles 会按需下载 + 比对设备已有 header 跳过。
750
+ * Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
751
+ * - resourceBundleFiles supplied: use binaries directly without version checks.
752
+ * - Otherwise load release.resourceBundles from remote config.json; the binaries
753
+ * are prefetched before reboot and synchronization skips matching device headers.
755
754
  */
756
755
  private prepareProtocolV2ResourceBundles(
757
756
  firmwareType: EFirmwareType,
758
757
  features: Features
759
758
  ): ProtocolV2ResourceBundleBinary[] | undefined {
760
- // 模式1:用户手动传入 binary,直接安装,不比对
759
+ // Manual binaries are installed directly without comparison.
761
760
  if (this.params.resourceBundleFiles?.length) {
762
761
  return this.params.resourceBundleFiles.map(file => ({
763
762
  name: file.devicePath.split('/').pop() ?? file.devicePath,
@@ -770,7 +769,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
770
769
  return undefined;
771
770
  }
772
771
 
773
- // 模式2:远端配置模式,后续按需下载 + 比对
772
+ // Remote-config bundles are downloaded and compared later.
774
773
  const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
775
774
  if (!release?.resourceBundles?.length) return undefined;
776
775
 
@@ -785,11 +784,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
785
784
  })) as ProtocolV2ResourceBundleBinary[];
786
785
  }
787
786
 
787
+ private async prefetchProtocolV2ResourceBundles(
788
+ bundles: ProtocolV2ResourceBundleBinary[]
789
+ ): Promise<ProtocolV2ResourceBundleBinary[]> {
790
+ const prefetched: ProtocolV2ResourceBundleBinary[] = [];
791
+ for (const bundle of bundles) {
792
+ let { binary } = bundle;
793
+ if (binary.byteLength === 0) {
794
+ if (!bundle.url) {
795
+ throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
796
+ }
797
+ Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
798
+ ({ binary } = await getSysResourceBinary(bundle.url));
799
+ }
800
+ if (binary.byteLength === 0) {
801
+ throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
802
+ }
803
+ prefetched.push({ ...bundle, binary });
804
+ }
805
+ return prefetched;
806
+ }
807
+
788
808
  /**
789
- * 同步 RESC bundles 到设备。
809
+ * Synchronize RESC bundles to the device.
790
810
  *
791
- * 手动传入模式(有 binary):直接 FileWrite 直写,不比对。
792
- * 远端配置模式(无 binary,有 url):按需下载 + FileRead 比对跳过已更新的。
811
+ * Manual mode writes supplied binaries directly.
812
+ * Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
793
813
  */
794
814
  private async syncProtocolV2ResourceBundles(
795
815
  bundles: ProtocolV2ResourceBundleBinary[],
@@ -798,20 +818,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
798
818
  const transferStartTime = Date.now();
799
819
  const transferTransport = this.getProtocolV2FirmwareTransferTransport();
800
820
 
801
- const isManualMode = bundles.every(b => b.binary.byteLength > 0);
821
+ const isRemoteMode = bundles.some(bundle => !!bundle.url);
802
822
  let bundlesToSync = bundles;
803
823
 
804
- // 远端配置模式:按需下载 + 比对跳过
805
- if (!isManualMode) {
824
+ // Remote binaries were prefetched before bootloader entry. Only device-local
825
+ // header comparison and transfer are allowed in this phase.
826
+ if (isRemoteMode) {
806
827
  const filtered: ProtocolV2ResourceBundleBinary[] = [];
807
828
  for (const bundle of bundles) {
808
- // 下载 binary
809
- if (bundle.binary.byteLength === 0 && bundle.url) {
810
- Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
811
- const { binary } = await getSysResourceBinary(bundle.url);
812
- bundle.binary = binary;
813
- }
814
- // 比对设备上已有 header
829
+ // Compare the existing device header.
815
830
  const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
816
831
  if (upToDate) {
817
832
  Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
@@ -827,7 +842,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
827
842
  return { processedSize: 0, totalSize: firmwareSize };
828
843
  }
829
844
 
830
- // FileWrite 直写到设备
845
+ // Write directly to the device through FileWrite.
831
846
  let totalSize = 0;
832
847
  for (const b of bundlesToSync) totalSize += b.binary.byteLength;
833
848
 
@@ -855,7 +870,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
855
870
  }
856
871
 
857
872
  /**
858
- * 比对设备上已有 okpkg OKPP header(仅远端配置模式使用)。
873
+ * Compare the existing okpkg OKPP header in remote-config mode.
859
874
  */
860
875
  private async isProtocolV2ResourceBundleUpToDate(
861
876
  bundle: ProtocolV2ResourceBundleBinary
@@ -894,8 +909,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
894
909
  }
895
910
 
896
911
  async enterProtocolV2BootloaderMode() {
897
- // romloader 本身就是升级链路的首段执行环境,能够接收更新请求并把待处理目标交给 bootloader
898
- // 它不接受 DeviceRebootType.Bootloader,因此这里必须直接复用当前连接。
912
+ // romloader is the first update environment and forwards targets to bootloader.
913
+ // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
899
914
  if (this.isProtocolV2BootloaderMode() || this.device.features?.mode === 'romloader') {
900
915
  Log.debug('Protocol V2 device is already in loader mode, skip reboot to bootloader');
901
916
  return false;
@@ -931,9 +946,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
931
946
  commands: this.device.getCommands(),
932
947
  timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
933
948
  });
934
- // 重启进入 bootloader 后,旧的普通模式 DeviceStatus 已失效。
935
- // 此阶段只以新的 DeviceInfo 为准,避免继续请求或复用 DeviceStatus。
936
- const features = this.device.updateProtocolV2Features(deviceInfo, null);
949
+ // This is a reconnect probe after a Bootloader reboot. Bootloader does not
950
+ // support DeviceStatusGet, so the generic runtime-mode probe is invalid here.
951
+ const features = this.device.updateProtocolV2Features(deviceInfo, null, 'bootloader');
937
952
  assertProtocolV2ReconnectIdentity(
938
953
  this.protocolV2ExpectedDeviceId,
939
954
  features.deviceId ?? undefined,
@@ -959,8 +974,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
959
974
  }
960
975
 
961
976
  /**
962
- * 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
963
- * 文件名仅用于 staging 路径展示,target_id 已显式给定。
977
+ * Collect explicit target binaries grouped by DeviceFirmwareTargetType.
978
+ * Filenames are display-only staging paths because target_id is explicit.
964
979
  */
965
980
  private collectExplicitTargetBinaries() {
966
981
  const entries: ProtocolV2TargetBinary[] = [];
@@ -1030,7 +1045,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1030
1045
  totalSize = resourceTransfer.totalSize;
1031
1046
  }
1032
1047
 
1033
- // 只有 RESC bundle、没有固件 target 时跳过 staging/install 阶段
1048
+ // Skip staging and installation when the update contains RESC bundles only.
1034
1049
  if (orderedInstallItems.length === 0) {
1035
1050
  Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1036
1051
  if (totalSize > 0) {
@@ -1100,8 +1115,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1100
1115
  path: item.path,
1101
1116
  }));
1102
1117
  Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
1103
- const startResponse = await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1104
- await this.waitForProtocolV2FirmwareUpdateComplete(allTargets, startResponse);
1118
+ await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
1119
+ await this.waitForProtocolV2FirmwareUpdateComplete(allTargets);
1105
1120
  }
1106
1121
 
1107
1122
  private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
@@ -1121,98 +1136,117 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1121
1136
  }
1122
1137
  }
1123
1138
 
1124
- private isProtocolV2NormalModeFeatures(features?: Features | null) {
1125
- return !!features && !features.bootloaderMode && features.mode !== 'bootloader';
1126
- }
1127
-
1128
- private async probeProtocolV2NormalMode() {
1129
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
1130
- const deviceInfo = await requestProtocolV2DeviceInfo({
1131
- commands: this.device.getCommands(),
1132
- timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1133
- });
1134
- if (isProtocolV2LoaderDeviceInfo(deviceInfo, null)) {
1135
- this.device.updateProtocolV2Features(deviceInfo, null);
1136
- return false;
1137
- }
1138
- const { message: deviceStatus } = await typedCall(
1139
- 'DeviceStatusGet',
1140
- 'DeviceStatus',
1141
- {},
1142
- { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1143
- );
1144
- const features = this.device.updateProtocolV2Features(deviceInfo, deviceStatus);
1145
- assertProtocolV2ReconnectIdentity(
1146
- this.protocolV2ExpectedDeviceId,
1147
- features.deviceId ?? undefined
1148
- );
1149
- if (this.isProtocolV2NormalModeFeatures(features)) {
1150
- Log.log('Protocol V2 firmware install finished; device is back in normal mode');
1151
- return true;
1152
- }
1153
- return false;
1154
- }
1155
-
1156
1139
  private assertProtocolV2TargetStatus(
1157
1140
  statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
1158
1141
  expectedTargetIds: Set<number>
1159
1142
  ) {
1143
+ Log.log(
1144
+ `[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
1145
+ );
1160
1146
  const failedTarget = statusTargets.find(
1161
1147
  target =>
1162
1148
  expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1163
1149
  isProtocolV2TargetStatusFailed(target.status)
1164
1150
  );
1165
1151
  if (failedTarget) {
1152
+ const failedTargetDetails = JSON.stringify({
1153
+ targetId: failedTarget.target_id,
1154
+ status: failedTarget.status,
1155
+ payloadVersion: failedTarget.payload_version,
1156
+ path: failedTarget.path,
1157
+ });
1158
+ Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
1166
1159
  throw ERRORS.TypedError(
1167
1160
  HardwareErrorCode.FirmwareError,
1168
- `Protocol V2 firmware target ${failedTarget.target_id} failed`
1161
+ `Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
1162
+ failedTarget.status ?? 'unknown'
1163
+ } payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
1164
+ failedTarget.path ?? 'unknown'
1165
+ }`
1169
1166
  );
1170
1167
  }
1171
1168
 
1172
- const completedTargets = statusTargets.filter(
1173
- target =>
1174
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1175
- isProtocolV2TargetStatusFinished(target.status)
1169
+ const matchingTargets = statusTargets.filter(target =>
1170
+ expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
1171
+ );
1172
+ const completedTargets = matchingTargets.filter(target =>
1173
+ isProtocolV2TargetStatusFinished(target.status)
1176
1174
  );
1177
1175
  if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
1176
+ this.postProgressMessage(100, 'installingFirmware');
1178
1177
  return true;
1179
1178
  }
1180
1179
 
1181
- const inProgressTarget = statusTargets.find(
1182
- target =>
1183
- expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
1180
+ if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
1181
+ const hasInProgressTarget = matchingTargets.some(target =>
1184
1182
  isProtocolV2TargetStatusInProgress(target.status)
1185
- );
1186
- if (inProgressTarget) {
1187
- this.postProgressMessage(99, 'installingFirmware');
1183
+ );
1184
+ const completedProgress = Math.floor(
1185
+ (completedTargets.length / expectedTargetIds.size) * 100
1186
+ );
1187
+ // The protocol exposes no per-target percentage, so report coarse progress by
1188
+ // completed targets and use 1% once work starts to keep the UI responsive.
1189
+ const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
1190
+ this.postProgressMessage(progress, 'installingFirmware');
1188
1191
  }
1189
1192
 
1190
1193
  return false;
1191
1194
  }
1192
1195
 
1193
1196
  private async waitForProtocolV2FirmwareUpdateComplete(
1194
- targets: Array<{ target_id: number; path: string }>,
1195
- startResponse?: ProtocolV2FirmwareUpdateStartResponse
1197
+ targets: Array<{ target_id: number; path: string }>
1196
1198
  ) {
1197
1199
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
1198
- if (startResponse?.type === 'DeviceFirmwareUpdateStatus') {
1199
- const statusTargets = (startResponse.message.records ??
1200
- []) as ProtocolV2FirmwareUpdateStatusTarget[];
1201
- // 仅消费安装请求自身返回的状态,用于尽早报告明确失败和安装中进度。
1202
- // 后续完成判定不再主动轮询 DeviceFirmwareUpdateStatusGet。
1203
- this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds);
1204
- }
1205
-
1206
1200
  const startTime = Date.now();
1207
1201
  let lastError: unknown;
1208
1202
 
1209
1203
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
1210
1204
  try {
1211
1205
  await this.reconnectProtocolV2Device();
1212
- if (await this.probeProtocolV2NormalMode()) {
1213
- return;
1206
+ try {
1207
+ const statusResponse = await this.device.getCommands().typedCall(
1208
+ 'DeviceFirmwareUpdateStatusGet',
1209
+ 'DeviceFirmwareUpdateStatus',
1210
+ {
1211
+ fields: {
1212
+ status: true,
1213
+ payload_version: true,
1214
+ path: true,
1215
+ },
1216
+ },
1217
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
1218
+ );
1219
+ if (
1220
+ this.assertProtocolV2TargetStatus(
1221
+ (statusResponse.message.records ?? []) as ProtocolV2FirmwareUpdateStatusTarget[],
1222
+ expectedTargetIds
1223
+ )
1224
+ ) {
1225
+ return;
1226
+ }
1227
+ lastError = new Error('Protocol V2 firmware targets are still installing');
1228
+ } catch (error) {
1229
+ if (
1230
+ error instanceof HardwareError &&
1231
+ error.errorCode === HardwareErrorCode.FirmwareError
1232
+ ) {
1233
+ throw error;
1234
+ }
1235
+ // App firmware does not register DeviceFirmwareUpdateStatusGet. If the
1236
+ // device already rebooted into App, this endpoint error signals that the
1237
+ // install phase ended; the later phase performs the final readiness check.
1238
+ if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
1239
+ Log.log(
1240
+ '[FirmwareUpdateV4] firmware status endpoint unavailable after reboot; continue with normal-mode verification'
1241
+ );
1242
+ return;
1243
+ }
1244
+ lastError = error;
1245
+ Log.log(
1246
+ '[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
1247
+ error
1248
+ );
1214
1249
  }
1215
- lastError = new Error('Protocol V2 device is reachable but is not in normal mode');
1216
1250
  } catch (error) {
1217
1251
  lastError = error;
1218
1252
  if (error instanceof HardwareError && error.errorCode === HardwareErrorCode.FirmwareError) {
@@ -1230,22 +1264,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1230
1264
  }
1231
1265
 
1232
1266
  private async exitProtocolV2BootloaderToNormal() {
1233
- this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
1234
- try {
1235
- await this.reconnectProtocolV2Device();
1236
- if (await this.probeProtocolV2NormalMode()) {
1237
- Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
1238
- return;
1239
- }
1240
- } catch (error) {
1241
- Log.log('Protocol V2 normal-mode probe before reboot failed: ', error);
1242
- }
1267
+ await this.reconnectProtocolV2Device();
1268
+ // The connection may still be in bootloader. Request a Normal reboot directly;
1269
+ // repeating it after an automatic App reboot is idempotent.
1243
1270
  await this.protocolV2Reboot(DeviceRebootType.Normal);
1244
1271
  }
1245
1272
 
1246
1273
  private async waitForProtocolV2FinalFeatures() {
1247
1274
  const features = await this.waitForProtocolV2ReconnectAndFeatures(
1248
- PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT
1275
+ PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1249
1276
  );
1250
1277
 
1251
1278
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
@@ -1274,15 +1301,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1274
1301
  const deviceInfo = await requestProtocolV2DeviceInfo({
1275
1302
  commands: this.device.getCommands(),
1276
1303
  timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
1277
- // 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
1304
+ // Completion needs target versions only; keep scope aligned with the request.
1278
1305
  request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
1279
1306
  });
1280
- const features = this.device.updateProtocolV2Features(deviceInfo);
1307
+ const features = await this.device.probeProtocolV2RuntimeState(
1308
+ deviceInfo,
1309
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
1310
+ );
1281
1311
  assertProtocolV2ReconnectIdentity(
1282
1312
  this.protocolV2ExpectedDeviceId,
1283
1313
  features.deviceId ?? undefined
1284
1314
  );
1285
- if (features.bootloaderMode || features.mode === 'bootloader') {
1315
+ if (features.mode !== 'normal' || features.bootloaderMode) {
1286
1316
  throw ERRORS.TypedError(
1287
1317
  HardwareErrorCode.DeviceNotFound,
1288
1318
  'Protocol V2 device is still in bootloader mode'
@@ -1290,6 +1320,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1290
1320
  }
1291
1321
  return features;
1292
1322
  } catch (error) {
1323
+ // A confirmed deviceId change is a firmware contract violation and cannot be
1324
+ // fixed by polling. A temporarily missing identity may still become ready.
1325
+ if (this.isProtocolV2ReconnectIdentityMismatch(error)) {
1326
+ throw error;
1327
+ }
1293
1328
  lastError = error;
1294
1329
  Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
1295
1330
  await wait(1000);
@@ -1324,19 +1359,21 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1324
1359
  },
1325
1360
  true
1326
1361
  );
1327
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
1362
+ await this.ensureProtocolV2DeviceAcquired();
1328
1363
  this.device.commands.disposed = false;
1329
1364
  this.device.getCommands().mainId = this.device.mainId ?? '';
1330
1365
  assertProtocolV2ReconnectIdentity(
1331
1366
  this.protocolV2ExpectedDeviceId,
1332
1367
  this.device.getCurrentDeviceId(),
1333
- { allowMissingActual: this.isProtocolV2BootloaderMode() }
1368
+ // Cached identity may be absent immediately after acquire. Reject only an
1369
+ // existing mismatch; the final refresh performs strict identity validation.
1370
+ { allowMissingActual: true }
1334
1371
  );
1335
1372
  return;
1336
1373
  }
1337
1374
 
1338
- // App bootloader 序列号暂时可能不一致。V4 升级重连阶段只接受唯一枚举设备,
1339
- // 避免继续按旧 app connectId 查缓存导致反复输出 path mismatch 日志。
1375
+ // App and bootloader serials may differ temporarily. During V4 reconnect, accept
1376
+ // only a uniquely enumerated device instead of repeatedly using the old App path.
1340
1377
  const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
1341
1378
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1342
1379
  });
@@ -1349,16 +1386,36 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1349
1386
  deviceList[0].getConnectId()
1350
1387
  );
1351
1388
  this.device.updateFromCache(deviceList[0]);
1352
- await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
1389
+ await this.ensureProtocolV2DeviceAcquired();
1353
1390
  this.device.commands.disposed = false;
1354
1391
  this.device.getCommands().mainId = this.device.mainId ?? '';
1355
1392
  assertProtocolV2ReconnectIdentity(
1356
1393
  this.protocolV2ExpectedDeviceId,
1357
1394
  this.device.getCurrentDeviceId(),
1358
- { allowMissingActual: this.isProtocolV2BootloaderMode() }
1395
+ // Cached identity may be absent immediately after acquire. Reject only an
1396
+ // existing mismatch; the final refresh performs strict identity validation.
1397
+ { allowMissingActual: true }
1359
1398
  );
1360
1399
  }
1361
1400
 
1401
+ /**
1402
+ * After acquiring a USB session, polling reuses its command channel. Reacquire only
1403
+ * after reboot or enumeration changes cause hasDeviceAcquire() to become false.
1404
+ */
1405
+ private async ensureProtocolV2DeviceAcquired() {
1406
+ const commands = this.device.getCommands();
1407
+ if (this.device.hasDeviceAcquire() && !commands.disposed) {
1408
+ return;
1409
+ }
1410
+ await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, {
1411
+ throwOnRunPromiseError: true,
1412
+ });
1413
+ }
1414
+
1415
+ private isProtocolV2ReconnectIdentityMismatch(error: unknown) {
1416
+ return this.normalizeErrorMessage(error).includes('Protocol V2 reconnect identity mismatch');
1417
+ }
1418
+
1362
1419
  private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
1363
1420
  let lastError: unknown;
1364
1421
  for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
@@ -1505,35 +1562,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1505
1562
  targets: Array<{ target_id: number; path: string }>;
1506
1563
  }) {
1507
1564
  const commands = this.device.getCommands();
1508
- let response: ProtocolV2FirmwareUpdateStartResponse;
1509
- try {
1510
- response = await commands.typedCall(
1511
- 'DeviceFirmwareUpdateRequest',
1512
- PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
1513
- {
1514
- targets,
1515
- },
1516
- {
1517
- intermediateTypes: ['DeviceFirmwareUpdateStatus'],
1518
- timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT,
1519
- onIntermediateResponse: (response: { type?: string }) => {
1520
- if (response.type === 'DeviceFirmwareUpdateStatus') {
1521
- this.postProgressMessage(99, 'installingFirmware');
1522
- }
1523
- },
1524
- }
1525
- );
1526
- } catch (error) {
1527
- if (isProtocolV2StartUpdateTransientError(error)) {
1528
- Log.log(
1529
- 'Protocol V2 firmware update request did not return; continue status polling',
1530
- error
1531
- );
1532
- } else {
1533
- throw error;
1534
- }
1535
- }
1565
+ const response: ProtocolV2FirmwareUpdateStartResponse = await commands.typedCall(
1566
+ 'DeviceFirmwareUpdateRequest',
1567
+ 'Success',
1568
+ { targets },
1569
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
1570
+ );
1571
+ // Success acknowledges that the device accepted installation. End confirmation
1572
+ // and begin status polling only after receiving this ACK.
1536
1573
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
1574
+ this.postProgressMessage(0, 'installingFirmware');
1537
1575
  return response;
1538
1576
  }
1539
1577
 
@@ -1,4 +1,4 @@
1
- import { EDeviceType } from '@onekeyfe/hd-shared';
1
+ import { ERRORS, HardwareErrorCode, createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { UI_REQUEST } from '../constants/ui-request';
4
4
  import { getPassphraseStateWithRefreshDeviceInfo } from '../utils/deviceFeaturesUtils';
@@ -11,23 +11,17 @@ export default class GetPassphraseState extends BaseMethod {
11
11
  }
12
12
 
13
13
  async run() {
14
- const { passphraseState } = await getPassphraseStateWithRefreshDeviceInfo(this.device, {
15
- expectPassphraseState: this.payload.passphraseState,
16
- onlyMainPin: this.payload.useEmptyPassphrase,
17
- initSession: this.payload.initSession,
18
- });
14
+ if (this.device.isProtocolV2()) {
15
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
16
+ }
17
+ if (!this.device.features) {
18
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed);
19
+ }
19
20
 
20
- const passphraseProtection = this.device.getCurrentPassphraseProtection();
21
- const deviceType = this.device.getCurrentDeviceType();
21
+ const { passphraseState } = await getPassphraseStateWithRefreshDeviceInfo(this.device);
22
22
 
23
- if (deviceType === EDeviceType.Pro2) {
24
- // Protocol V2 的钱包身份由 DeviceSession 返回的 btc_test_address 决定,
25
- // 不依赖固件 passphrase_enabled 状态。
26
- return Promise.resolve(passphraseState);
27
- }
23
+ const passphraseProtection = this.device.getCurrentPassphraseProtection();
28
24
 
29
- return Promise.resolve(
30
- deviceType === EDeviceType.Pro || passphraseProtection === true ? passphraseState : undefined
31
- );
25
+ return Promise.resolve(passphraseProtection === true ? passphraseState : undefined);
32
26
  }
33
27
  }