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

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 (202) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +78 -0
  2. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +14 -1
  4. package/__tests__/connectSettings.test.ts +5 -47
  5. package/__tests__/deviceUploadNft.test.ts +272 -0
  6. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  7. package/__tests__/get-device-state.test.ts +132 -12
  8. package/__tests__/homescreen.test.ts +41 -1
  9. package/__tests__/logBlockEvent.test.ts +1 -1
  10. package/__tests__/open-wallet-session.test.ts +366 -31
  11. package/__tests__/pro2Nft.test.ts +108 -0
  12. package/__tests__/protocol-v2-resources.test.ts +204 -0
  13. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  14. package/__tests__/protocol-v2-unlock-policy.test.ts +196 -35
  15. package/__tests__/protocol-v2.test.ts +568 -957
  16. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  17. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  18. package/__tests__/uiProgressThrottle.test.ts +74 -0
  19. package/dist/api/BaseMethod.d.ts +3 -1
  20. package/dist/api/BaseMethod.d.ts.map +1 -1
  21. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  22. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  23. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  25. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV4.d.ts +6 -25
  27. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  28. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  29. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  30. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  32. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  33. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -8
  34. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  35. package/dist/api/firmware/getBinary.d.ts +0 -7
  36. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  37. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  38. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  39. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  40. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  41. package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
  42. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  43. package/dist/api/index.d.ts +1 -0
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  46. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  48. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  49. package/dist/core/deviceEventRegistration.d.ts +2 -0
  50. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/data-manager/DataManager.d.ts +3 -2
  53. package/dist/data-manager/DataManager.d.ts.map +1 -1
  54. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  55. package/dist/device/Device.d.ts +24 -4
  56. package/dist/device/Device.d.ts.map +1 -1
  57. package/dist/device/DeviceCommands.d.ts +4 -4
  58. package/dist/events/device.d.ts +4 -0
  59. package/dist/events/device.d.ts.map +1 -1
  60. package/dist/events/logBlockEvent.d.ts.map +1 -1
  61. package/dist/events/ui-request.d.ts +28 -2
  62. package/dist/events/ui-request.d.ts.map +1 -1
  63. package/dist/index.d.ts +115 -184
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2051 -3349
  66. package/dist/inject.d.ts.map +1 -1
  67. package/dist/lowLevelInject.d.ts.map +1 -1
  68. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  69. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  70. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  71. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  72. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  73. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  74. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  75. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  76. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  77. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  78. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  79. package/dist/topLevelInject.d.ts.map +1 -1
  80. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -4
  81. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  82. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  83. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  84. package/dist/types/api/export.d.ts +1 -4
  85. package/dist/types/api/export.d.ts.map +1 -1
  86. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  87. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  88. package/dist/types/api/index.d.ts +3 -10
  89. package/dist/types/api/index.d.ts.map +1 -1
  90. package/dist/types/api/protocolV2.d.ts +3 -0
  91. package/dist/types/api/protocolV2.d.ts.map +1 -1
  92. package/dist/types/settings.d.ts +12 -13
  93. package/dist/types/settings.d.ts.map +1 -1
  94. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  95. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  96. package/dist/utils/homescreen.d.ts +4 -0
  97. package/dist/utils/homescreen.d.ts.map +1 -1
  98. package/dist/utils/index.d.ts +1 -1
  99. package/dist/utils/index.d.ts.map +1 -1
  100. package/dist/utils/patch.d.ts +1 -1
  101. package/dist/utils/patch.d.ts.map +1 -1
  102. package/dist/utils/pro2Nft.d.ts +31 -0
  103. package/dist/utils/pro2Nft.d.ts.map +1 -0
  104. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  105. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  106. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  107. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  108. package/package.json +4 -4
  109. package/src/api/BaseMethod.ts +8 -10
  110. package/src/api/CheckAllFirmwareRelease.ts +40 -53
  111. package/src/api/FirmwareUpdateV2.ts +120 -298
  112. package/src/api/FirmwareUpdateV3.ts +57 -263
  113. package/src/api/FirmwareUpdateV4.ts +307 -912
  114. package/src/api/GetPassphraseState.ts +7 -1
  115. package/src/api/OpenWalletSession.ts +54 -17
  116. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
  117. package/src/api/device/DeviceUnlock.ts +1 -1
  118. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  119. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -57
  120. package/src/api/firmware/updateBootloader.ts +4 -19
  121. package/src/api/firmware/uploadFirmware.ts +22 -140
  122. package/src/api/helpers/protocolV2FileWrite.ts +11 -4
  123. package/src/api/index.ts +1 -0
  124. package/src/api/protocol-v2/DeviceUploadNft.ts +189 -0
  125. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  126. package/src/core/deviceEventRegistration.ts +4 -0
  127. package/src/core/index.ts +136 -77
  128. package/src/data/messages/messages-protocol-v2.json +68 -0
  129. package/src/data-manager/DataManager.ts +63 -38
  130. package/src/data-manager/connectSettings.ts +0 -11
  131. package/src/device/Device.ts +203 -21
  132. package/src/events/device.ts +4 -0
  133. package/src/events/logBlockEvent.ts +1 -0
  134. package/src/events/ui-request.ts +33 -2
  135. package/src/index.ts +0 -5
  136. package/src/inject.ts +3 -22
  137. package/src/lowLevelInject.ts +1 -5
  138. package/src/protocols/protocol-v2/features.ts +9 -2
  139. package/src/protocols/protocol-v2/resources.ts +233 -0
  140. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  141. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  142. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +124 -0
  143. package/src/protocols/protocol-v2/walletSession.ts +154 -30
  144. package/src/topLevelInject.ts +1 -5
  145. package/src/types/api/checkAllFirmwareRelease.ts +1 -4
  146. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  147. package/src/types/api/export.ts +0 -18
  148. package/src/types/api/firmwareUpdate.ts +0 -76
  149. package/src/types/api/index.ts +2 -14
  150. package/src/types/api/protocolV2.ts +13 -0
  151. package/src/types/settings.ts +26 -13
  152. package/src/utils/deviceFeaturesUtils.ts +4 -0
  153. package/src/utils/homescreen.ts +32 -6
  154. package/src/utils/index.ts +6 -1
  155. package/src/utils/pro2Nft.ts +142 -0
  156. package/src/utils/pro2Wallpaper.ts +35 -8
  157. package/src/utils/uiProgressThrottle.ts +63 -0
  158. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  159. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  160. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  161. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  162. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  163. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  164. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  165. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  166. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  167. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  168. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  169. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  170. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  171. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  172. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  173. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  174. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  175. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  176. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  177. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  178. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  179. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  180. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  181. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  182. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  183. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  184. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  185. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  186. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  187. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  188. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  189. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  190. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  191. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  192. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  193. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  194. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  195. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -745
  196. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  197. package/src/api/firmware/progressThrottle.ts +0 -44
  198. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  199. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  200. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  201. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  202. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -10,7 +10,6 @@ import { bytesToHex } from '../helpers/hexUtils';
10
10
  import { DataManager } from '../../data-manager';
11
11
  import { DevicePool } from '../../device/DevicePool';
12
12
  import { buildProtocolV1FeaturesPayload } from '../../deviceProfile';
13
- import { openFirmwareByteSource, readFirmwareByteSourceFully } from './FirmwareArtifactSource';
14
13
 
15
14
  import type { KnownDevice } from '../../types';
16
15
  import type { TypedCall, TypedResponseMessage } from '../../device/DeviceCommands';
@@ -18,7 +17,6 @@ import type { PROTO } from '../../constants';
18
17
  import type { CoreMessage, IFirmwareUpdateProgressType } from '../../events';
19
18
  import type { Success } from '@onekeyfe/hd-transport';
20
19
  import type { Device } from '../../device/Device';
21
- import type { FirmwareByteSource } from './FirmwareArtifactSource';
22
20
 
23
21
  const NEW_BOOT_UPRATE_FIRMWARE_VERSION = '2.4.5';
24
22
  const SESSION_ERROR = 'session not found';
@@ -90,20 +88,17 @@ export const waitBleInstall = async (updateType: string) => {
90
88
  }
91
89
  };
92
90
 
93
- const toArrayBuffer = (payload: ArrayBuffer | Buffer): ArrayBuffer => {
94
- if (payload instanceof ArrayBuffer) {
95
- return payload;
96
- }
97
- return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength).slice().buffer;
98
- };
99
-
100
- export const uploadFirmwareFromSource = async (
91
+ export const uploadFirmware = async (
101
92
  updateType: 'firmware' | 'ble',
102
93
  typedCall: TypedCall,
103
94
  postMessage: (message: CoreMessage) => void,
104
95
  device: Device,
105
- source: FirmwareByteSource,
106
- rebootOnSuccess?: boolean,
96
+ {
97
+ payload,
98
+ rebootOnSuccess,
99
+ }: PROTO.FirmwareUpload & {
100
+ rebootOnSuccess?: boolean;
101
+ },
107
102
  isUpdateBootloader?: boolean
108
103
  ) => {
109
104
  const deviceType = device.getCurrentDeviceType();
@@ -124,20 +119,20 @@ export const uploadFirmwareFromSource = async (
124
119
  if (supportUpgradeFileHeader) {
125
120
  // Extract and validate firmware header (first 1KB)
126
121
  const HEADER_SIZE = 1024;
127
- if (source.size < HEADER_SIZE) {
122
+ if (payload.byteLength < HEADER_SIZE) {
128
123
  throw ERRORS.TypedError(
129
124
  HardwareErrorCode.RuntimeError,
130
- `firmware payload too small: ${source.size} bytes, expected at least ${HEADER_SIZE} bytes`
125
+ `firmware payload too small: ${payload.byteLength} bytes, expected at least ${HEADER_SIZE} bytes`
131
126
  );
132
127
  }
133
128
 
134
129
  Log.debug('Uploading firmware header:', {
135
130
  size: HEADER_SIZE,
136
- totalSize: source.size,
131
+ totalSize: payload.byteLength,
137
132
  });
138
133
  postProgressTip(device, 'UploadingFirmwareHeader', postMessage);
139
134
 
140
- const header = new Uint8Array(await source.readAt(0, HEADER_SIZE));
135
+ const header = new Uint8Array(payload.slice(0, HEADER_SIZE));
141
136
 
142
137
  try {
143
138
  const headerRes = await typedCall('UpgradeFileHeader', 'Success', {
@@ -174,9 +169,6 @@ export const uploadFirmwareFromSource = async (
174
169
  postProgressMessage(device, 0, 'installingFirmware', postMessage);
175
170
  let updateResponse: TypedResponseMessage<'Success'>;
176
171
  try {
177
- // Classic/Mini bootloaders accept one protobuf payload and do not expose
178
- // a request-range protocol. Other device families remain fully streamed.
179
- const payload = await readFirmwareByteSourceFully(source);
180
172
  updateResponse = await typedCall('FirmwareUpload', 'Success', {
181
173
  payload,
182
174
  });
@@ -208,7 +200,9 @@ export const uploadFirmwareFromSource = async (
208
200
  updateType,
209
201
  postMessage,
210
202
  device,
211
- source,
203
+ {
204
+ payload,
205
+ },
212
206
  rebootOnSuccess
213
207
  );
214
208
  return response.message;
@@ -217,7 +211,7 @@ export const uploadFirmwareFromSource = async (
217
211
 
218
212
  postConfirmationMessage(device);
219
213
  postProgressTip(device, 'ConfirmOnDevice', postMessage);
220
- const length = source.size;
214
+ const length = payload.byteLength;
221
215
 
222
216
  let response = await typedCall('FirmwareErase', ['FirmwareRequest', 'Success'], { length });
223
217
  postProgressTip(device, 'FirmwareEraseSuccess', postMessage);
@@ -226,7 +220,7 @@ export const uploadFirmwareFromSource = async (
226
220
  const start = response.message.offset!;
227
221
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
228
222
  const end = response.message.offset! + response.message.length!;
229
- const chunk = await source.readAt(start, end - start);
223
+ const chunk = payload.slice(start, end);
230
224
 
231
225
  if (start > 0) {
232
226
  postProgressMessage(
@@ -255,45 +249,11 @@ export const uploadFirmwareFromSource = async (
255
249
  throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'uploadFirmware: unknown device model');
256
250
  };
257
251
 
258
- export const uploadFirmware = async (
259
- updateType: 'firmware' | 'ble',
260
- typedCall: TypedCall,
261
- postMessage: (message: CoreMessage) => void,
262
- device: Device,
263
- {
264
- payload,
265
- rebootOnSuccess,
266
- }: PROTO.FirmwareUpload & {
267
- rebootOnSuccess?: boolean;
268
- },
269
- isUpdateBootloader?: boolean
270
- ) => {
271
- const source = await openFirmwareByteSource({
272
- binary: toArrayBuffer(payload),
273
- });
274
- if (!source) {
275
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'firmware payload is empty');
276
- }
277
- try {
278
- return await uploadFirmwareFromSource(
279
- updateType,
280
- typedCall,
281
- postMessage,
282
- device,
283
- source,
284
- rebootOnSuccess,
285
- isUpdateBootloader
286
- );
287
- } finally {
288
- await source.close();
289
- }
290
- };
291
-
292
252
  const newTouchUpdateProcess = async (
293
253
  updateType: 'firmware' | 'ble',
294
254
  postMessage: (message: CoreMessage) => void,
295
255
  device: Device,
296
- source: FirmwareByteSource,
256
+ { payload }: PROTO.FirmwareUpload,
297
257
  rebootOnSuccess = true
298
258
  ) => {
299
259
  let typedCall = device.getCommands().typedCall.bind(device.getCommands());
@@ -303,13 +263,13 @@ const newTouchUpdateProcess = async (
303
263
  const env = DataManager.getSettings('env');
304
264
  const perPackageSize = DataManager.isBleConnect(env) ? 16 : 128;
305
265
  const chunkSize = 1024 * perPackageSize;
306
- const totalChunks = Math.ceil(source.size / chunkSize);
266
+ const totalChunks = Math.ceil(payload.byteLength / chunkSize);
307
267
  let offset = 0;
308
268
  for (let i = 0; i < totalChunks; i++) {
309
269
  const chunkStart = i * chunkSize;
310
- const chunkEnd = Math.min(chunkStart + chunkSize, source.size);
270
+ const chunkEnd = Math.min(chunkStart + chunkSize, payload.byteLength);
311
271
  const chunkLength = chunkEnd - chunkStart;
312
- const chunk = await source.readAt(chunkStart, chunkLength);
272
+ const chunk = payload.slice(chunkStart, chunkEnd);
313
273
  const overwrite = i === 0;
314
274
  const progress = Math.round(((i + 1) / totalChunks) * 100);
315
275
  const writeRes = await emmcFileWriteWithRetry(
@@ -530,67 +490,6 @@ export const updateResource = async (
530
490
  return processResourceRequest(typedCall, res, data);
531
491
  };
532
492
 
533
- export const updateResourceFromSource = async (
534
- typedCall: TypedCall,
535
- fileName: string,
536
- source: FirmwareByteSource,
537
- onConfirmAfter?: () => void
538
- ) => {
539
- const initialLength = Math.min(INIT_DATA_CHUNK_SIZE, source.size);
540
- const initialChunk = new Uint8Array(await source.readAt(0, initialLength));
541
- let response = await typedCall('ResourceUpdate', ['ResourceRequest', 'Success'], {
542
- file_name: fileName,
543
- data_length: source.size,
544
- initial_data_chunk: bytesToHex(initialChunk),
545
- hash: bytesToHex(blake2s(initialChunk)),
546
- });
547
- onConfirmAfter?.();
548
-
549
- while (response.type !== 'Success') {
550
- const { offset, data_length: dataLength } = response.message;
551
- if (
552
- !Number.isSafeInteger(offset) ||
553
- offset === undefined ||
554
- offset < 0 ||
555
- !Number.isSafeInteger(dataLength) ||
556
- dataLength === undefined ||
557
- dataLength <= 0 ||
558
- offset + dataLength > source.size
559
- ) {
560
- throw ERRORS.TypedError(
561
- HardwareErrorCode.RuntimeError,
562
- 'Device requested an invalid firmware resource range'
563
- );
564
- }
565
- const chunk = new Uint8Array(await source.readAt(offset, dataLength));
566
- response = await typedCall('ResourceAck', ['ResourceRequest', 'Success'], {
567
- data_chunk: bytesToHex(chunk),
568
- hash: bytesToHex(blake2s(chunk)),
569
- });
570
- }
571
- return response.message;
572
- };
573
-
574
- export const updateResourcesFromSources = async (
575
- typedCall: TypedCall,
576
- postMessage: (message: CoreMessage) => void,
577
- device: Device,
578
- entries: ReadonlyArray<{ entryName: string; source: FirmwareByteSource }>
579
- ) => {
580
- postProgressTip(device, 'UpdateSysResource', postMessage);
581
- for (const [index, entry] of entries.entries()) {
582
- await updateResourceFromSource(typedCall, entry.entryName, entry.source);
583
- postProgressMessage(
584
- device,
585
- Math.floor(((index + 1) / entries.length) * 100),
586
- 'installingFirmware',
587
- postMessage
588
- );
589
- }
590
- postProgressTip(device, 'UpdateSysResourceSuccess', postMessage);
591
- return true;
592
- };
593
-
594
493
  export const updateResources = async (
595
494
  typedCall: TypedCall,
596
495
  postMessage: (message: CoreMessage) => void,
@@ -625,28 +524,11 @@ export const updateBootloader = async (
625
524
  typedCall: TypedCall,
626
525
  postMessage: (message: CoreMessage) => void,
627
526
  device: Device,
628
- data: ArrayBuffer
629
- ) => {
630
- const source = await openFirmwareByteSource({ binary: data });
631
- if (!source) {
632
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'bootloader payload is empty');
633
- }
634
- try {
635
- return await updateBootloaderFromSource(typedCall, postMessage, device, source);
636
- } finally {
637
- await source.close();
638
- }
639
- };
640
-
641
- export const updateBootloaderFromSource = async (
642
- typedCall: TypedCall,
643
- postMessage: (message: CoreMessage) => void,
644
- device: Device,
645
- source: FirmwareByteSource
527
+ source: ArrayBuffer
646
528
  ) => {
647
529
  postProgressTip(device, 'UpdateBootloader', postMessage);
648
530
  postProgressMessage(device, Math.floor(0), 'installingFirmware', postMessage);
649
- await updateResourceFromSource(typedCall, 'bootloader.bin', source, () => {
531
+ await updateResource(typedCall, 'bootloader.bin', source, () => {
650
532
  postProcessingMessage('resource', postMessage);
651
533
  });
652
534
  postProgressMessage(device, Math.floor(100), 'installingFirmware', postMessage);
@@ -31,19 +31,21 @@ export type ProtocolV2FileWriteOptions = {
31
31
  uiPercentage?: number;
32
32
  timeoutMs?: number;
33
33
  maxChunkRetries?: number;
34
+ paceMs?: number;
34
35
  throwIfAborted?: () => void;
35
36
  onProgress?: (progress: ProtocolV2FileWriteProgress) => void;
36
37
  };
37
38
 
38
39
  const MIN_FILE_CHUNK_SIZE = 64;
39
40
 
40
- function isRetryableFileWriteTimeout(error: unknown) {
41
+ export function isProtocolV2ResponseTimeout(error: unknown) {
41
42
  if (!error || typeof error !== 'object') return false;
42
- const candidate = error as { errorCode?: number; code?: number; message?: string };
43
+ const candidate = error as { errorCode?: number; code?: number | string; message?: string };
43
44
  const code = candidate.errorCode ?? candidate.code;
44
45
  return (
45
46
  code === HardwareErrorCode.BleTimeoutError ||
46
- /Lowlevel response timeout/i.test(candidate.message ?? '')
47
+ code === 'response-timeout' ||
48
+ /(?:BLE|Lowlevel|Protocol V2) response timeout/i.test(candidate.message ?? '')
47
49
  );
48
50
  }
49
51
 
@@ -147,7 +149,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
147
149
  );
148
150
  isWritePending = false;
149
151
  } catch (error) {
150
- if (retryCount >= maxChunkRetries || !isRetryableFileWriteTimeout(error)) throw error;
152
+ if (retryCount >= maxChunkRetries || !isProtocolV2ResponseTimeout(error)) throw error;
151
153
  retryCount += 1;
152
154
  options.throwIfAborted?.();
153
155
  }
@@ -186,6 +188,11 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
186
188
  elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
187
189
  elapsedMs,
188
190
  });
191
+ if (options.paceMs && options.paceMs > 0) {
192
+ await new Promise(resolve => {
193
+ setTimeout(resolve, options.paceMs);
194
+ });
195
+ }
189
196
  }
190
197
 
191
198
  return {
package/src/api/index.ts CHANGED
@@ -49,6 +49,7 @@ export { default as promptWebDeviceAccess } from './PromptWebDeviceAccess';
49
49
  export { default as deviceReboot } from './protocol-v2/DeviceReboot';
50
50
  export { default as deviceGetOnboardingStatus } from './protocol-v2/DeviceGetOnboardingStatus';
51
51
  export { default as deviceUploadWallpaper } from './protocol-v2/DeviceUploadWallpaper';
52
+ export { default as deviceUploadNft } from './protocol-v2/DeviceUploadNft';
52
53
  export { default as uploadPortfolio } from './UploadPortfolio';
53
54
 
54
55
  export { default as cipherKeyValue } from './CipherKeyValue';
@@ -0,0 +1,189 @@
1
+ import { ERRORS, HardwareErrorCode, createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
+
3
+ import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
4
+ import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
5
+ import {
6
+ PRO2_NFT_DEFAULT_CHUNK_SIZE,
7
+ PRO2_NFT_DEFAULT_PACE_MS,
8
+ PRO2_NFT_DEFAULT_TIMEOUT_MS,
9
+ PRO2_NFT_DIRECTORY,
10
+ PRO2_NFT_MAX_CHUNK_SIZE,
11
+ PRO2_NFT_MAX_ITEMS,
12
+ PRO2_NFT_MIN_CHUNK_SIZE,
13
+ type Pro2NftBundle,
14
+ type Pro2NftImage,
15
+ buildPro2NftBundle,
16
+ getCompletePro2NftBasenames,
17
+ } from '../../utils/pro2Nft';
18
+ import { BaseMethod } from '../BaseMethod';
19
+ import { invalidParameter } from '../helpers/filesystemValidation';
20
+ import { isProtocolV2ResponseTimeout, writeProtocolV2File } from '../helpers/protocolV2FileWrite';
21
+
22
+ export type DeviceUploadNftParams = {
23
+ image: Pro2NftImage;
24
+ thumbnail: Pro2NftImage;
25
+ title: string;
26
+ subtitle: string;
27
+ timestampMs?: number;
28
+ chunkSize?: number;
29
+ paceMs?: number;
30
+ timeoutMs?: number;
31
+ };
32
+
33
+ export type DeviceUploadNftResponse = {
34
+ basename: string;
35
+ imagePath: string;
36
+ thumbnailPath: string;
37
+ metadataPath: string;
38
+ totalSize: number;
39
+ nftUpdated: true;
40
+ message?: string;
41
+ };
42
+
43
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
44
+ const FILESYSTEM_DIR_LIST_MESSAGE_TYPE = 60808;
45
+ const NFT_UPDATE_MESSAGE_TYPE = 61500;
46
+
47
+ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
48
+ private bundle?: Pro2NftBundle;
49
+
50
+ getSupportedProtocols() {
51
+ return ['V2'] as const;
52
+ }
53
+
54
+ init() {
55
+ const {
56
+ image,
57
+ thumbnail,
58
+ title,
59
+ subtitle,
60
+ timestampMs = Date.now(),
61
+ chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE,
62
+ paceMs = PRO2_NFT_DEFAULT_PACE_MS,
63
+ timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS,
64
+ } = this.payload;
65
+ if (
66
+ !Number.isInteger(chunkSize) ||
67
+ chunkSize < PRO2_NFT_MIN_CHUNK_SIZE ||
68
+ chunkSize > PRO2_NFT_MAX_CHUNK_SIZE
69
+ ) {
70
+ throw invalidParameter(
71
+ `Parameter [chunkSize] must be an integer between ${PRO2_NFT_MIN_CHUNK_SIZE} and ${PRO2_NFT_MAX_CHUNK_SIZE}.`
72
+ );
73
+ }
74
+ if (!Number.isInteger(paceMs) || paceMs < 0) {
75
+ throw invalidParameter('Parameter [paceMs] must be a non-negative integer.');
76
+ }
77
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
78
+ throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
79
+ }
80
+
81
+ this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
82
+ this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
83
+ this.unlockPolicy = 'none';
84
+ this.skipForceUpdateCheck = true;
85
+ this.useDevicePassphraseState = false;
86
+ }
87
+
88
+ private async assertCapabilities() {
89
+ const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
90
+ const hasFileWrite = supportsProtocolV2Message(
91
+ protocolInfo,
92
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE
93
+ );
94
+ const hasDirList = supportsProtocolV2Message(protocolInfo, FILESYSTEM_DIR_LIST_MESSAGE_TYPE);
95
+ const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
96
+ if (!hasFileWrite || !hasDirList || !hasNftUpdate) {
97
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
98
+ }
99
+ }
100
+
101
+ private async assertStorageCapacity(basename: string) {
102
+ const { message } = await this.device.commands.typedCall(
103
+ 'FilesystemDirList',
104
+ 'FilesystemDir',
105
+ { path: PRO2_NFT_DIRECTORY, depth: 1 },
106
+ { timeoutMs: this.params.timeoutMs }
107
+ );
108
+ const existingBasenames = getCompletePro2NftBasenames(message.child_files);
109
+ if (existingBasenames.size >= PRO2_NFT_MAX_ITEMS && !existingBasenames.has(basename)) {
110
+ throw ERRORS.TypedError(HardwareErrorCode.NftStorageLimitReached, undefined, {
111
+ count: existingBasenames.size,
112
+ limit: PRO2_NFT_MAX_ITEMS,
113
+ });
114
+ }
115
+ }
116
+
117
+ private async updateNft(basename: string) {
118
+ const params = { file_name_no_ext: basename };
119
+ const options = { timeoutMs: this.params.timeoutMs };
120
+ try {
121
+ return await this.device.commands.typedCall('NftUpdate', 'Success', params, options);
122
+ } catch (error) {
123
+ if (!isProtocolV2ResponseTimeout(error)) {
124
+ throw error;
125
+ }
126
+ this.throwIfAborted();
127
+ return this.device.commands.typedCall('NftUpdate', 'Success', params, options);
128
+ }
129
+ }
130
+
131
+ async run(): Promise<DeviceUploadNftResponse> {
132
+ const { bundle } = this;
133
+ if (!bundle) throw invalidParameter('NFT data has not been initialized.');
134
+
135
+ await this.assertCapabilities();
136
+ this.throwIfAborted();
137
+ await this.assertStorageCapacity(bundle.basename);
138
+ this.throwIfAborted();
139
+
140
+ const files = [
141
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
142
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
143
+ { path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
144
+ ];
145
+ const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
146
+ let transferredBeforeFile = 0;
147
+
148
+ for (const file of files) {
149
+ const transferredAtFileStart = transferredBeforeFile;
150
+ await writeProtocolV2File({
151
+ commands: this.device.commands,
152
+ path: file.path,
153
+ data: file.data,
154
+ totalSize: file.data.byteLength,
155
+ chunkSize: this.params.chunkSize,
156
+ timeoutMs: this.params.timeoutMs,
157
+ paceMs: this.params.paceMs,
158
+ overwrite: true,
159
+ append: false,
160
+ throwIfAborted: () => this.throwIfAborted(),
161
+ onProgress: progress => {
162
+ if (typeof this.postMessage !== 'function') return;
163
+ const transferredBytes = transferredAtFileStart + progress.transferredBytes;
164
+ this.postMessage(
165
+ createUiMessage(UI_REQUEST.DEVICE_PROGRESS, {
166
+ ...progress,
167
+ progress: Math.floor((transferredBytes / totalSize) * 100),
168
+ transferredBytes,
169
+ totalBytes: totalSize,
170
+ })
171
+ );
172
+ },
173
+ });
174
+ transferredBeforeFile += file.data.byteLength;
175
+ }
176
+
177
+ this.throwIfAborted();
178
+ const response = await this.updateNft(bundle.basename);
179
+ return {
180
+ basename: bundle.basename,
181
+ imagePath: files[0].path,
182
+ thumbnailPath: files[1].path,
183
+ metadataPath: files[2].path,
184
+ totalSize,
185
+ nftUpdated: true,
186
+ message: response.message?.message,
187
+ };
188
+ }
189
+ }
@@ -13,7 +13,9 @@ export default class ProtocolInfoRequest extends BaseMethod {
13
13
  }
14
14
 
15
15
  async run() {
16
- const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {});
16
+ const res = await this.device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
17
+ eventless_wallet_session: true,
18
+ });
17
19
  return Promise.resolve(res.message);
18
20
  }
19
21
  }
@@ -4,6 +4,8 @@ import type { Device, DeviceEvents } from '../device/Device';
4
4
 
5
5
  export type HardwareUiEventHandlers = {
6
6
  pin: (...event: DeviceEvents[typeof DEVICE.PIN]) => void;
7
+ pinOnDevice: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE]) => void;
8
+ pinOnDeviceComplete: (...event: DeviceEvents[typeof DEVICE.PIN_ON_DEVICE_COMPLETE]) => void;
7
9
  button: (...event: DeviceEvents[typeof DEVICE.BUTTON]) => void;
8
10
  passphrase: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE]) => void;
9
11
  passphraseOnDevice: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE_ON_DEVICE]) => void;
@@ -15,6 +17,8 @@ export function registerHardwareUiEventListeners(
15
17
  handlers: HardwareUiEventHandlers
16
18
  ) {
17
19
  device.on(DEVICE.PIN, handlers.pin);
20
+ device.on(DEVICE.PIN_ON_DEVICE, handlers.pinOnDevice);
21
+ device.on(DEVICE.PIN_ON_DEVICE_COMPLETE, handlers.pinOnDeviceComplete);
18
22
  device.on(DEVICE.BUTTON, handlers.button);
19
23
  device.on(DEVICE.PASSPHRASE, handlers.passphrase);
20
24
  device.on(DEVICE.PASSPHRASE_ON_DEVICE, handlers.passphraseOnDevice);