@onekeyfe/hd-core 1.2.0-alpha.67 → 1.2.0-alpha.69

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 (316) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
  2. package/__tests__/DeviceCommands.test.ts +59 -13
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +226 -3
  5. package/__tests__/connectSettings.test.ts +5 -47
  6. package/__tests__/core-initialization.test.ts +23 -0
  7. package/__tests__/device-connector-protocol.test.ts +81 -0
  8. package/__tests__/device-lifecycle-events.test.ts +105 -3
  9. package/__tests__/device-pool-state.test.ts +22 -0
  10. package/__tests__/device-settings.test.ts +39 -18
  11. package/__tests__/device-state-contract.test.ts +1 -0
  12. package/__tests__/device-state-mapper.test.ts +13 -1
  13. package/__tests__/device-utils.test.ts +48 -1
  14. package/__tests__/deviceSettings.test.ts +11 -1
  15. package/__tests__/deviceUploadNft.test.ts +316 -0
  16. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  17. package/__tests__/get-device-state.test.ts +132 -12
  18. package/__tests__/homescreen.test.ts +41 -1
  19. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  20. package/__tests__/kaspaSignTransaction.test.ts +3 -26
  21. package/__tests__/logBlockEvent.test.ts +1 -1
  22. package/__tests__/method-protocol-support.test.ts +19 -0
  23. package/__tests__/open-wallet-session.test.ts +568 -37
  24. package/__tests__/pro2Nft.test.ts +108 -0
  25. package/__tests__/protocol-binding.test.ts +89 -0
  26. package/__tests__/protocol-v2-firmware-targets.test.ts +33 -1
  27. package/__tests__/protocol-v2-resources.test.ts +343 -0
  28. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  29. package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
  30. package/__tests__/protocol-v2.test.ts +1586 -1213
  31. package/__tests__/protocolV2FileWrite.test.ts +193 -0
  32. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  33. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  34. package/__tests__/search-devices.test.ts +12 -3
  35. package/__tests__/tron-sign-message-init.test.ts +2 -2
  36. package/__tests__/uiProgressThrottle.test.ts +74 -0
  37. package/__tests__/uiPromiseRegistry.test.ts +115 -0
  38. package/dist/api/BaseMethod.d.ts +3 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts +3 -1
  41. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  42. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  43. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  44. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  45. package/dist/api/FirmwareUpdateV2.d.ts +0 -10
  46. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  47. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  48. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  49. package/dist/api/FirmwareUpdateV4.d.ts +15 -27
  50. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  51. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  52. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  53. package/dist/api/SearchDevices.d.ts.map +1 -1
  54. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  55. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  56. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  57. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  58. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  59. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/getBinary.d.ts +0 -7
  63. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  64. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  65. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  66. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  67. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  68. package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
  69. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  70. package/dist/api/index.d.ts +2 -0
  71. package/dist/api/index.d.ts.map +1 -1
  72. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  73. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  74. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  75. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  76. package/dist/api/polkadot/networks.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  78. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  79. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  81. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  82. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  83. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  84. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  85. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  86. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  87. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  88. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  89. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  90. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  91. package/dist/api/ton/TonSignProof.d.ts +1 -1
  92. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  93. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  94. package/dist/core/deviceEventRegistration.d.ts +2 -0
  95. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  96. package/dist/core/index.d.ts.map +1 -1
  97. package/dist/core/uiPromiseRegistry.d.ts +6 -0
  98. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  99. package/dist/data-manager/DataManager.d.ts +9 -4
  100. package/dist/data-manager/DataManager.d.ts.map +1 -1
  101. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  102. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  103. package/dist/device/Device.d.ts +29 -6
  104. package/dist/device/Device.d.ts.map +1 -1
  105. package/dist/device/DeviceCommands.d.ts +4 -4
  106. package/dist/device/DeviceCommands.d.ts.map +1 -1
  107. package/dist/device/DeviceConnector.d.ts +2 -1
  108. package/dist/device/DeviceConnector.d.ts.map +1 -1
  109. package/dist/device/DevicePool.d.ts.map +1 -1
  110. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  111. package/dist/events/device.d.ts +4 -0
  112. package/dist/events/device.d.ts.map +1 -1
  113. package/dist/events/logBlockEvent.d.ts.map +1 -1
  114. package/dist/events/ui-promise.d.ts +4 -2
  115. package/dist/events/ui-promise.d.ts.map +1 -1
  116. package/dist/events/ui-request.d.ts +31 -2
  117. package/dist/events/ui-request.d.ts.map +1 -1
  118. package/dist/events/ui-response.d.ts +10 -2
  119. package/dist/events/ui-response.d.ts.map +1 -1
  120. package/dist/index.d.ts +243 -247
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +3135 -3785
  123. package/dist/inject.d.ts +6 -1
  124. package/dist/inject.d.ts.map +1 -1
  125. package/dist/lowLevelInject.d.ts.map +1 -1
  126. package/dist/protocols/protocol-v2/features.d.ts +0 -4
  127. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  128. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  129. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  130. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  131. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  132. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  133. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  134. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  135. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  136. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  137. package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
  138. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  139. package/dist/topLevelInject.d.ts.map +1 -1
  140. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
  141. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  142. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  143. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  144. package/dist/types/api/deviceUnlock.d.ts +5 -1
  145. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  146. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  147. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  148. package/dist/types/api/export.d.ts +2 -4
  149. package/dist/types/api/export.d.ts.map +1 -1
  150. package/dist/types/api/firmwareUpdate.d.ts +4 -70
  151. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  152. package/dist/types/api/index.d.ts +7 -10
  153. package/dist/types/api/index.d.ts.map +1 -1
  154. package/dist/types/api/protocolV2.d.ts +3 -0
  155. package/dist/types/api/protocolV2.d.ts.map +1 -1
  156. package/dist/types/device.d.ts +2 -2
  157. package/dist/types/device.d.ts.map +1 -1
  158. package/dist/types/params.d.ts +1 -0
  159. package/dist/types/params.d.ts.map +1 -1
  160. package/dist/types/settings.d.ts +38 -21
  161. package/dist/types/settings.d.ts.map +1 -1
  162. package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
  163. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  164. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  165. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  166. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  167. package/dist/utils/deviceSettings.d.ts.map +1 -1
  168. package/dist/utils/homescreen.d.ts +4 -0
  169. package/dist/utils/homescreen.d.ts.map +1 -1
  170. package/dist/utils/index.d.ts +1 -1
  171. package/dist/utils/index.d.ts.map +1 -1
  172. package/dist/utils/patch.d.ts +1 -1
  173. package/dist/utils/patch.d.ts.map +1 -1
  174. package/dist/utils/pro2Nft.d.ts +31 -0
  175. package/dist/utils/pro2Nft.d.ts.map +1 -0
  176. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  177. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  178. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  179. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  180. package/package.json +4 -4
  181. package/src/api/BaseMethod.ts +8 -10
  182. package/src/api/CheckAllFirmwareRelease.ts +78 -64
  183. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  184. package/src/api/FirmwareUpdate.ts +7 -15
  185. package/src/api/FirmwareUpdateV2.ts +126 -298
  186. package/src/api/FirmwareUpdateV3.ts +63 -265
  187. package/src/api/FirmwareUpdateV4.ts +645 -1018
  188. package/src/api/GetPassphraseState.ts +7 -1
  189. package/src/api/OpenWalletSession.ts +71 -17
  190. package/src/api/SearchDevices.ts +3 -1
  191. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  192. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
  193. package/src/api/device/DeviceSettings.ts +5 -4
  194. package/src/api/device/DeviceUnlock.ts +8 -3
  195. package/src/api/device/DeviceUpdateBootloader.ts +6 -125
  196. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
  197. package/src/api/firmware/updateBootloader.ts +4 -19
  198. package/src/api/firmware/uploadFirmware.ts +39 -144
  199. package/src/api/helpers/protocolV2FileWrite.ts +252 -63
  200. package/src/api/index.ts +2 -0
  201. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  202. package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
  203. package/src/api/polkadot/networks.ts +3 -3
  204. package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
  205. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  206. package/src/api/protocol-v2/helpers.ts +1 -1
  207. package/src/api/solana/SolSignMessage.ts +1 -1
  208. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  209. package/src/api/solana/SolSignTransaction.ts +2 -2
  210. package/src/api/sui/SuiGetAddress.ts +1 -1
  211. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  212. package/src/api/sui/SuiSignMessage.ts +1 -1
  213. package/src/api/sui/SuiSignTransaction.ts +1 -1
  214. package/src/api/ton/TonGetAddress.ts +1 -1
  215. package/src/api/ton/TonSignMessage.ts +1 -1
  216. package/src/api/ton/TonSignProof.ts +1 -1
  217. package/src/api/tron/TronSignMessage.ts +2 -2
  218. package/src/api/tron/TronSignTransaction.ts +1 -1
  219. package/src/core/deviceEventRegistration.ts +4 -0
  220. package/src/core/index.ts +213 -111
  221. package/src/core/uiPromiseRegistry.ts +63 -0
  222. package/src/data/messages/messages-protocol-v2.json +25 -0
  223. package/src/data-manager/DataManager.ts +113 -41
  224. package/src/data-manager/TransportManager.ts +6 -0
  225. package/src/data-manager/connectSettings.ts +0 -11
  226. package/src/device/Device.ts +274 -49
  227. package/src/device/DeviceCommands.ts +31 -4
  228. package/src/device/DeviceConnector.ts +33 -13
  229. package/src/device/DevicePool.ts +21 -7
  230. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  231. package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
  232. package/src/events/device.ts +4 -0
  233. package/src/events/logBlockEvent.ts +1 -0
  234. package/src/events/ui-promise.ts +4 -2
  235. package/src/events/ui-request.ts +36 -2
  236. package/src/events/ui-response.ts +12 -2
  237. package/src/index.ts +0 -5
  238. package/src/inject.ts +60 -24
  239. package/src/lowLevelInject.ts +7 -8
  240. package/src/protocols/protocol-v2/features.ts +10 -7
  241. package/src/protocols/protocol-v2/firmware.ts +2 -0
  242. package/src/protocols/protocol-v2/resources.ts +394 -0
  243. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  244. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  245. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
  246. package/src/protocols/protocol-v2/walletSession.ts +214 -36
  247. package/src/topLevelInject.ts +7 -8
  248. package/src/types/api/checkAllFirmwareRelease.ts +3 -5
  249. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  250. package/src/types/api/deviceUnlock.ts +12 -1
  251. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  252. package/src/types/api/export.ts +1 -18
  253. package/src/types/api/firmwareUpdate.ts +6 -78
  254. package/src/types/api/index.ts +13 -14
  255. package/src/types/api/protocolV2.ts +13 -0
  256. package/src/types/device.ts +15 -3
  257. package/src/types/params.ts +7 -1
  258. package/src/types/settings.ts +61 -22
  259. package/src/utils/deviceFeaturesCompat.ts +5 -6
  260. package/src/utils/deviceFeaturesUtils.ts +14 -3
  261. package/src/utils/deviceInfoUtils.ts +2 -0
  262. package/src/utils/deviceSettings.ts +3 -0
  263. package/src/utils/homescreen.ts +32 -6
  264. package/src/utils/index.ts +6 -1
  265. package/src/utils/pro2Nft.ts +142 -0
  266. package/src/utils/pro2Wallpaper.ts +35 -8
  267. package/src/utils/uiProgressThrottle.ts +63 -0
  268. package/__tests__/device-initialize-timeout.test.ts +0 -56
  269. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  270. package/__tests__/firmware-update/device-update-bootloader.test.ts +0 -87
  271. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  272. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  273. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  274. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  275. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  276. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
  277. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  278. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -231
  279. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  280. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  281. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  282. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  283. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  284. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  285. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  286. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  287. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  288. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  289. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  290. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  291. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
  292. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  293. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  294. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  295. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  296. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  297. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  298. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  299. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  300. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  301. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  302. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  303. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  304. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  305. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  306. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  307. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  308. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  309. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
  310. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
  311. package/src/api/firmware/progressThrottle.ts +0 -44
  312. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  313. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  314. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  315. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  316. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -5,6 +5,7 @@ import CheckAllFirmwareRelease, {
5
5
  } from '../src/api/CheckAllFirmwareRelease';
6
6
  import { DataManager } from '../src/data-manager';
7
7
  import { createCoreApi } from '../src/inject';
8
+ import { PROTOCOL_V2_RESOURCE_DEVICE_PATHS } from '../src/protocols/protocol-v2/resources';
8
9
 
9
10
  import type { CoreApi } from '../src/types/api';
10
11
  import type { DeviceStateVersions, IFirmwareReleaseInfo } from '../src/types';
@@ -72,7 +73,74 @@ const release: IFirmwareReleaseInfo = {
72
73
  ],
73
74
  };
74
75
 
76
+ const stableResources = ['images', 'animation', 'wallpaper', 'translations', 'roobert', 'noto'].map(
77
+ (type, index) => ({
78
+ type,
79
+ url: `https://example.com/${type}.okpkg`,
80
+ size: 0x52a0 + index + 1,
81
+ fileHash: 'a'.repeat(64),
82
+ headerHash: index.toString(16).padStart(128, '0'),
83
+ })
84
+ );
85
+
86
+ const createFilesystemTypedCall = (missingAll = false) => {
87
+ const resourceByPath = new Map(
88
+ stableResources.map(resource => [
89
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS[
90
+ resource.type as keyof typeof PROTOCOL_V2_RESOURCE_DEVICE_PATHS
91
+ ],
92
+ resource,
93
+ ])
94
+ );
95
+ return jest.fn((requestType: string, _responseType: string, payload: Record<string, any>) => {
96
+ if (requestType === 'ResourceInventoryGet') {
97
+ throw new Error('ResourceInventoryGet is unavailable on released firmware');
98
+ }
99
+ if (requestType === 'FilesystemPathInfoQuery') {
100
+ const resource = resourceByPath.get(payload.path);
101
+ return {
102
+ message: {
103
+ exist: Boolean(resource) && !missingAll,
104
+ directory: false,
105
+ size: missingAll ? 0 : resource?.size,
106
+ },
107
+ };
108
+ }
109
+ if (requestType === 'FilesystemFileRead') {
110
+ const resource = resourceByPath.get(payload.file.path);
111
+ if (!resource || missingAll) throw new Error('missing resource');
112
+ const header = new Uint8Array(0x52a0);
113
+ const view = new DataView(header.buffer);
114
+ 'OKPP'.split('').forEach((char, index) => {
115
+ header[index] = char.charCodeAt(0);
116
+ });
117
+ 'RESC'.split('').forEach((char, index) => {
118
+ header[0x08 + index] = char.charCodeAt(0);
119
+ });
120
+ view.setUint32(0x0c, header.byteLength, true);
121
+ for (let index = 0; index < resource.headerHash.length / 2; index++) {
122
+ header[0x240 + index] = Number.parseInt(
123
+ resource.headerHash.slice(index * 2, index * 2 + 2),
124
+ 16
125
+ );
126
+ }
127
+ const offset = Number(payload.file.offset);
128
+ const chunkLength = Number(payload.chunk_len);
129
+ return {
130
+ message: {
131
+ data: header.slice(offset, offset + chunkLength),
132
+ },
133
+ };
134
+ }
135
+ throw new Error(`Unexpected request: ${requestType}`);
136
+ });
137
+ };
138
+
75
139
  describe('checkAllFirmwareRelease Protocol V2 support', () => {
140
+ afterEach(() => {
141
+ jest.restoreAllMocks();
142
+ });
143
+
76
144
  test('builds ordered firmwareUpdateV4 targets from recommended component versions', () => {
77
145
  const result = buildProtocolV2FirmwareRelease({
78
146
  currentVersions,
@@ -84,7 +152,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
84
152
  status: 'required',
85
153
  hasUpgrade: true,
86
154
  required: true,
87
- targetsToUpdate: ['boot', 'app_v1', 'resource'],
155
+ targetsToUpdate: ['boot', 'app_v1'],
88
156
  components: [
89
157
  { configKey: 'bootloader', status: 'outdated', updateTarget: 'boot' },
90
158
  { configKey: 'applicationP1', status: 'outdated', updateTarget: 'app_v1' },
@@ -95,6 +163,34 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
95
163
  });
96
164
  });
97
165
 
166
+ test('marks SE03 and SE04 as unsupported in a Neo firmware plan', () => {
167
+ const neoRelease: IFirmwareReleaseInfo = {
168
+ ...release,
169
+ installOrder: ['se01', 'se02', 'se03', 'se04'],
170
+ components: {
171
+ se01: { target: 'SE01', url: 'https://example.com/se01.bin', version: [2, 0, 0] },
172
+ se02: { target: 'SE02', url: 'https://example.com/se02.bin', version: [3, 0, 0] },
173
+ se03: { target: 'SE03', url: 'https://example.com/se03.bin', version: [2, 0, 0] },
174
+ se04: { target: 'SE04', url: 'https://example.com/se04.bin', version: [2, 0, 0] },
175
+ },
176
+ };
177
+
178
+ const result = buildProtocolV2FirmwareRelease({
179
+ currentVersions,
180
+ firmwareType: EFirmwareType.Universal,
181
+ release: neoRelease,
182
+ deviceType: 'neo',
183
+ });
184
+
185
+ expect(result.targetsToUpdate).toEqual(['se01', 'se02']);
186
+ expect(result.components).toEqual(
187
+ expect.arrayContaining([
188
+ expect.objectContaining({ configKey: 'se03', status: 'unsupported', updateTarget: null }),
189
+ expect.objectContaining({ configKey: 'se04', status: 'unsupported', updateTarget: null }),
190
+ ])
191
+ );
192
+ });
193
+
98
194
  test('returns an unavailable plan when no Pro2 release is configured', () => {
99
195
  expect(
100
196
  buildProtocolV2FirmwareRelease({
@@ -142,6 +238,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
142
238
  applicationP1Hash: 'aa'.repeat(32),
143
239
  applicationP2Hash: 'bb'.repeat(32),
144
240
  },
241
+ checkFirmwareHash: true,
145
242
  remotePayloadHashes: {
146
243
  applicationP1: 'aa'.repeat(64),
147
244
  applicationP2: 'cc'.repeat(64),
@@ -160,6 +257,37 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
160
257
  });
161
258
  });
162
259
 
260
+ test('uses version-only comparison for same-version packages by default', () => {
261
+ const packageSet: IFirmwareReleaseInfo = {
262
+ ...release,
263
+ required: false,
264
+ resourceBundles: undefined,
265
+ installOrder: ['applicationP1'],
266
+ components: {
267
+ applicationP1: {
268
+ target: 'APPLICATION_P1',
269
+ version: [1, 0, 0],
270
+ url: 'https://example.com/application-p1.okpkg',
271
+ payloadHash: 'cc'.repeat(64),
272
+ },
273
+ },
274
+ };
275
+
276
+ expect(
277
+ buildProtocolV2FirmwareRelease({
278
+ currentVersions: { ...currentVersions, applicationP1: '1.0.0' },
279
+ currentVerification: { applicationP1Hash: 'aa'.repeat(32) },
280
+ firmwareType: EFirmwareType.Universal,
281
+ release: packageSet,
282
+ })
283
+ ).toMatchObject({
284
+ status: 'valid',
285
+ hasUpgrade: false,
286
+ targetsToUpdate: [],
287
+ components: [{ configKey: 'applicationP1', status: 'valid' }],
288
+ });
289
+ });
290
+
163
291
  test('keeps same-version hash-aware components unknown without metadata payloadHash', () => {
164
292
  const packageSet: IFirmwareReleaseInfo = {
165
293
  ...release,
@@ -180,6 +308,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
180
308
  buildProtocolV2FirmwareRelease({
181
309
  currentVersions: { ...currentVersions, applicationP1: '1.0.0' },
182
310
  currentVerification: { applicationP1Hash: 'aa'.repeat(32) },
311
+ checkFirmwareHash: true,
183
312
  firmwareType: EFirmwareType.Universal,
184
313
  release: packageSet,
185
314
  })
@@ -215,6 +344,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
215
344
  const result = buildProtocolV2FirmwareRelease({
216
345
  currentVersions,
217
346
  currentVerification: { applicationP1Hash: 'aa'.repeat(32) },
347
+ checkFirmwareHash: true,
218
348
  remotePayloadHashes: { applicationP1: 'cc'.repeat(64) },
219
349
  firmwareType: EFirmwareType.Universal,
220
350
  release: packageSet,
@@ -223,7 +353,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
223
353
  expect(result.targetsToUpdate).toEqual(['app_v1', 'app_v2']);
224
354
  });
225
355
 
226
- test('uses the existing method on Protocol V2 and keeps its public API name', async () => {
356
+ test('does not read vol0 resource inventory while Protocol V2 is in Application mode', async () => {
227
357
  const method = new CheckAllFirmwareRelease({
228
358
  id: 1,
229
359
  payload: {
@@ -240,6 +370,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
240
370
  status: { mode: 'normal' },
241
371
  versions: currentVersions,
242
372
  });
373
+ const typedCall = createFilesystemTypedCall(true);
243
374
  method.device = {
244
375
  isProtocolV2: () => true,
245
376
  features: {
@@ -247,32 +378,124 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
247
378
  firmwareVersion: '1.0.0',
248
379
  },
249
380
  getDeviceState,
381
+ getCommands: () => ({ typedCall }),
250
382
  } as unknown as CheckAllFirmwareRelease['device'];
251
383
  jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
384
+ jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
252
385
 
253
386
  await expect(method.run()).resolves.toMatchObject({
254
387
  protocol: 'V2',
255
388
  deviceType: 'pro2',
256
389
  status: 'required',
257
- targetsToUpdate: ['boot', 'app_v1', 'resource'],
390
+ resourceStatus: 'unknown',
391
+ targetsToUpdate: ['boot', 'app_v1'],
258
392
  firmware: {
259
393
  status: 'required',
260
394
  },
261
395
  });
396
+ expect(typedCall).not.toHaveBeenCalled();
397
+ expect(getDeviceState).toHaveBeenCalledWith({
398
+ refreshSections: ['identity', 'versions'],
399
+ });
262
400
  expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
263
401
  });
264
402
 
403
+ test('requests and compares firmware hashes only when explicitly enabled', async () => {
404
+ const packageSet: IFirmwareReleaseInfo = {
405
+ ...release,
406
+ required: false,
407
+ resourceBundles: undefined,
408
+ installOrder: ['applicationP1'],
409
+ components: {
410
+ applicationP1: {
411
+ target: 'APPLICATION_P1',
412
+ version: [1, 0, 0],
413
+ url: 'https://example.com/application-p1.okpkg',
414
+ payloadHash: 'cc'.repeat(64),
415
+ },
416
+ },
417
+ };
418
+ const method = new CheckAllFirmwareRelease({
419
+ id: 1,
420
+ payload: {
421
+ method: 'checkAllFirmwareRelease',
422
+ firmwareType: EFirmwareType.Universal,
423
+ checkFirmwareHash: true,
424
+ },
425
+ });
426
+ method.init();
427
+ const getDeviceState = jest.fn().mockResolvedValue({
428
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
429
+ status: { mode: 'normal' },
430
+ versions: { ...currentVersions, applicationP1: '1.0.0' },
431
+ verification: { applicationP1Hash: 'aa'.repeat(32) },
432
+ });
433
+ method.device = {
434
+ isProtocolV2: () => true,
435
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
436
+ getDeviceState,
437
+ getCommands: () => ({ typedCall: jest.fn() }),
438
+ } as unknown as CheckAllFirmwareRelease['device'];
439
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(packageSet);
440
+ jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(undefined);
441
+
442
+ await expect(method.run()).resolves.toMatchObject({
443
+ status: 'outdated',
444
+ targetsToUpdate: ['app_v1'],
445
+ });
446
+ expect(getDeviceState).toHaveBeenCalledWith({
447
+ refreshSections: ['identity', 'versions', 'verification'],
448
+ });
449
+ });
450
+
451
+ test.each(['bootloader', 'romloader'] as const)(
452
+ 'uses filesystem inventory in %s mode instead of forcing all resources',
453
+ async mode => {
454
+ const method = new CheckAllFirmwareRelease({
455
+ id: 1,
456
+ payload: {
457
+ method: 'checkAllFirmwareRelease',
458
+ firmwareType: EFirmwareType.Universal,
459
+ },
460
+ });
461
+ method.init();
462
+ const typedCall = createFilesystemTypedCall();
463
+ method.device = {
464
+ isProtocolV2: () => true,
465
+ features: { deviceType: 'pro2', firmwareVersion: '1.0.0' },
466
+ getDeviceState: jest.fn().mockResolvedValue({
467
+ identity: { deviceType: 'pro2', firmwareType: EFirmwareType.Universal },
468
+ status: { mode },
469
+ versions: currentVersions,
470
+ }),
471
+ getCommands: () => ({ typedCall }),
472
+ } as unknown as CheckAllFirmwareRelease['device'];
473
+ jest.spyOn(DataManager, 'getFirmwareLatestRelease').mockReturnValue(release);
474
+ jest.spyOn(DataManager, 'getProtocolV2Resources').mockReturnValue(stableResources as any);
475
+
476
+ await expect(method.run()).resolves.toMatchObject({
477
+ protocol: 'V2',
478
+ resourceStatus: 'valid',
479
+ targetsToUpdate: ['boot', 'app_v1'],
480
+ });
481
+ expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
482
+ expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
483
+ }
484
+ );
485
+
265
486
  test('continues forwarding the existing public method', async () => {
266
487
  const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
267
488
  const api = createCoreApi(call as CoreApi['call']) as CoreApi;
268
489
 
269
490
  await api.checkAllFirmwareRelease('pro2-connect-id', {
491
+ checkFirmwareHash: true,
270
492
  firmwareType: EFirmwareType.Universal,
271
493
  retryCount: 0,
272
494
  });
273
495
 
274
496
  expect(call).toHaveBeenCalledWith({
275
497
  connectId: 'pro2-connect-id',
498
+ checkFirmwareHash: true,
276
499
  firmwareType: EFirmwareType.Universal,
277
500
  retryCount: 0,
278
501
  method: 'checkAllFirmwareRelease',
@@ -1,61 +1,19 @@
1
- import DataManager from '../src/data-manager/DataManager';
2
1
  import { parseConnectSettings } from '../src/data-manager/connectSettings';
3
2
 
4
- import type { RemoteConfigResponse } from '../src/types';
5
-
6
- jest.mock('axios', () => ({
7
- get: jest.fn(),
8
- }));
9
-
10
3
  jest.mock('../src/data/config', () => ({
11
4
  getSDKVersion: jest.fn(() => '1.0.0'),
12
5
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
13
6
  }));
14
7
 
15
- const mockAxiosGet: jest.Mock = jest.requireMock('axios').get;
16
-
17
8
  describe('parseConnectSettings', () => {
18
- const preloadedConfig = {
19
- bridge: {},
20
- classic: { firmware: [], ble: [] },
21
- classic1s: { firmware: [], ble: [] },
22
- classicpure: { firmware: [], ble: [] },
23
- mini: { firmware: [], ble: [] },
24
- touch: { firmware: [], ble: [] },
25
- pro: { firmware: [], ble: [] },
26
- pro2: { firmware: [], ble: [] },
27
- } as unknown as RemoteConfigResponse;
28
-
29
- it('keeps a serializable preloaded config in parsed settings', () => {
30
- const settings = parseConnectSettings({
31
- firmwareManifestMode: 'sdk-managed',
32
- preloadedConfig,
33
- });
34
-
35
- expect(settings.firmwareManifestMode).toBe('sdk-managed');
36
- expect(settings.preloadedConfig).toBe(preloadedConfig);
37
- });
38
-
39
- it('uses the preloaded config before the SDK network client', async () => {
40
- const settings = parseConnectSettings({
41
- firmwareManifestMode: 'sdk-managed',
42
- preloadedConfig,
43
- });
44
-
45
- await DataManager.load(settings);
46
-
47
- expect(mockAxiosGet).not.toHaveBeenCalled();
48
- expect(DataManager.deviceMap.classic).toEqual({ firmware: [], ble: [] });
49
- });
9
+ it('keeps custom configFetcher in parsed settings', () => {
10
+ const configFetcher = jest.fn();
50
11
 
51
- it('fails closed without a preloaded config in external-only mode', async () => {
52
12
  const settings = parseConnectSettings({
53
- firmwareManifestMode: 'external-only',
13
+ fetchConfig: true,
14
+ configFetcher,
54
15
  });
55
16
 
56
- await expect(DataManager.load(settings)).rejects.toThrow(
57
- 'External firmware config snapshot is required'
58
- );
59
- expect(mockAxiosGet).not.toHaveBeenCalled();
17
+ expect(settings.configFetcher).toBe(configFetcher);
60
18
  });
61
19
  });
@@ -0,0 +1,23 @@
1
+ import { DataManager } from '../src/data-manager';
2
+ import { init } from '../src/core';
3
+
4
+ import type { ConnectSettings } from '../src/types';
5
+
6
+ jest.mock('../src/data/config', () => ({
7
+ getSDKVersion: jest.fn(() => '1.0.0-test'),
8
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
9
+ }));
10
+
11
+ describe('Core initialization', () => {
12
+ afterEach(() => {
13
+ jest.restoreAllMocks();
14
+ });
15
+
16
+ it('does not create a partially initialized core when settings loading fails', async () => {
17
+ jest.spyOn(DataManager, 'load').mockRejectedValue(new Error('settings load failed'));
18
+
19
+ await expect(init({} as ConnectSettings, class TestTransport {})).rejects.toThrow(
20
+ 'settings load failed'
21
+ );
22
+ });
23
+ });
@@ -0,0 +1,81 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+
3
+ import DeviceConnector from '../src/device/DeviceConnector';
4
+
5
+ jest.mock('../src/data-manager/TransportManager', () => ({
6
+ __esModule: true,
7
+ default: {
8
+ load: jest.fn(),
9
+ getTransport: jest.fn(),
10
+ },
11
+ }));
12
+
13
+ jest.mock('../src/data-manager', () => ({
14
+ DataManager: {
15
+ getSettings: jest.fn(() => 'desktop-web-ble'),
16
+ isBleConnect: jest.fn(() => true),
17
+ },
18
+ }));
19
+
20
+ jest.mock('../src/device/DevicePool', () => ({
21
+ DevicePool: {
22
+ setConnector: jest.fn(),
23
+ },
24
+ }));
25
+
26
+ const transportManagerMock: {
27
+ default: {
28
+ getTransport: jest.Mock;
29
+ };
30
+ } = jest.requireMock('../src/data-manager/TransportManager');
31
+
32
+ describe('DeviceConnector protocol validation', () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+
37
+ it('reports an explicit transport error when connector initialization ran too early', async () => {
38
+ transportManagerMock.default.getTransport.mockReturnValue(undefined);
39
+ const connector = new DeviceConnector();
40
+
41
+ await expect(connector.enumerate()).rejects.toMatchObject({
42
+ errorCode: HardwareErrorCode.TransportNotConfigured,
43
+ });
44
+ });
45
+
46
+ it('uses the actively probed protocol returned by acquire when the cache was cleared', async () => {
47
+ const acquire = jest.fn().mockResolvedValue({
48
+ id: 'pro2-id',
49
+ path: 'pro2-id',
50
+ protocolType: 'V2',
51
+ });
52
+ transportManagerMock.default.getTransport.mockReturnValue({
53
+ acquire,
54
+ getProtocolType: jest.fn(() => undefined),
55
+ });
56
+ const connector = new DeviceConnector();
57
+
58
+ await expect(connector.acquire('pro2-id', undefined, undefined, 'V2')).resolves.toEqual(
59
+ expect.objectContaining({ protocolType: 'V2' })
60
+ );
61
+ expect(acquire).toHaveBeenCalledWith(
62
+ expect.objectContaining({ uuid: 'pro2-id', expectedProtocol: 'V2' })
63
+ );
64
+ });
65
+
66
+ it('still rejects a real mismatch reported by the active protocol probe', async () => {
67
+ transportManagerMock.default.getTransport.mockReturnValue({
68
+ acquire: jest.fn().mockResolvedValue({
69
+ id: 'classic-id',
70
+ path: 'classic-id',
71
+ protocolType: 'V1',
72
+ }),
73
+ getProtocolType: jest.fn(() => 'V1'),
74
+ });
75
+ const connector = new DeviceConnector();
76
+
77
+ await expect(connector.acquire('classic-id', undefined, undefined, 'V2')).rejects.toThrow(
78
+ 'Device protocol mismatch: expected V2, detected V1'
79
+ );
80
+ });
81
+ });
@@ -1,3 +1,4 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
1
2
  import { TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { initConnector, initCore } from '../src/core';
@@ -95,7 +96,7 @@ describe('public device lifecycle events', () => {
95
96
  expect(messages.filter(message => message.type === DEVICE.DISCONNECT)).toHaveLength(1);
96
97
  });
97
98
 
98
- test('uses a cached protocol as a fallback-capable acquire hint', async () => {
99
+ test('uses a verified cached protocol as a strict expected protocol', async () => {
99
100
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
100
101
  const device = Device.fromDescriptor({
101
102
  id: 'ble-id',
@@ -103,16 +104,117 @@ describe('public device lifecycle events', () => {
103
104
  commType: 'ble',
104
105
  protocolType: 'V1',
105
106
  } as never);
106
- const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
107
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V1' });
107
108
  device.deviceConnector = { acquire } as never;
108
109
 
109
110
  await device.acquire();
110
111
 
111
- expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, 'V1');
112
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined);
113
+ expect(device.getProtocol()).toBe('V1');
114
+ expect(device.originalDescriptor.protocolType).toBe('V1');
115
+ });
116
+
117
+ test('bypasses a cached protocol only for explicit active detection', async () => {
118
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
119
+ const device = Device.fromDescriptor({
120
+ id: 'ble-id',
121
+ path: 'ble-id',
122
+ commType: 'ble',
123
+ protocolType: 'V1',
124
+ } as never);
125
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
126
+ device.deviceConnector = { acquire } as never;
127
+
128
+ await device.acquire(undefined, { forceProtocolDetection: true });
129
+
130
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, undefined);
112
131
  expect(device.getProtocol()).toBe('V2');
113
132
  expect(device.originalDescriptor.protocolType).toBe('V2');
114
133
  });
115
134
 
135
+ test('does not reuse a stale protocol when active detection returns no protocol', async () => {
136
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
137
+ const device = Device.fromDescriptor({
138
+ id: 'ble-id',
139
+ path: 'ble-id',
140
+ commType: 'ble',
141
+ protocolType: 'V1',
142
+ } as never);
143
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id' });
144
+ const release = jest.fn().mockResolvedValue(undefined);
145
+ device.deviceConnector = { acquire, release } as never;
146
+ await expect(
147
+ device.acquire(undefined, {
148
+ forceProtocolDetection: true,
149
+ throwOnRunPromiseError: true,
150
+ })
151
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.RuntimeError });
152
+ expect(device.originalDescriptor.protocolType).toBe('V1');
153
+ expect(device.hasDeviceAcquire()).toBe(false);
154
+ expect(release).toHaveBeenCalledWith('ble-id', false);
155
+ });
156
+
157
+ test('actively reacquires an already connected device without initializing it', async () => {
158
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
159
+ const device = Device.fromDescriptor({
160
+ id: 'usb-device',
161
+ path: 'usb-device',
162
+ session: 'old-session',
163
+ protocolType: 'V1',
164
+ } as never);
165
+ device.mainId = 'old-session';
166
+ device.commands = { disposed: false } as never;
167
+ const release = jest.spyOn(device, 'release').mockImplementation(() => {
168
+ device.mainId = null;
169
+ device.originalDescriptor.session = null;
170
+ device.commands.disposed = true;
171
+ return Promise.resolve();
172
+ });
173
+ const acquire = jest.spyOn(device, 'acquire').mockImplementation(() => {
174
+ device.mainId = 'new-session';
175
+ device.originalDescriptor.session = 'new-session';
176
+ device.originalDescriptor.protocolType = 'V2';
177
+ device.commands.disposed = false;
178
+ return Promise.resolve();
179
+ });
180
+ const initialize = jest.spyOn(device, 'initialize').mockResolvedValue(undefined);
181
+
182
+ await device.run(() => Promise.resolve(undefined), {
183
+ forceProtocolDetection: true,
184
+ skipInitialize: true,
185
+ });
186
+
187
+ expect(release).toHaveBeenCalled();
188
+ expect(acquire).toHaveBeenCalledWith(undefined, {
189
+ forceProtocolDetection: true,
190
+ });
191
+ expect(initialize).not.toHaveBeenCalled();
192
+ expect(device.getProtocol()).toBe('V2');
193
+ });
194
+
195
+ test('keeps the freshly probed React Native BLE session until the call completes', async () => {
196
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
197
+ const device = Device.fromDescriptor({
198
+ id: 'ble-device',
199
+ path: 'ble-device',
200
+ commType: 'ble',
201
+ protocolType: 'V1',
202
+ } as never);
203
+ device.mainId = 'ble-device';
204
+ device.commands = { disposed: false } as never;
205
+ (device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
206
+ const release = jest.spyOn(device, 'release').mockResolvedValue(undefined);
207
+ const run = jest.fn().mockResolvedValue(undefined);
208
+
209
+ await device.run(run, {
210
+ forceProtocolDetection: true,
211
+ skipInitialize: true,
212
+ });
213
+
214
+ expect(run).toHaveBeenCalledTimes(1);
215
+ expect(release).toHaveBeenCalledTimes(1);
216
+ });
217
+
116
218
  test('keeps an explicit connectProtocol as a strict expected protocol', async () => {
117
219
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
118
220
  const device = Device.fromDescriptor({
@@ -38,6 +38,28 @@ describe('DevicePool state lifecycle', () => {
38
38
  expect(getDeviceState).toHaveBeenNthCalledWith(2, { refreshSections: ['settings'] });
39
39
  });
40
40
 
41
+ test('actively re-detects a cached device when protocol detection is forced', async () => {
42
+ const descriptor = { path: 'cached-path', protocolType: 'V1' } as any;
43
+ const device = Device.fromDescriptor(descriptor);
44
+ const acquire = jest.spyOn(device, 'acquire').mockImplementation(() => {
45
+ device.originalDescriptor.protocolType = 'V2';
46
+ return Promise.resolve(true);
47
+ });
48
+ const initialize = jest.spyOn(device, 'initialize').mockResolvedValue(undefined);
49
+ const release = jest.spyOn(device, 'release').mockResolvedValue(undefined);
50
+ DevicePool.devicesCache = { 'cached-id': device };
51
+
52
+ const result = await DevicePool.getDevices([descriptor], 'cached-id', {
53
+ forceProtocolDetection: true,
54
+ });
55
+
56
+ expect(acquire).toHaveBeenCalledWith(undefined, { forceProtocolDetection: true });
57
+ expect(initialize).toHaveBeenCalledWith({ forceProtocolDetection: true });
58
+ expect(release).toHaveBeenCalledTimes(1);
59
+ expect(device.originalDescriptor.protocolType).toBe('V2');
60
+ expect(result.deviceList).toEqual([device]);
61
+ });
62
+
41
63
  test('keeps a discovered device when its Protocol V2 label cannot be read', async () => {
42
64
  const labelError = new Error('settings unavailable');
43
65
  const getDeviceState = jest