@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
@@ -1,14 +1,28 @@
1
+ import { type Deferred, createDeferred } from '@onekeyfe/hd-shared';
2
+
1
3
  import { LoggerNames, getLogger } from '../utils';
2
4
 
3
- import type { Deferred } from '@onekeyfe/hd-shared';
4
5
  import type { BaseMethod } from '../api/BaseMethod';
5
6
 
6
7
  const Log = getLogger(LoggerNames.Core);
8
+ const OPERATION_PRE_CANCEL_TTL_MS = 30_000;
9
+ const DEFAULT_BACKGROUND_PREEMPT_TIMEOUT_MS = 5_500;
10
+
7
11
  export type RequestTask = {
8
12
  id: number;
9
13
  method: BaseMethod;
10
14
  callPromise?: Deferred<any> | undefined;
11
15
  abortController?: AbortController;
16
+ settled: Deferred<void>;
17
+ released: Deferred<void>;
18
+ };
19
+
20
+ export type RequestAdmission =
21
+ | { status: 'reserved'; task: RequestTask }
22
+ | { status: 'busy'; blockingTask?: RequestTask };
23
+
24
+ export type RequestAdmissionOptions = {
25
+ backgroundPreemptTimeoutMs?: number;
12
26
  };
13
27
 
14
28
  export default class RequestQueue {
@@ -16,8 +30,18 @@ export default class RequestQueue {
16
30
 
17
31
  private operationRequestIds = new Map<string, number>();
18
32
 
33
+ private pendingOperationCancellations = new Map<string, number>();
34
+
19
35
  private pendingCallbackTasks = new Map<string, Deferred<void>>();
20
36
 
37
+ private prunePendingOperationCancellations(now = Date.now()) {
38
+ this.pendingOperationCancellations.forEach((expiresAt, operationId) => {
39
+ if (expiresAt <= now) {
40
+ this.pendingOperationCancellations.delete(operationId);
41
+ }
42
+ });
43
+ }
44
+
21
45
  // 生成唯一请求ID
22
46
  public generateRequestId = (method?: BaseMethod) => {
23
47
  if (method && method.responseID != null) {
@@ -33,18 +57,91 @@ export default class RequestQueue {
33
57
  }
34
58
  const abortController = new AbortController();
35
59
  method.abortSignal = abortController.signal;
36
- const task = { id: requestId, method, abortController };
60
+ const task = {
61
+ id: requestId,
62
+ method,
63
+ abortController,
64
+ settled: createDeferred<void>(),
65
+ released: createDeferred<void>(),
66
+ };
37
67
  this.requestQueue.set(requestId, task);
38
68
  const operationId = method.payload?.operationId;
39
69
  if (typeof operationId === 'string' && operationId) {
40
70
  this.operationRequestIds.set(operationId, requestId);
71
+ this.prunePendingOperationCancellations();
72
+ if (this.pendingOperationCancellations.delete(operationId)) {
73
+ abortController.abort();
74
+ }
41
75
  }
42
76
  return task;
43
77
  }
44
78
 
79
+ public async admitTask(
80
+ method: BaseMethod,
81
+ options: RequestAdmissionOptions = {}
82
+ ): Promise<RequestAdmission> {
83
+ const activeTasks = Array.from(this.requestQueue.values()).filter(
84
+ task => task.method.connectId === method.connectId
85
+ );
86
+
87
+ if (method.executionPriority === 'background') {
88
+ if (activeTasks.length) {
89
+ Log.debug(
90
+ `Reject background method ${method.name}: device is occupied by ${activeTasks[0].method.name}`
91
+ );
92
+ return { status: 'busy', blockingTask: activeTasks[0] };
93
+ }
94
+ return { status: 'reserved', task: this.createTask(method) };
95
+ }
96
+
97
+ const backgroundTasks = activeTasks.filter(
98
+ task => task.method.executionPriority === 'background'
99
+ );
100
+ if (!backgroundTasks.length) {
101
+ return { status: 'reserved', task: this.createTask(method) };
102
+ }
103
+
104
+ Log.debug(
105
+ `Preempt ${backgroundTasks.length} background request(s) for user method ${method.name}`
106
+ );
107
+ backgroundTasks.forEach(task => task.abortController?.abort());
108
+ const timeoutMs =
109
+ options.backgroundPreemptTimeoutMs ??
110
+ Math.max(
111
+ DEFAULT_BACKGROUND_PREEMPT_TIMEOUT_MS,
112
+ ...backgroundTasks.map(task => task.method.maxAbortLatencyMs ?? 0)
113
+ );
114
+ const released = await this.waitForTasksReleased(backgroundTasks, timeoutMs);
115
+ if (!released) {
116
+ Log.warn(`Background request cleanup timed out before user method ${method.name}`);
117
+ return { status: 'busy', blockingTask: backgroundTasks[0] };
118
+ }
119
+
120
+ return { status: 'reserved', task: this.createTask(method) };
121
+ }
122
+
123
+ private async waitForTasksReleased(tasks: RequestTask[], timeoutMs: number) {
124
+ let timer: ReturnType<typeof setTimeout> | undefined;
125
+ const timeoutPromise = new Promise<false>(resolve => {
126
+ timer = setTimeout(() => resolve(false), timeoutMs);
127
+ });
128
+ const releasedPromise = Promise.all(tasks.map(task => task.released.promise)).then(() => true);
129
+ const released = await Promise.race([releasedPromise, timeoutPromise]);
130
+ if (timer) {
131
+ clearTimeout(timer);
132
+ }
133
+ return released;
134
+ }
135
+
45
136
  public abortOperation(operationId: string) {
46
137
  const requestId = this.operationRequestIds.get(operationId);
47
- return requestId === undefined ? false : this.abortRequest(requestId);
138
+ if (requestId !== undefined) {
139
+ return this.abortRequest(requestId);
140
+ }
141
+ const now = Date.now();
142
+ this.prunePendingOperationCancellations(now);
143
+ this.pendingOperationCancellations.set(operationId, now + OPERATION_PRE_CANCEL_TTL_MS);
144
+ return false;
48
145
  }
49
146
 
50
147
  public getTask(requestId: number): RequestTask | undefined {
@@ -89,6 +186,7 @@ export default class RequestQueue {
89
186
  count++;
90
187
  }
91
188
  });
189
+ this.pendingOperationCancellations.clear();
92
190
  return count;
93
191
  }
94
192
 
@@ -102,8 +200,8 @@ export default class RequestQueue {
102
200
  const request = this.requestQueue.get(requestId);
103
201
  if (request) {
104
202
  request.callPromise?.resolve(response);
203
+ request.settled.resolve();
105
204
  }
106
- this.releaseTask(requestId);
107
205
  }
108
206
 
109
207
  // 拒绝请求
@@ -111,19 +209,22 @@ export default class RequestQueue {
111
209
  const request = this.requestQueue.get(requestId);
112
210
  if (request) {
113
211
  request.callPromise?.reject(error);
212
+ request.settled.resolve();
114
213
  }
115
- this.releaseTask(requestId);
116
214
  }
117
215
 
118
216
  // 删除请求
119
217
  public releaseTask(requestId: number) {
120
- const operationId = this.requestQueue.get(requestId)?.method.payload?.operationId;
218
+ const task = this.requestQueue.get(requestId);
219
+ const operationId = task?.method.payload?.operationId;
121
220
  if (
122
221
  typeof operationId === 'string' &&
123
222
  this.operationRequestIds.get(operationId) === requestId
124
223
  ) {
125
224
  this.operationRequestIds.delete(operationId);
126
225
  }
226
+ task?.settled.resolve();
227
+ task?.released.resolve();
127
228
  this.requestQueue.delete(requestId);
128
229
  }
129
230
 
package/src/core/index.ts CHANGED
@@ -52,6 +52,7 @@ import {
52
52
  createDeviceMessage,
53
53
  createResponseMessage,
54
54
  createUiMessage,
55
+ getLogBlockLabel,
55
56
  } from '../events';
56
57
  import TransportManager from '../data-manager/TransportManager';
57
58
  import DeviceConnector from '../device/DeviceConnector';
@@ -98,14 +99,14 @@ function hasDeriveCardano(method: BaseMethod): boolean {
98
99
 
99
100
  const parseInitOptions = (method?: BaseMethod): InitOptions => ({
100
101
  initSession: method?.payload.initSession,
101
- passphraseState: method?.payload.passphraseState,
102
+ passphraseState: method?.payload.useEmptyPassphrase ? undefined : method?.payload.passphraseState,
102
103
  deviceId: method?.payload.deviceId,
103
104
  deriveCardano: method && hasDeriveCardano(method),
104
105
  connectProtocol: method?.payload.connectProtocol,
105
106
  protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
106
107
  });
107
108
 
108
- let _core: Core;
109
+ let _core: Core | undefined;
109
110
  let _deviceList: DeviceList | undefined;
110
111
  let _connector: DeviceConnector | undefined;
111
112
  let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
@@ -149,7 +150,7 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
149
150
  }
150
151
 
151
152
  // find api method
152
- let method: BaseMethod;
153
+ let method!: BaseMethod;
153
154
  try {
154
155
  method = findMethod(message as IFrameCallMessage);
155
156
  method.connector = _connector;
@@ -165,7 +166,11 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
165
166
 
166
167
  method.init();
167
168
  } catch (error) {
168
- return Promise.reject(error);
169
+ if (method) {
170
+ completeMethodRequestContext(method, error);
171
+ method.dispose();
172
+ }
173
+ return createResponseMessage(method?.responseID ?? message.id, false, { error });
169
174
  }
170
175
 
171
176
  DevicePool.emitter.on(DEVICE.CONNECT, onDeviceConnectHandler);
@@ -191,34 +196,25 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
191
196
  // );
192
197
  // }
193
198
 
194
- const { requestQueue, methodSynchronize } = context;
195
- const error = await methodSynchronize(() => {
196
- for (const requestId of requestQueue.getRequestTasksId()) {
197
- const task = requestQueue.getTask(requestId);
198
- Log.debug(
199
- 'pre request task: ',
200
- `task?.id: ${task?.id},
201
- task?.method.connectId: ${task?.method.connectId},
202
- task?.method.deviceId: ${task?.method.deviceId},
203
- task?.method.name: ${task?.method.name}`
204
- );
205
- // if (task) {
206
- // return Promise.reject(ERRORS.TypedError(HardwareErrorCode.DeviceBusy));
207
- // }
208
- }
209
- return null;
210
- });
211
-
212
- if (error) {
213
- return createResponseMessage(method.responseID, false, { error });
214
- }
215
-
216
199
  // only the pre-warm signal (PreInitialize) forks here; normal methods fall
217
200
  // through to onCallDevice below, so the pre-warm dedup/guards never touch them
218
201
  if (method.isPreWarmSignal) {
219
202
  return handlePreWarmSignal(context, message, method);
220
203
  }
221
204
 
205
+ const { requestQueue, methodSynchronize } = context;
206
+ const admission = await methodSynchronize(
207
+ () => requestQueue.admitTask(method),
208
+ method.connectId ?? method.deviceId ?? 'unknown-device'
209
+ );
210
+ if (admission.status === 'busy') {
211
+ const error = ERRORS.TypedError(HardwareErrorCode.DeviceBusy);
212
+ DevicePool.emitter.removeListener(DEVICE.CONNECT, onDeviceConnectHandler);
213
+ completeMethodRequestContext(method, error);
214
+ method.dispose();
215
+ return createResponseMessage(method.responseID, false, { error });
216
+ }
217
+
222
218
  return onCallDevice(context, message, method);
223
219
  };
224
220
 
@@ -231,6 +227,7 @@ const handlePreWarmSignal = async (
231
227
  method: BaseMethod
232
228
  ): Promise<any> => {
233
229
  const createAckResponse = () => {
230
+ DevicePool.emitter.removeListener(DEVICE.CONNECT, onDeviceConnectHandler);
234
231
  completeMethodRequestContext(method);
235
232
  method.dispose();
236
233
  return createResponseMessage(method.responseID, true, true);
@@ -337,7 +334,7 @@ const onCallDevice = async (
337
334
 
338
335
  await waitForPendingPromise(getPrePendingCallPromise, setPrePendingCallPromise);
339
336
 
340
- const task = requestQueue.createTask(method);
337
+ const task = requestQueue.getTask(method.responseID) ?? requestQueue.createTask(method);
341
338
 
342
339
  // Pre-warm holds the device as a per-connectId callback task so a concurrent
343
340
  // real call waits (before ensureConnected) instead of racing its Initialize.
@@ -364,7 +361,7 @@ const onCallDevice = async (
364
361
  );
365
362
  } catch (e) {
366
363
  preWarmCallbackTask?.resolve();
367
- console.log('ensureConnected error: ', e);
364
+ Log.debug('ensureConnected error: ', e);
368
365
 
369
366
  completeMethodRequestContext(method, e);
370
367
 
@@ -381,6 +378,10 @@ const onCallDevice = async (
381
378
  if (method.payload?.onlyConnectBleDevice) {
382
379
  preWarmCallbackTask?.resolve();
383
380
  Log.debug('Call API - only connect ble device: ', device?.mainId);
381
+ DevicePool.emitter.removeListener(DEVICE.CONNECT, onDeviceConnectHandler);
382
+ requestQueue.releaseTask(method.responseID);
383
+ completeMethodRequestContext(method);
384
+ method.dispose();
384
385
  return createResponseMessage(method.responseID, true, null);
385
386
  }
386
387
 
@@ -432,8 +433,8 @@ const onCallDevice = async (
432
433
  await waitForPendingPromise(getPrePendingCallPromise, setPrePendingCallPromise);
433
434
 
434
435
  const inner = async (): Promise<void> => {
435
- // Protocol V2 专属方法统一守卫:协议在 acquire/initialize 后已确定,
436
- // V2 设备直接抛出明确的 NotSupport 错误(见 BaseMethod.requireProtocolV2)。
436
+ // The protocol is established after acquire/initialize. Reject Protocol V2-only
437
+ // methods on other devices through BaseMethod.requireProtocolV2.
437
438
  if (method.requireProtocolV2 && !device.isProtocolV2()) {
438
439
  throw createDeviceNotSupportMethodError(method.name, device.getCurrentFirmwareType());
439
440
  }
@@ -447,8 +448,8 @@ const onCallDevice = async (
447
448
  const deviceFirmwareType = device.getCurrentFirmwareType();
448
449
  let newVersionStatus: ReturnType<typeof DataManager.getFirmwareStatus> | undefined;
449
450
 
450
- // 故障批次检测与远端强制升级门禁基于 V1 features/remote config
451
- // Pro2(V2) 暂不参与:profile 版的 firmwareStatus 需要 DataManager 支持后再接入。
451
+ // Defective-batch and forced-update gates depend on V1 features/remote config.
452
+ // Pro2 joins after DataManager supports its profile firmwareStatus.
452
453
  if (device.features && !device.isProtocolV2()) {
453
454
  await DataManager.checkAndReloadData();
454
455
 
@@ -660,8 +661,9 @@ const onCallDevice = async (
660
661
  skipInitialize: canSkipInitialize(method, device),
661
662
  ...parseInitOptions(method),
662
663
  };
663
- const deviceRun = () => device.run(inner, runOptions);
664
- task.callPromise = createDeferred<any>(deviceRun);
664
+ const deviceRunPromise = device.run(inner, runOptions);
665
+ task.callPromise = createDeferred<any>();
666
+ deviceRunPromise.catch(error => task.callPromise?.reject(error));
665
667
 
666
668
  try {
667
669
  return await task.callPromise.promise;
@@ -669,6 +671,8 @@ const onCallDevice = async (
669
671
  Log.debug('Device Run Error: ', e);
670
672
  completeMethodRequestContext(method, e);
671
673
  return createResponseMessage(method.responseID, false, { error: e });
674
+ } finally {
675
+ await deviceRunPromise.catch(() => undefined);
672
676
  }
673
677
  } catch (error) {
674
678
  messageResponse = createResponseMessage(method.responseID, false, { error });
@@ -1192,16 +1196,16 @@ const closePopup = () => {
1192
1196
  };
1193
1197
 
1194
1198
  const onDeviceConnectHandler = (device: Device) => {
1195
- const env = DataManager.getSettings('env');
1196
- const deviceObject = DataManager.isBleConnect(env) ? device : device.toMessageObject();
1197
- postMessage(createDeviceMessage(DEVICE.CONNECT, { device: deviceObject as KnownDevice }));
1199
+ const deviceObject = device.toMessageObject();
1200
+ if (!deviceObject) return;
1201
+ postMessage(createDeviceMessage(DEVICE.CONNECT, { device: deviceObject }));
1198
1202
  };
1199
1203
 
1200
1204
  const onDeviceDisconnectHandler = (device: Device) => {
1201
1205
  device.clearPreInitialized();
1202
- const env = DataManager.getSettings('env');
1203
- const deviceObject = DataManager.isBleConnect(env) ? device : device.toMessageObject();
1204
- postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject as KnownDevice }));
1206
+ const deviceObject = device.toMessageObject();
1207
+ if (!deviceObject) return;
1208
+ postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject }));
1205
1209
  };
1206
1210
 
1207
1211
  const onDevicePinHandler = async (...[device, type, callback]: DeviceEvents['pin']) => {
@@ -1371,6 +1375,8 @@ export default class Core extends EventEmitter {
1371
1375
 
1372
1376
  private requestQueue = new RequestQueue();
1373
1377
 
1378
+ private disposePromise?: Promise<void>;
1379
+
1374
1380
  // background task
1375
1381
  private prePendingCallPromise: Promise<void> | undefined;
1376
1382
 
@@ -1433,10 +1439,17 @@ export default class Core extends EventEmitter {
1433
1439
  }
1434
1440
 
1435
1441
  case IFRAME.CALL: {
1436
- Log.log(`[${Date.now()}][CALL_API]`, message);
1442
+ const logBlockLabel = getLogBlockLabel(message);
1443
+ Log.log(
1444
+ `[${Date.now()}][CALL_API]`,
1445
+ logBlockLabel ? { method: logBlockLabel, payload: '[REDACTED]' } : message
1446
+ );
1437
1447
  const response = await callAPI(this.getCoreContext(), message);
1438
1448
  const { success, payload } = response;
1439
- Log.log(`[${Date.now()}][CALL_API_RESPONSE]`, response);
1449
+ Log.log(
1450
+ `[${Date.now()}][CALL_API_RESPONSE]`,
1451
+ logBlockLabel ? { method: logBlockLabel, payload: '[REDACTED]' } : response
1452
+ );
1440
1453
  if (success) {
1441
1454
  return response;
1442
1455
  }
@@ -1470,20 +1483,52 @@ export default class Core extends EventEmitter {
1470
1483
  return Promise.resolve(message);
1471
1484
  }
1472
1485
 
1473
- dispose() {
1486
+ dispose(): Promise<void> {
1487
+ if (!this.disposePromise) {
1488
+ this.disposePromise = this.disposeResources();
1489
+ }
1490
+ return this.disposePromise;
1491
+ }
1492
+
1493
+ private async disposeResources(): Promise<void> {
1494
+ Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
1495
+ pollingManager.stopAll();
1496
+ this.requestQueue.abortAllRequests();
1497
+ cleanup();
1498
+
1499
+ _connector?.stop();
1500
+ let transportCleanup: Promise<void>;
1501
+ try {
1502
+ transportCleanup = Promise.resolve(TransportManager.getTransport()?.stop?.()).catch(error => {
1503
+ Log.warn('[Core] Transport cleanup failed:', error);
1504
+ });
1505
+ } catch (error) {
1506
+ Log.warn('[Core] Transport cleanup failed:', error);
1507
+ transportCleanup = Promise.resolve();
1508
+ }
1509
+
1510
+ // Preserve synchronous dispose semantics for in-memory state and listeners.
1474
1511
  _deviceList = undefined;
1475
1512
  _connector = undefined;
1513
+ DevicePool.resetState();
1476
1514
  deviceCacheMap.clear();
1477
1515
  preWarmInflight.clear();
1478
1516
  preWarmDoneAt.clear();
1479
- Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
1517
+ preConnectCache = { passphraseState: undefined };
1518
+ this.prePendingCallPromise = undefined;
1519
+ this.removeAllListeners();
1480
1520
  cleanupSdkInstance(this.sdkInstanceId);
1521
+ if (_core === this) _core = undefined;
1522
+
1523
+ // Transports, especially Node USB, may release native handles asynchronously.
1524
+ await transportCleanup;
1481
1525
  }
1482
1526
  }
1483
1527
 
1484
1528
  export const initCore = () => {
1485
- _core = new Core();
1486
- return _core;
1529
+ const core = new Core();
1530
+ _core = core;
1531
+ return core;
1487
1532
  };
1488
1533
 
1489
1534
  export const initConnector = () => {
@@ -482,7 +482,7 @@
482
482
  "MessageType_DeviceStatus": 60603,
483
483
  "MessageType_DevGetOnboardingStatus": 60604,
484
484
  "MessageType_DevOnboardingStatus": 60605,
485
- "MessageType_DeviceSessionOpen": 60606,
485
+ "MessageType_DeviceSessionGet": 60606,
486
486
  "MessageType_DeviceSession": 60607,
487
487
  "MessageType_DeviceSessionAskPin": 60608,
488
488
  "MessageType_FilesystemPermissionFix": 60800,
@@ -11849,61 +11849,49 @@
11849
11849
  },
11850
11850
  "DeviceSettings": {
11851
11851
  "fields": {
11852
- "label": {
11853
- "type": "string",
11854
- "id": 1
11855
- },
11856
11852
  "bt_enable": {
11857
11853
  "type": "bool",
11858
- "id": 2
11854
+ "id": 1
11859
11855
  },
11860
11856
  "language": {
11861
11857
  "type": "string",
11862
- "id": 3
11858
+ "id": 2
11863
11859
  },
11864
11860
  "wallpaper_path": {
11865
11861
  "type": "string",
11866
- "id": 4
11862
+ "id": 3
11867
11863
  },
11868
11864
  "brightness": {
11869
11865
  "type": "uint32",
11870
- "id": 7
11871
- },
11872
- "autolock_delay_ms": {
11873
- "type": "uint32",
11874
- "id": 8
11875
- },
11876
- "autoshutdown_delay_ms": {
11877
- "type": "uint32",
11878
- "id": 9
11866
+ "id": 4
11879
11867
  },
11880
11868
  "animation_enable": {
11881
11869
  "type": "bool",
11882
- "id": 10
11870
+ "id": 5
11883
11871
  },
11884
11872
  "tap_to_wake": {
11885
11873
  "type": "bool",
11886
- "id": 11
11874
+ "id": 6
11887
11875
  },
11888
11876
  "haptic_feedback": {
11889
11877
  "type": "bool",
11890
- "id": 12
11878
+ "id": 7
11891
11879
  },
11892
11880
  "device_name_display_enabled": {
11893
11881
  "type": "bool",
11894
- "id": 13
11882
+ "id": 8
11895
11883
  },
11896
11884
  "airgap_mode": {
11897
11885
  "type": "bool",
11898
- "id": 14
11886
+ "id": 9
11899
11887
  },
11900
11888
  "usb_lock_enable": {
11901
11889
  "type": "bool",
11902
- "id": 17
11890
+ "id": 10
11903
11891
  },
11904
11892
  "random_keypad": {
11905
11893
  "type": "bool",
11906
- "id": 18
11894
+ "id": 11
11907
11895
  },
11908
11896
  "passphrase_enable": {
11909
11897
  "type": "bool",
@@ -11912,6 +11900,18 @@
11912
11900
  "fido_enabled": {
11913
11901
  "type": "bool",
11914
11902
  "id": 101
11903
+ },
11904
+ "autolock_delay_ms": {
11905
+ "type": "uint32",
11906
+ "id": 102
11907
+ },
11908
+ "autoshutdown_delay_ms": {
11909
+ "type": "uint32",
11910
+ "id": 103
11911
+ },
11912
+ "label": {
11913
+ "type": "string",
11914
+ "id": 104
11915
11915
  }
11916
11916
  }
11917
11917
  },
@@ -12427,68 +12427,14 @@
12427
12427
  }
12428
12428
  }
12429
12429
  },
12430
- "DeviceSessionResume": {
12430
+ "DeviceSessionGet": {
12431
12431
  "fields": {
12432
12432
  "session_id": {
12433
- "rule": "required",
12434
12433
  "type": "bytes",
12435
12434
  "id": 1
12436
12435
  }
12437
12436
  }
12438
12437
  },
12439
- "DeviceHostPassphrase": {
12440
- "fields": {
12441
- "passphrase": {
12442
- "rule": "required",
12443
- "type": "string",
12444
- "id": 1
12445
- }
12446
- }
12447
- },
12448
- "DevicePassphraseOnDevice": {
12449
- "fields": {}
12450
- },
12451
- "DeviceAttachPinOnDevice": {
12452
- "fields": {}
12453
- },
12454
- "DeviceWalletSelect": {
12455
- "oneofs": {
12456
- "access": {
12457
- "oneof": ["host_passphrase", "passphrase_on_device", "attach_pin_on_device"]
12458
- }
12459
- },
12460
- "fields": {
12461
- "host_passphrase": {
12462
- "type": "DeviceHostPassphrase",
12463
- "id": 1
12464
- },
12465
- "passphrase_on_device": {
12466
- "type": "DevicePassphraseOnDevice",
12467
- "id": 2
12468
- },
12469
- "attach_pin_on_device": {
12470
- "type": "DeviceAttachPinOnDevice",
12471
- "id": 3
12472
- }
12473
- }
12474
- },
12475
- "DeviceSessionOpen": {
12476
- "oneofs": {
12477
- "mode": {
12478
- "oneof": ["resume", "select"]
12479
- }
12480
- },
12481
- "fields": {
12482
- "resume": {
12483
- "type": "DeviceSessionResume",
12484
- "id": 1
12485
- },
12486
- "select": {
12487
- "type": "DeviceWalletSelect",
12488
- "id": 2
12489
- }
12490
- }
12491
- },
12492
12438
  "DeviceSession": {
12493
12439
  "fields": {
12494
12440
  "session_id": {
@@ -309,15 +309,15 @@ export default class DataManager {
309
309
  const deviceType = getDeviceType(features);
310
310
  if (deviceType === EDeviceType.Unknown) return 'unknown';
311
311
 
312
- const deviceBLEFirmwareVersion = getDeviceBLEFirmwareVersion(features);
312
+ const deviceBleFirmwareVersion = getDeviceBLEFirmwareVersion(features);
313
313
 
314
314
  /** mini has no device ble_ver */
315
- if (!deviceBLEFirmwareVersion) {
315
+ if (!deviceBleFirmwareVersion) {
316
316
  return 'none';
317
317
  }
318
318
 
319
319
  const targetDeviceConfigList = this.deviceMap[deviceType]?.ble ?? [];
320
- const currentVersion = deviceBLEFirmwareVersion.join('.');
320
+ const currentVersion = deviceBleFirmwareVersion.join('.');
321
321
  return getReleaseStatus(targetDeviceConfigList, currentVersion);
322
322
  };
323
323
 
@@ -325,14 +325,14 @@ export default class DataManager {
325
325
  const deviceType = getDeviceType(features);
326
326
  if (deviceType === EDeviceType.Unknown) return [];
327
327
 
328
- const deviceBLEFirmwareVersion = getDeviceBLEFirmwareVersion(features);
328
+ const deviceBleFirmwareVersion = getDeviceBLEFirmwareVersion(features);
329
329
 
330
- if (!deviceBLEFirmwareVersion) {
330
+ if (!deviceBleFirmwareVersion) {
331
331
  return [];
332
332
  }
333
333
 
334
334
  const targetDeviceConfigList = this.deviceMap[deviceType]?.ble ?? [];
335
- const currentVersion = deviceBLEFirmwareVersion.join('.');
335
+ const currentVersion = deviceBleFirmwareVersion.join('.');
336
336
  return getReleaseChangelog(targetDeviceConfigList, currentVersion);
337
337
  };
338
338