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

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 (121) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -28
  2. package/__tests__/connectSettings.test.ts +45 -5
  3. package/__tests__/device-initialize-timeout.test.ts +56 -0
  4. package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
  5. package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
  6. package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
  7. package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
  8. package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
  9. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
  10. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
  11. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
  12. package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
  13. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
  14. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
  15. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +63 -0
  16. package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
  17. package/__tests__/protocol-v2-firmware-targets.test.ts +1 -33
  18. package/__tests__/protocol-v2-resources.test.ts +6 -9
  19. package/__tests__/protocol-v2.test.ts +383 -174
  20. package/__tests__/protocolV2FileWrite.test.ts +7 -48
  21. package/dist/api/CheckAllFirmwareRelease.d.ts +1 -2
  22. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  23. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV2.d.ts +10 -0
  25. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  26. package/dist/api/FirmwareUpdateV3.d.ts +7 -3
  27. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  28. package/dist/api/FirmwareUpdateV4.d.ts +25 -6
  29. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  30. package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
  31. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  32. package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
  33. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
  34. package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
  35. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
  36. package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
  37. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
  38. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
  39. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  40. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
  41. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
  42. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
  43. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
  44. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
  45. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
  46. package/dist/api/firmware/getBinary.d.ts +7 -0
  47. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  48. package/dist/api/firmware/updateBootloader.d.ts +2 -0
  49. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  50. package/dist/api/firmware/uploadFirmware.d.ts +9 -1
  51. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  52. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  53. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  54. package/dist/data-manager/DataManager.d.ts +1 -0
  55. package/dist/data-manager/DataManager.d.ts.map +1 -1
  56. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  57. package/dist/device/Device.d.ts +1 -0
  58. package/dist/device/Device.d.ts.map +1 -1
  59. package/dist/index.d.ts +180 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +3038 -1041
  62. package/dist/inject.d.ts.map +1 -1
  63. package/dist/lowLevelInject.d.ts.map +1 -1
  64. package/dist/protocols/protocol-v2/resources.d.ts +1 -1
  65. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  66. package/dist/topLevelInject.d.ts.map +1 -1
  67. package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
  68. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  69. package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
  70. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  71. package/dist/types/api/export.d.ts +4 -1
  72. package/dist/types/api/export.d.ts.map +1 -1
  73. package/dist/types/api/firmwareUpdate.d.ts +68 -0
  74. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  75. package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
  76. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
  77. package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
  78. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
  79. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
  80. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
  81. package/dist/types/api/index.d.ts +9 -1
  82. package/dist/types/api/index.d.ts.map +1 -1
  83. package/dist/types/settings.d.ts +14 -1
  84. package/dist/types/settings.d.ts.map +1 -1
  85. package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
  86. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  87. package/package.json +4 -4
  88. package/src/api/CheckAllFirmwareRelease.ts +61 -25
  89. package/src/api/FirmwareUpdate.ts +13 -1
  90. package/src/api/FirmwareUpdateV2.ts +296 -121
  91. package/src/api/FirmwareUpdateV3.ts +263 -57
  92. package/src/api/FirmwareUpdateV4.ts +865 -287
  93. package/src/api/device/DeviceUpdateBootloader.ts +125 -6
  94. package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
  95. package/src/api/firmware/FirmwareHostBinding.ts +100 -0
  96. package/src/api/firmware/FirmwarePreparationError.ts +12 -0
  97. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
  98. package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
  99. package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
  100. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
  101. package/src/api/firmware/updateBootloader.ts +19 -4
  102. package/src/api/firmware/uploadFirmware.ts +140 -22
  103. package/src/api/helpers/protocolV2FileWrite.ts +4 -54
  104. package/src/data-manager/DataManager.ts +62 -54
  105. package/src/data-manager/connectSettings.ts +11 -0
  106. package/src/device/Device.ts +6 -1
  107. package/src/index.ts +5 -0
  108. package/src/inject.ts +22 -2
  109. package/src/lowLevelInject.ts +5 -1
  110. package/src/protocols/protocol-v2/resources.ts +1 -5
  111. package/src/topLevelInject.ts +5 -1
  112. package/src/types/api/checkAllFirmwareRelease.ts +4 -0
  113. package/src/types/api/deviceUpdateBootloader.ts +6 -0
  114. package/src/types/api/export.ts +18 -0
  115. package/src/types/api/firmwareUpdate.ts +75 -0
  116. package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
  117. package/src/types/api/firmwareUpdatePlan.ts +38 -0
  118. package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
  119. package/src/types/api/index.ts +14 -0
  120. package/src/types/settings.ts +14 -2
  121. package/src/utils/deviceFeaturesCompat.ts +6 -0
@@ -10,6 +10,7 @@ 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';
13
14
 
14
15
  import type { KnownDevice } from '../../types';
15
16
  import type { TypedCall, TypedResponseMessage } from '../../device/DeviceCommands';
@@ -17,6 +18,7 @@ import type { PROTO } from '../../constants';
17
18
  import type { CoreMessage, IFirmwareUpdateProgressType } from '../../events';
18
19
  import type { Success } from '@onekeyfe/hd-transport';
19
20
  import type { Device } from '../../device/Device';
21
+ import type { FirmwareByteSource } from './FirmwareArtifactSource';
20
22
 
21
23
  const NEW_BOOT_UPRATE_FIRMWARE_VERSION = '2.4.5';
22
24
  const SESSION_ERROR = 'session not found';
@@ -88,17 +90,20 @@ export const waitBleInstall = async (updateType: string) => {
88
90
  }
89
91
  };
90
92
 
91
- export const uploadFirmware = async (
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 (
92
101
  updateType: 'firmware' | 'ble',
93
102
  typedCall: TypedCall,
94
103
  postMessage: (message: CoreMessage) => void,
95
104
  device: Device,
96
- {
97
- payload,
98
- rebootOnSuccess,
99
- }: PROTO.FirmwareUpload & {
100
- rebootOnSuccess?: boolean;
101
- },
105
+ source: FirmwareByteSource,
106
+ rebootOnSuccess?: boolean,
102
107
  isUpdateBootloader?: boolean
103
108
  ) => {
104
109
  const deviceType = device.getCurrentDeviceType();
@@ -119,20 +124,20 @@ export const uploadFirmware = async (
119
124
  if (supportUpgradeFileHeader) {
120
125
  // Extract and validate firmware header (first 1KB)
121
126
  const HEADER_SIZE = 1024;
122
- if (payload.byteLength < HEADER_SIZE) {
127
+ if (source.size < HEADER_SIZE) {
123
128
  throw ERRORS.TypedError(
124
129
  HardwareErrorCode.RuntimeError,
125
- `firmware payload too small: ${payload.byteLength} bytes, expected at least ${HEADER_SIZE} bytes`
130
+ `firmware payload too small: ${source.size} bytes, expected at least ${HEADER_SIZE} bytes`
126
131
  );
127
132
  }
128
133
 
129
134
  Log.debug('Uploading firmware header:', {
130
135
  size: HEADER_SIZE,
131
- totalSize: payload.byteLength,
136
+ totalSize: source.size,
132
137
  });
133
138
  postProgressTip(device, 'UploadingFirmwareHeader', postMessage);
134
139
 
135
- const header = new Uint8Array(payload.slice(0, HEADER_SIZE));
140
+ const header = new Uint8Array(await source.readAt(0, HEADER_SIZE));
136
141
 
137
142
  try {
138
143
  const headerRes = await typedCall('UpgradeFileHeader', 'Success', {
@@ -169,6 +174,9 @@ export const uploadFirmware = async (
169
174
  postProgressMessage(device, 0, 'installingFirmware', postMessage);
170
175
  let updateResponse: TypedResponseMessage<'Success'>;
171
176
  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);
172
180
  updateResponse = await typedCall('FirmwareUpload', 'Success', {
173
181
  payload,
174
182
  });
@@ -200,9 +208,7 @@ export const uploadFirmware = async (
200
208
  updateType,
201
209
  postMessage,
202
210
  device,
203
- {
204
- payload,
205
- },
211
+ source,
206
212
  rebootOnSuccess
207
213
  );
208
214
  return response.message;
@@ -211,7 +217,7 @@ export const uploadFirmware = async (
211
217
 
212
218
  postConfirmationMessage(device);
213
219
  postProgressTip(device, 'ConfirmOnDevice', postMessage);
214
- const length = payload.byteLength;
220
+ const length = source.size;
215
221
 
216
222
  let response = await typedCall('FirmwareErase', ['FirmwareRequest', 'Success'], { length });
217
223
  postProgressTip(device, 'FirmwareEraseSuccess', postMessage);
@@ -220,7 +226,7 @@ export const uploadFirmware = async (
220
226
  const start = response.message.offset!;
221
227
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
222
228
  const end = response.message.offset! + response.message.length!;
223
- const chunk = payload.slice(start, end);
229
+ const chunk = await source.readAt(start, end - start);
224
230
 
225
231
  if (start > 0) {
226
232
  postProgressMessage(
@@ -249,11 +255,45 @@ export const uploadFirmware = async (
249
255
  throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'uploadFirmware: unknown device model');
250
256
  };
251
257
 
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
+
252
292
  const newTouchUpdateProcess = async (
253
293
  updateType: 'firmware' | 'ble',
254
294
  postMessage: (message: CoreMessage) => void,
255
295
  device: Device,
256
- { payload }: PROTO.FirmwareUpload,
296
+ source: FirmwareByteSource,
257
297
  rebootOnSuccess = true
258
298
  ) => {
259
299
  let typedCall = device.getCommands().typedCall.bind(device.getCommands());
@@ -263,13 +303,13 @@ const newTouchUpdateProcess = async (
263
303
  const env = DataManager.getSettings('env');
264
304
  const perPackageSize = DataManager.isBleConnect(env) ? 16 : 128;
265
305
  const chunkSize = 1024 * perPackageSize;
266
- const totalChunks = Math.ceil(payload.byteLength / chunkSize);
306
+ const totalChunks = Math.ceil(source.size / chunkSize);
267
307
  let offset = 0;
268
308
  for (let i = 0; i < totalChunks; i++) {
269
309
  const chunkStart = i * chunkSize;
270
- const chunkEnd = Math.min(chunkStart + chunkSize, payload.byteLength);
310
+ const chunkEnd = Math.min(chunkStart + chunkSize, source.size);
271
311
  const chunkLength = chunkEnd - chunkStart;
272
- const chunk = payload.slice(chunkStart, chunkEnd);
312
+ const chunk = await source.readAt(chunkStart, chunkLength);
273
313
  const overwrite = i === 0;
274
314
  const progress = Math.round(((i + 1) / totalChunks) * 100);
275
315
  const writeRes = await emmcFileWriteWithRetry(
@@ -503,6 +543,67 @@ export const updateResource = async (
503
543
  return processResourceRequest(typedCall, res, data);
504
544
  };
505
545
 
546
+ export const updateResourceFromSource = async (
547
+ typedCall: TypedCall,
548
+ fileName: string,
549
+ source: FirmwareByteSource,
550
+ onConfirmAfter?: () => void
551
+ ) => {
552
+ const initialLength = Math.min(INIT_DATA_CHUNK_SIZE, source.size);
553
+ const initialChunk = new Uint8Array(await source.readAt(0, initialLength));
554
+ let response = await typedCall('ResourceUpdate', ['ResourceRequest', 'Success'], {
555
+ file_name: fileName,
556
+ data_length: source.size,
557
+ initial_data_chunk: bytesToHex(initialChunk),
558
+ hash: bytesToHex(blake2s(initialChunk)),
559
+ });
560
+ onConfirmAfter?.();
561
+
562
+ while (response.type !== 'Success') {
563
+ const { offset, data_length: dataLength } = response.message;
564
+ if (
565
+ !Number.isSafeInteger(offset) ||
566
+ offset === undefined ||
567
+ offset < 0 ||
568
+ !Number.isSafeInteger(dataLength) ||
569
+ dataLength === undefined ||
570
+ dataLength <= 0 ||
571
+ offset + dataLength > source.size
572
+ ) {
573
+ throw ERRORS.TypedError(
574
+ HardwareErrorCode.RuntimeError,
575
+ 'Device requested an invalid firmware resource range'
576
+ );
577
+ }
578
+ const chunk = new Uint8Array(await source.readAt(offset, dataLength));
579
+ response = await typedCall('ResourceAck', ['ResourceRequest', 'Success'], {
580
+ data_chunk: bytesToHex(chunk),
581
+ hash: bytesToHex(blake2s(chunk)),
582
+ });
583
+ }
584
+ return response.message;
585
+ };
586
+
587
+ export const updateResourcesFromSources = async (
588
+ typedCall: TypedCall,
589
+ postMessage: (message: CoreMessage) => void,
590
+ device: Device,
591
+ entries: ReadonlyArray<{ entryName: string; source: FirmwareByteSource }>
592
+ ) => {
593
+ postProgressTip(device, 'UpdateSysResource', postMessage);
594
+ for (const [index, entry] of entries.entries()) {
595
+ await updateResourceFromSource(typedCall, entry.entryName, entry.source);
596
+ postProgressMessage(
597
+ device,
598
+ Math.floor(((index + 1) / entries.length) * 100),
599
+ 'installingFirmware',
600
+ postMessage
601
+ );
602
+ }
603
+ postProgressTip(device, 'UpdateSysResourceSuccess', postMessage);
604
+ return true;
605
+ };
606
+
506
607
  export const updateResources = async (
507
608
  typedCall: TypedCall,
508
609
  postMessage: (message: CoreMessage) => void,
@@ -537,11 +638,28 @@ export const updateBootloader = async (
537
638
  typedCall: TypedCall,
538
639
  postMessage: (message: CoreMessage) => void,
539
640
  device: Device,
540
- source: ArrayBuffer
641
+ data: ArrayBuffer
642
+ ) => {
643
+ const source = await openFirmwareByteSource({ binary: data });
644
+ if (!source) {
645
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'bootloader payload is empty');
646
+ }
647
+ try {
648
+ return await updateBootloaderFromSource(typedCall, postMessage, device, source);
649
+ } finally {
650
+ await source.close();
651
+ }
652
+ };
653
+
654
+ export const updateBootloaderFromSource = async (
655
+ typedCall: TypedCall,
656
+ postMessage: (message: CoreMessage) => void,
657
+ device: Device,
658
+ source: FirmwareByteSource
541
659
  ) => {
542
660
  postProgressTip(device, 'UpdateBootloader', postMessage);
543
661
  postProgressMessage(device, Math.floor(0), 'installingFirmware', postMessage);
544
- await updateResource(typedCall, 'bootloader.bin', source, () => {
662
+ await updateResourceFromSource(typedCall, 'bootloader.bin', source, () => {
545
663
  postProcessingMessage('resource', postMessage);
546
664
  });
547
665
  postProgressMessage(device, Math.floor(100), 'installingFirmware', postMessage);
@@ -9,7 +9,7 @@ import { LoggerNames, getLogger } from '../../utils/logger';
9
9
 
10
10
  import type { DeviceCommands } from '../../device/DeviceCommands';
11
11
 
12
- const Log = getLogger(LoggerNames.Core);
12
+ const Log = getLogger(LoggerNames.Method);
13
13
 
14
14
  export type ProtocolV2FileWriteData = ArrayBuffer | Uint8Array | Blob | string;
15
15
 
@@ -69,36 +69,22 @@ function getFileTransferTransport() {
69
69
  function logFileTransferMetrics({
70
70
  transport,
71
71
  status,
72
- path,
73
72
  transferredBytes,
74
73
  totalBytes,
75
74
  elapsedMs,
76
75
  rateBytesPerSecond,
77
- hostWriteElapsedMs,
78
- responseWaitElapsedMs,
79
- measuredAttempts,
80
76
  }: {
81
77
  transport: string;
82
78
  status: 'progress' | 'completed' | 'failed';
83
- path: string;
84
79
  transferredBytes: number;
85
80
  totalBytes: number;
86
81
  elapsedMs: number;
87
82
  rateBytesPerSecond: number;
88
- hostWriteElapsedMs: number;
89
- responseWaitElapsedMs: number;
90
- measuredAttempts: number;
91
83
  }) {
92
- const segmentedMetrics =
93
- measuredAttempts > 0
94
- ? ` hostWriteTotal=${(hostWriteElapsedMs / 1000).toFixed(2)}s responseWaitTotal=${(
95
- responseWaitElapsedMs / 1000
96
- ).toFixed(2)}s measuredAttempts=${measuredAttempts}`
97
- : '';
98
84
  Log.log(
99
- `[FileWrite] metrics transport=${transport} status=${status} path=${path} bytes=${transferredBytes}/${totalBytes} elapsed=${(
85
+ `[FileWrite] metrics transport=${transport} status=${status} bytes=${transferredBytes}/${totalBytes} elapsed=${(
100
86
  elapsedMs / 1000
101
- ).toFixed(2)}s speed=${formatFileTransferRate(rateBytesPerSecond)} KiB/s${segmentedMetrics}`
87
+ ).toFixed(2)}s speed=${formatFileTransferRate(rateBytesPerSecond)} KiB/s`
102
88
  );
103
89
  }
104
90
 
@@ -191,13 +177,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
191
177
  let lastConfirmedAt = startTime;
192
178
  let logWindowStartedAt = startTime;
193
179
  let logWindowStartedBytes = 0;
194
- let hostWriteElapsedMs = 0;
195
- let responseWaitElapsedMs = 0;
196
- let measuredAttempts = 0;
197
180
  const transport = getFileTransferTransport();
198
- Log.log(
199
- `[FileWrite] started transport=${transport} path=${options.path} bytes=${dataLength} offset=${startOffset} chunk=${chunkSize}`
200
- );
201
181
 
202
182
  try {
203
183
  while (written < dataLength) {
@@ -223,25 +203,13 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
223
203
  let response;
224
204
  let isWritePending = true;
225
205
  while (isWritePending) {
226
- let currentHostWriteElapsedMs: number | undefined;
227
- let responseWaitStartedAt: number | undefined;
228
206
  try {
229
207
  const callOptions =
230
208
  options.writeWithResponse === undefined
231
- ? {
232
- timeoutMs: options.timeoutMs,
233
- onWriteCompleted: ({ elapsedMs }: { elapsedMs: number }) => {
234
- currentHostWriteElapsedMs = elapsedMs;
235
- responseWaitStartedAt = Date.now();
236
- },
237
- }
209
+ ? { timeoutMs: options.timeoutMs }
238
210
  : {
239
211
  ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
240
212
  writeWithResponse: options.writeWithResponse,
241
- onWriteCompleted: ({ elapsedMs }: { elapsedMs: number }) => {
242
- currentHostWriteElapsedMs = elapsedMs;
243
- responseWaitStartedAt = Date.now();
244
- },
245
213
  };
246
214
  response = await options.commands.typedCall(
247
215
  'FilesystemFileWrite',
@@ -254,12 +222,6 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
254
222
  if (retryCount >= maxChunkRetries || !isProtocolV2ResponseTimeout(error)) throw error;
255
223
  retryCount += 1;
256
224
  options.throwIfAborted?.();
257
- } finally {
258
- if (currentHostWriteElapsedMs !== undefined && responseWaitStartedAt !== undefined) {
259
- hostWriteElapsedMs += currentHostWriteElapsedMs;
260
- responseWaitElapsedMs += Math.max(Date.now() - responseWaitStartedAt, 0);
261
- measuredAttempts += 1;
262
- }
263
225
  }
264
226
  }
265
227
  if (!response) {
@@ -323,14 +285,10 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
323
285
  logFileTransferMetrics({
324
286
  transport,
325
287
  status: 'progress',
326
- path: options.path,
327
288
  transferredBytes,
328
289
  totalBytes: dataLength,
329
290
  elapsedMs,
330
291
  rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
331
- hostWriteElapsedMs,
332
- responseWaitElapsedMs,
333
- measuredAttempts,
334
292
  });
335
293
  logWindowStartedAt = now;
336
294
  logWindowStartedBytes = transferredBytes;
@@ -349,14 +307,10 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
349
307
  logFileTransferMetrics({
350
308
  transport,
351
309
  status: 'failed',
352
- path: options.path,
353
310
  transferredBytes: written,
354
311
  totalBytes: dataLength,
355
312
  elapsedMs,
356
313
  rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
357
- hostWriteElapsedMs,
358
- responseWaitElapsedMs,
359
- measuredAttempts,
360
314
  });
361
315
  throw error;
362
316
  }
@@ -367,14 +321,10 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
367
321
  logFileTransferMetrics({
368
322
  transport,
369
323
  status: 'completed',
370
- path: options.path,
371
324
  transferredBytes: written,
372
325
  totalBytes: dataLength,
373
326
  elapsedMs,
374
327
  rateBytesPerSecond: getAverageFileTransferRate(logWindowBytes, logWindowElapsedMs),
375
- hostWriteElapsedMs,
376
- responseWaitElapsedMs,
377
- measuredAttempts,
378
328
  });
379
329
 
380
330
  return {
@@ -408,7 +408,20 @@ export default class DataManager {
408
408
  static async load(settings: ConnectSettings): Promise<boolean> {
409
409
  this.settings = settings;
410
410
  this.protocolV2ResourcesConfigError = undefined;
411
- if (!settings.fetchConfig) {
411
+ const manifestMode =
412
+ settings.firmwareManifestMode ?? (settings.fetchConfig ? 'sdk-managed' : undefined);
413
+ if (settings.preloadedConfig) {
414
+ this.applyRemoteConfig(settings.preloadedConfig);
415
+ Log.log('[DataConfig] Preloaded firmware config loaded');
416
+ return true;
417
+ }
418
+ if (manifestMode === 'external-only') {
419
+ Log.warn(
420
+ '[DataConfig] External firmware config is unavailable; SDK-managed networking remains disabled'
421
+ );
422
+ return false;
423
+ }
424
+ if (manifestMode !== 'sdk-managed') {
412
425
  return false;
413
426
  }
414
427
 
@@ -419,26 +432,19 @@ export default class DataManager {
419
432
  const urlWithCache = `${url}?noCache=${getTimeStamp()}`;
420
433
  let data: RemoteConfigResponse | null = null;
421
434
  let fetchMethod: 'configFetcher' | 'axios' | 'none' = 'none';
422
-
423
- // 1. Try custom configFetcher first (client-side IP direct connection support)
424
435
  if (settings.configFetcher) {
425
- Log.debug('[DataConfig] Trying configFetcher (client-side fetcher)...');
436
+ Log.debug('[DataConfig] Trying client configFetcher...');
426
437
  try {
427
438
  data = await settings.configFetcher(urlWithCache);
428
439
  if (data) {
429
440
  fetchMethod = 'configFetcher';
430
- Log.log('[DataConfig] ConfigFetcher success');
431
- } else {
432
- Log.debug('[DataConfig] ConfigFetcher returned null, will fallback to axios');
433
441
  }
434
442
  } catch (e) {
435
- Log.warn('[DataConfig] ConfigFetcher error, will fallback to axios:', e);
443
+ Log.warn('[DataConfig] Client configFetcher failed:', e);
436
444
  }
437
445
  }
438
-
439
- // 2. Fallback to default axios request
440
446
  if (!data) {
441
- Log.debug('[DataConfig] Trying axios (SDK default fetcher)...');
447
+ Log.debug('[DataConfig] Trying SDK-managed config request...');
442
448
  try {
443
449
  const response = await axios.get<RemoteConfigResponse>(urlWithCache, {
444
450
  // because of iframe timeout is 10000
@@ -446,61 +452,63 @@ export default class DataManager {
446
452
  });
447
453
  data = response.data;
448
454
  fetchMethod = 'axios';
449
- Log.log('[DataConfig] Axios fetch success');
455
+ Log.log('[DataConfig] SDK-managed config request succeeded');
450
456
  } catch (e) {
451
- Log.warn('[DataConfig] Axios fetch error:', e);
457
+ Log.warn('[DataConfig] SDK-managed config request failed:', e);
452
458
  }
453
459
  }
454
460
 
455
- // 3. Apply config if available
456
461
  if (data) {
457
- let pro2Resources: IProtocolV2Resources | undefined;
458
- let neoResources: IProtocolV2Resources | undefined;
459
- this.protocolV2ResourcesConfigError = undefined;
460
- try {
461
- pro2Resources = parseProtocolV2Resources(
462
- (data.pro2 as { resources?: unknown } | undefined)?.resources
463
- );
464
- neoResources = parseProtocolV2Resources(
465
- (data.neo as { resources?: unknown } | undefined)?.resources
466
- );
467
- } catch (error) {
468
- // Firmware resource metadata is not required for base communication. If the
469
- // remote config is temporarily incomplete, disable this resource update only.
470
- this.protocolV2ResourcesConfigError =
471
- error instanceof Error ? error : new Error(String(error));
472
- Log.warn('[DataConfig] Ignoring invalid Pro2 resources config:', error);
473
- }
474
- const enrichedPro2Config = this.enrichFirmwareReleaseInfo(data.pro2);
475
- const enrichedNeoConfig = this.enrichFirmwareReleaseInfo(data.neo);
476
- const { resources: _unvalidatedResources, ...pro2Config } = enrichedPro2Config;
477
- const { resources: _unvalidatedNeoResources, ...neoConfig } = enrichedNeoConfig;
462
+ this.applyRemoteConfig(data);
478
463
  Log.log(`[DataConfig] Config loaded successfully via [${fetchMethod}]`);
479
- this.deviceMap = {
480
- [EDeviceType.Classic]: this.enrichFirmwareReleaseInfo(data.classic),
481
- [EDeviceType.Classic1s]: this.enrichFirmwareReleaseInfo(data.classic1s),
482
- [EDeviceType.ClassicPure]: this.enrichFirmwareReleaseInfo(data.classicpure),
483
- [EDeviceType.Mini]: this.enrichFirmwareReleaseInfo(data.mini),
484
- [EDeviceType.Touch]: this.enrichFirmwareReleaseInfo(data.touch),
485
- [EDeviceType.Pro]: this.enrichFirmwareReleaseInfo(data.pro),
486
- [EDeviceType.Pro2]: {
487
- ...pro2Config,
488
- ...(pro2Resources ? { resources: pro2Resources } : undefined),
489
- },
490
- [EDeviceType.Neo]: {
491
- ...neoConfig,
492
- ...(neoResources ? { resources: neoResources } : undefined),
493
- },
494
- };
495
- this.assets = {
496
- bridge: data.bridge,
497
- };
498
464
  return true;
499
465
  }
500
466
  Log.warn('[DataConfig] All fetch methods failed, using built-in default config');
501
467
  return false;
502
468
  }
503
469
 
470
+ private static applyRemoteConfig(data: RemoteConfigResponse) {
471
+ let pro2Resources: IProtocolV2Resources | undefined;
472
+ let neoResources: IProtocolV2Resources | undefined;
473
+ try {
474
+ pro2Resources = parseProtocolV2Resources(
475
+ (data.pro2 as { resources?: unknown } | undefined)?.resources
476
+ );
477
+ neoResources = parseProtocolV2Resources(
478
+ (data.neo as { resources?: unknown } | undefined)?.resources
479
+ );
480
+ } catch (error) {
481
+ // Firmware resource metadata is not required for base communication. If the
482
+ // remote config is temporarily incomplete, disable this resource update only.
483
+ this.protocolV2ResourcesConfigError =
484
+ error instanceof Error ? error : new Error(String(error));
485
+ Log.warn('[DataConfig] Ignoring invalid Protocol V2 resources config:', error);
486
+ }
487
+ const enrichedPro2Config = this.enrichFirmwareReleaseInfo(data.pro2);
488
+ const enrichedNeoConfig = this.enrichFirmwareReleaseInfo(data.neo);
489
+ const { resources: _unvalidatedResources, ...pro2Config } = enrichedPro2Config;
490
+ const { resources: _unvalidatedNeoResources, ...neoConfig } = enrichedNeoConfig;
491
+ this.deviceMap = {
492
+ [EDeviceType.Classic]: this.enrichFirmwareReleaseInfo(data.classic),
493
+ [EDeviceType.Classic1s]: this.enrichFirmwareReleaseInfo(data.classic1s),
494
+ [EDeviceType.ClassicPure]: this.enrichFirmwareReleaseInfo(data.classicpure),
495
+ [EDeviceType.Mini]: this.enrichFirmwareReleaseInfo(data.mini),
496
+ [EDeviceType.Touch]: this.enrichFirmwareReleaseInfo(data.touch),
497
+ [EDeviceType.Pro]: this.enrichFirmwareReleaseInfo(data.pro),
498
+ [EDeviceType.Pro2]: {
499
+ ...pro2Config,
500
+ ...(pro2Resources ? { resources: pro2Resources } : undefined),
501
+ },
502
+ [EDeviceType.Neo]: {
503
+ ...neoConfig,
504
+ ...(neoResources ? { resources: neoResources } : undefined),
505
+ },
506
+ };
507
+ this.assets = {
508
+ bridge: data.bridge,
509
+ };
510
+ }
511
+
504
512
  static updateEnv(newEnv: ConnectSettings['env']) {
505
513
  if (this.settings) {
506
514
  const prevEnv = this.settings.env;
@@ -115,10 +115,21 @@ export const parseConnectSettings = (input: Partial<ConnectSettings> = {}) => {
115
115
  settings.fetchConfig = input.fetchConfig;
116
116
  }
117
117
 
118
+ if (
119
+ input.firmwareManifestMode === 'sdk-managed' ||
120
+ input.firmwareManifestMode === 'external-only'
121
+ ) {
122
+ settings.firmwareManifestMode = input.firmwareManifestMode;
123
+ }
124
+
118
125
  if (input.configFetcher) {
119
126
  settings.configFetcher = input.configFetcher;
120
127
  }
121
128
 
129
+ if (input.preloadedConfig) {
130
+ settings.preloadedConfig = input.preloadedConfig;
131
+ }
132
+
122
133
  return settings;
123
134
  };
124
135
 
@@ -99,6 +99,11 @@ export type InitOptions = {
99
99
  protocolV2DeviceInfoTimeoutMs?: number;
100
100
  /** Refresh Protocol V2 runtime state before returning discovery results. */
101
101
  refreshRuntimeState?: boolean;
102
+ /**
103
+ * Protocol V1 Initialize response timeout override. Reboot-wait polling passes a
104
+ * short value so an unanswered probe settles before the next poll tick.
105
+ */
106
+ timeoutMs?: number;
102
107
  };
103
108
 
104
109
  export type RunOptions = {
@@ -908,7 +913,7 @@ export class Device extends EventEmitter {
908
913
  const initStartAt = Date.now();
909
914
  const { message } = await this.commands.typedCall('Initialize', 'Features', payload, {
910
915
  // iOS BLE bound devices can need close to 20 seconds.
911
- timeoutMs: 25 * 1000,
916
+ timeoutMs: options?.timeoutMs ?? 25 * 1000,
912
917
  });
913
918
  this.setLastInitializeDuration(Date.now() - initStartAt);
914
919
  this._updateFeatures(message, initSession);
package/src/index.ts CHANGED
@@ -19,6 +19,11 @@ export * from './types';
19
19
  export { whitelist, whitelistExtension } from './data/config';
20
20
  export { executeCallback, cleanupCallback };
21
21
  export { preloadSessionCache } from './device/Device';
22
+ export {
23
+ getFirmwareUpdateHostBindingGeneration,
24
+ registerFirmwareUpdateHostBinding,
25
+ unregisterFirmwareUpdateHostBinding,
26
+ } from './api/firmware/FirmwareHostBinding';
22
27
 
23
28
  const HardwareSdk = ({
24
29
  init,
package/src/inject.ts CHANGED
@@ -1,7 +1,18 @@
1
+ import { getFirmwareUpdateCapabilities } from './api/firmware/FirmwareUpdateCapabilities';
2
+ import {
3
+ prepareFirmwareUpdatePlan,
4
+ validateFirmwareUpdatePreparedPlan,
5
+ } from './api/firmware/FirmwareUpdatePreparedPlan';
6
+ import {
7
+ getFirmwareUpdateHostBindingGeneration,
8
+ registerFirmwareUpdateHostBinding,
9
+ unregisterFirmwareUpdateHostBinding,
10
+ } from './api/firmware/FirmwareHostBinding';
11
+
1
12
  import type { HardwareConnectProtocol } from '@onekeyfe/hd-shared';
2
- import type { Unsuccessful } from './types';
3
13
  import type { EventEmitter } from 'events';
4
14
  import type { CallMethod } from './events';
15
+ import type { Unsuccessful } from './types';
5
16
  import type { CoreApi } from './types/api';
6
17
  import type { AllNetworkAddress } from './types/api/allNetworkGetAddress';
7
18
 
@@ -116,7 +127,10 @@ export const inject = ({
116
127
 
117
128
  setDeviceConnectProtocol: protocolAwareCall.setDeviceConnectProtocol,
118
129
 
119
- dispose,
130
+ dispose: async () => {
131
+ unregisterFirmwareUpdateHostBinding();
132
+ await dispose();
133
+ },
120
134
 
121
135
  uiResponse,
122
136
 
@@ -148,6 +162,12 @@ export const createCoreApi = (
148
162
  | 'updateSettings'
149
163
  | 'switchTransport'
150
164
  > => ({
165
+ getFirmwareUpdateCapabilities,
166
+ registerFirmwareUpdateHostBinding,
167
+ unregisterFirmwareUpdateHostBinding,
168
+ getFirmwareUpdateHostBindingGeneration,
169
+ prepareFirmwareUpdatePlan,
170
+ validateFirmwareUpdatePreparedPlan,
151
171
  getLogs: () => call({ method: 'getLogs' }),
152
172
  clearSessionCache: params => call({ ...params, method: 'clearSessionCache' }),
153
173
  /**